Page MenuHome GnuPG

No OneTemporary

This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/agent/agent.h b/agent/agent.h
index dbb3000dd..7ffbd5cc2 100644
--- a/agent/agent.h
+++ b/agent/agent.h
@@ -1,798 +1,798 @@
/* agent.h - Global definitions for the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef AGENT_H
#define AGENT_H
#ifdef GPG_ERR_SOURCE_DEFAULT
#error GPG_ERR_SOURCE_DEFAULT already defined
#endif
#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGAGENT
#include <gpg-error.h>
#define map_assuan_err(a) \
map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
#include <errno.h>
#include <assuan.h>
#include <gcrypt.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "../common/session-env.h"
#include "../common/shareddefs.h"
#include "../common/name-value.h"
/* To convey some special hash algorithms we use algorithm numbers
reserved for application use. */
#ifndef GCRY_MODULE_ID_USER
#define GCRY_MODULE_ID_USER 1024
#endif
#define MD_USER_TLS_MD5SHA1 (GCRY_MODULE_ID_USER+1)
/* Maximum length of a digest. */
#define MAX_DIGEST_LEN 64
/* The maximum length of a passphrase (in bytes). Note: this is
further constrained by the Assuan line length (and any other text on
the same line). However, the Assuan line length is 1k bytes so
this shouldn't be a problem in practice. */
#define MAX_PASSPHRASE_LEN 255
/* The daemons we support. When you add a new daemon, add to
both the daemon_type and the daemon_modules array in call-daemon.c */
enum daemon_type
{
DAEMON_SCD,
DAEMON_TPM2D,
DAEMON_MAX_TYPE
};
/* A large struct name "opt" to keep global flags */
EXTERN_UNLESS_MAIN_MODULE
struct
{
unsigned int debug; /* Debug flags (DBG_foo_VALUE) */
int verbose; /* Verbosity level */
int quiet; /* Be as quiet as possible */
int dry_run; /* Don't change any persistent data */
int batch; /* Batch mode */
/* True if we handle sigusr2. */
int sigusr2_enabled;
/* Environment settings gathered at program start or changed using the
Assuan command UPDATESTARTUPTTY. */
session_env_t startup_env;
char *startup_lc_ctype;
char *startup_lc_messages;
/* Enable pinentry debugging (--debug 1024 should also be used). */
int debug_pinentry;
/* Filename of the program to start as pinentry (malloced). */
char *pinentry_program;
/* Filename of the program to handle daemon tasks. */
const char *daemon_program[DAEMON_MAX_TYPE];
int disable_daemon[DAEMON_MAX_TYPE]; /* Never use the daemon. */
int no_grab; /* Don't let the pinentry grab the keyboard */
/* The name of the file pinentry shall touch before exiting. If
this is not set the file name of the standard socket is used. */
const char *pinentry_touch_file;
/* A string where the first character is used by the pinentry as a
custom invisible character. */
char *pinentry_invisible_char;
/* The timeout value for the Pinentry in seconds. This is passed to
the pinentry if it is not 0. It is up to the pinentry to act
upon this timeout value. */
unsigned long pinentry_timeout;
/* If set, then passphrase formatting is enabled in pinentry. */
int pinentry_formatted_passphrase;
/* The default and maximum TTL of cache entries. */
unsigned long def_cache_ttl; /* Default. */
unsigned long def_cache_ttl_ssh; /* for SSH. */
unsigned long max_cache_ttl; /* Default. */
unsigned long max_cache_ttl_ssh; /* for SSH. */
/* Flag disallowing bypassing of the warning. */
int enforce_passphrase_constraints;
- /* The require minmum length of a passphrase. */
+ /* The required minimum length of a passphrase. */
unsigned int min_passphrase_len;
/* The minimum number of non-alpha characters in a passphrase. */
unsigned int min_passphrase_nonalpha;
/* File name with a patternfile or NULL if not enabled. If the
* second one is set, it is used for symmetric only encryption
* instead of the former. */
const char *check_passphrase_pattern;
const char *check_sym_passphrase_pattern;
/* If not 0 the user is asked to change his passphrase after these
number of days. */
unsigned int max_passphrase_days;
/* If set, a passphrase history will be written and checked at each
passphrase change. */
int enable_passphrase_history;
int running_detached; /* We are running detached from the tty. */
/* If this global option is true, the passphrase cache is ignored
for signing operations. */
int ignore_cache_for_signing;
/* If this global option is true, the user is allowed to
interactively mark certificate in trustlist.txt as trusted. */
int allow_mark_trusted;
/* Only use the system trustlist. */
int no_user_trustlist;
/* The standard system trustlist is SYSCONFDIR/trustlist.txt. This
* option can be used to change the name. */
const char *sys_trustlist_name;
/* If this global option is true, the Assuan command
PRESET_PASSPHRASE is allowed. */
int allow_preset_passphrase;
/* If this global option is true, the Assuan option
pinentry-mode=loopback is allowed. */
int allow_loopback_pinentry;
/* Allow the use of an external password cache. If this option is
enabled (which is the default) we send an option to Pinentry
to allow it to enable such a cache. */
int allow_external_cache;
/* If this global option is true, the Assuan option of Pinentry
allow-emacs-prompt is allowed. */
int allow_emacs_pinentry;
int keep_tty; /* Don't switch the TTY (for pinentry) on request */
int keep_display; /* Don't switch the DISPLAY (for pinentry) on request */
/* This global option indicates the use of an extra socket. Note
that we use a hack for cleanup handling in gpg-agent.c: If the
value is less than 2 the name has not yet been malloced. */
int extra_socket;
/* This global option indicates the use of an extra socket for web
browsers. Note that we use a hack for cleanup handling in
gpg-agent.c: If the value is less than 2 the name has not yet
been malloced. */
int browser_socket;
/* The digest algorithm to use for ssh fingerprints when
* communicating with the user. */
int ssh_fingerprint_digest;
/* The value of the option --s2k-count. If this option is not given
* or 0 an auto-calibrated value is used. */
unsigned long s2k_count;
} opt;
/* Bit values for the --debug option. */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_MEMORY_VALUE 32 /* debug memory allocation stuff */
#define DBG_CACHE_VALUE 64 /* debug the caching */
#define DBG_MEMSTAT_VALUE 128 /* show memory statistics */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* Enable Assuan debugging. */
/* Test macros for the debug option. */
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_MEMORY (opt.debug & DBG_MEMORY_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
/* Forward reference for local definitions in command.c. */
struct server_local_s;
/* Declaration of objects from command-ssh.c. */
struct ssh_control_file_s;
typedef struct ssh_control_file_s *ssh_control_file_t;
/* Forward reference for local definitions in call-scd.c. */
struct daemon_local_s;
/* Object to hold ephemeral secret keys. */
struct ephemeral_private_key_s
{
struct ephemeral_private_key_s *next;
unsigned char grip[KEYGRIP_LEN];
unsigned char *keybuf; /* Canon-s-exp with the private key (malloced). */
size_t keybuflen;
};
typedef struct ephemeral_private_key_s *ephemeral_private_key_t;
/* Collection of data per session (aka connection). */
struct server_control_s
{
/* Private data used to fire up the connection thread. We use this
structure do avoid an extra allocation for only a few bytes while
spawning a new connection thread. */
struct {
gnupg_fd_t fd;
} thread_startup;
/* Flag indicating the connection is run in restricted mode.
A value of 1 if used for --extra-socket,
a value of 2 is used for --browser-socket. */
int restricted;
/* Private data of the server (command.c). */
struct server_local_s *server_local;
/* Private data of the daemon (call-XXX.c). */
struct daemon_local_s *d_local[DAEMON_MAX_TYPE];
/* Linked list with ephemeral stored private keys. */
ephemeral_private_key_t ephemeral_keys;
/* If set functions will lookup keys in the ephemeral_keys list. */
int ephemeral_mode;
/* Environment settings for the connection. */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
unsigned long client_pid;
int client_uid;
/* The current pinentry mode. */
pinentry_mode_t pinentry_mode;
/* The TTL used for the --preset option of certain commands. */
int cache_ttl_opt_preset;
/* Information on the currently used digest (for signing commands). */
struct {
char *data; /* NULL or malloced data of length VALUELEN. If
this is set the other fields are ignored. Used
for PureEdDSA and RSA with PSS (in which case
data_is_pss is also set). */
int valuelen;
int algo;
unsigned char value[MAX_DIGEST_LEN];
unsigned int raw_value: 1;
- unsigned int is_pss: 1; /* DATA holds PSS formated data. */
+ unsigned int is_pss: 1; /* DATA holds PSS formatted data. */
} digest;
unsigned int have_keygrip: 1;
unsigned int have_keygrip1: 1;
unsigned char keygrip[20];
unsigned char keygrip1[20]; /* Another keygrip for hybrid crypto. */
/* A flag to enable a hack to send the PKAUTH command instead of the
PKSIGN command to the scdaemon. */
int use_auth_call;
/* A flag to inhibit enforced passphrase change during an explicit
passwd command. */
int in_passwd;
/* The current S2K which might be different from the calibrated
count. */
unsigned long s2k_count;
/* If pinentry is active for this thread. It can be more than 1,
when pinentry is called recursively. */
int pinentry_active;
};
/* Status of pinentry. */
enum
{
PINENTRY_STATUS_CLOSE_BUTTON = 1 << 0,
PINENTRY_STATUS_PIN_REPEATED = 1 << 8,
PINENTRY_STATUS_PASSWORD_FROM_CACHE = 1 << 9,
PINENTRY_STATUS_PASSWORD_GENERATED = 1 << 10
};
/* Information pertaining to pinentry requests. */
struct pin_entry_info_s
{
int min_digits; /* min. number of digits required or 0 for freeform entry */
int max_digits; /* max. number of allowed digits allowed*/
int max_tries; /* max. number of allowed tries. */
unsigned int constraints_flags; /* CHECK_CONSTRAINTS_... */
int failed_tries; /* Number of tries so far failed. */
int with_qualitybar; /* Set if the quality bar should be displayed. */
int with_repeat; /* Request repetition of the passphrase. */
int repeat_okay; /* Repetition worked. */
unsigned int status; /* Status. */
gpg_error_t (*check_cb)(struct pin_entry_info_s *); /* CB used to check
the PIN */
void *check_cb_arg; /* optional argument which might be of use in the CB */
const char *cb_errtext; /* used by the cb to display a specific error */
size_t max_length; /* Allocated length of the buffer PIN. */
char pin[1]; /* The buffer to hold the PIN or passphrase.
It's actual allocated length is given by
MAX_LENGTH (above). */
};
/* Types of the private keys. */
enum
{
PRIVATE_KEY_UNKNOWN = 0, /* Type of key is not known. */
PRIVATE_KEY_CLEAR = 1, /* The key is not protected. */
PRIVATE_KEY_PROTECTED = 2, /* The key is protected. */
PRIVATE_KEY_SHADOWED = 3, /* The key is a stub for a smartcard
based key. */
PROTECTED_SHARED_SECRET = 4, /* RFU. */
PRIVATE_KEY_OPENPGP_NONE = 5 /* openpgp-native with protection "none". */
};
/* Values for the cache_mode arguments. */
typedef enum
{
CACHE_MODE_IGNORE = 0, /* Special mode to bypass the cache. */
CACHE_MODE_ANY, /* Any mode except ignore and data matches. */
CACHE_MODE_NORMAL, /* Normal cache (gpg-agent). */
CACHE_MODE_USER, /* GET_PASSPHRASE related cache. */
CACHE_MODE_SSH, /* SSH related cache. */
CACHE_MODE_NONCE, /* This is a non-predictable nonce. */
CACHE_MODE_PIN, /* PINs stored/retrieved by scdaemon. */
CACHE_MODE_DATA /* Arbitrary data. */
}
cache_mode_t;
/* The TTL is seconds used for adding a new nonce mode cache item. */
#define CACHE_TTL_NONCE 120
/* The TTL in seconds used by the --preset option of some commands.
This is the default value changeable by an OPTION command. */
#define CACHE_TTL_OPT_PRESET 900
/* The type of a function to lookup a TTL by a keygrip. */
typedef int (*lookup_ttl_t)(const char *hexgrip);
/* This is a special version of the usual _() gettext macro. It
assumes a server connection control variable with the name "ctrl"
and uses that to translate a string according to the locale set for
the connection. The macro LunderscoreIMPL is used by i18n to
actually define the inline function when needed. */
#if defined (ENABLE_NLS) || defined (USE_SIMPLE_GETTEXT)
#define L_(a) agent_Lunderscore (ctrl, (a))
#define LunderscorePROTO \
static inline const char *agent_Lunderscore (ctrl_t ctrl, \
const char *string) \
GNUPG_GCC_ATTR_FORMAT_ARG(2);
#define LunderscoreIMPL \
static inline const char * \
agent_Lunderscore (ctrl_t ctrl, const char *string) \
{ \
return ctrl? i18n_localegettext (ctrl->lc_messages, string) \
/* */: gettext (string); \
}
#else
#define L_(a) (a)
#endif
/* Information from scdaemon for card keys. */
struct card_key_info_s
{
struct card_key_info_s *next;
char keygrip[41];
char *serialno;
char *idstr;
char *usage;
};
/*-- gpg-agent.c --*/
void agent_exit (int rc)
GPGRT_ATTR_NORETURN; /* Also implemented in other tools */
void agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl);
gpg_error_t agent_copy_startup_env (ctrl_t ctrl);
const char *get_agent_socket_name (void);
const char *get_agent_ssh_socket_name (void);
int get_agent_active_connection_count (void);
#ifdef HAVE_W32_SYSTEM
void *get_agent_daemon_notify_event (void);
#endif
void agent_sighup_action (void);
int map_pk_openpgp_to_gcry (int openpgp_algo);
void agent_kick_the_loop (void);
/*-- command.c --*/
gpg_error_t agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid,
const char *extra);
gpg_error_t agent_write_status (ctrl_t ctrl, const char *keyword, ...)
GPGRT_ATTR_SENTINEL(0);
gpg_error_t agent_print_status (ctrl_t ctrl, const char *keyword,
const char *format, ...)
GPGRT_ATTR_PRINTF(3,4);
void bump_key_eventcounter (void);
void bump_card_eventcounter (void);
void start_command_handler (ctrl_t, gnupg_fd_t, gnupg_fd_t);
gpg_error_t pinentry_loopback (ctrl_t, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length);
gpg_error_t pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
int ask_confirmation,
const char *ok, const char *notok);
#ifdef HAVE_W32_SYSTEM
int serve_mmapped_ssh_request (ctrl_t ctrl,
unsigned char *request, size_t maxreqlen);
#endif /*HAVE_W32_SYSTEM*/
/*-- command-ssh.c --*/
ssh_control_file_t ssh_open_control_file (void);
void ssh_close_control_file (ssh_control_file_t cf);
gpg_error_t ssh_read_control_file (ssh_control_file_t cf,
char *r_hexgrip, int *r_disabled,
int *r_ttl, int *r_confirm);
gpg_error_t ssh_search_control_file (ssh_control_file_t cf,
const char *hexgrip,
int *r_disabled,
int *r_ttl, int *r_confirm);
void start_command_handler_ssh_stream (ctrl_t ctrl, estream_t stream);
void start_command_handler_ssh (ctrl_t, gnupg_fd_t);
/*-- findkey.c --*/
gpg_error_t agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result);
gpg_error_t agent_write_private_key (ctrl_t ctrl,
const unsigned char *grip,
const void *buffer, size_t length,
int force,
const char *serialno, const char *keyref,
const char *dispserialno,
time_t timestamp);
gpg_error_t agent_key_from_file (ctrl_t ctrl,
const char *cache_nonce,
const char *desc_text,
const unsigned char *grip,
unsigned char **shadow_info,
cache_mode_t cache_mode,
lookup_ttl_t lookup_ttl,
gcry_sexp_t *result,
char **r_passphrase, time_t *r_timestamp);
gpg_error_t agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta);
gpg_error_t agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result);
gpg_error_t agent_ssh_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result, int *r_order);
int agent_pk_get_algo (gcry_sexp_t s_key);
int agent_is_tpm2_key(gcry_sexp_t s_key);
int agent_key_available (ctrl_t ctrl, const unsigned char *grip);
gpg_error_t agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype,
unsigned char **r_shadow_info,
unsigned char **r_shadow_info_type);
gpg_error_t agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip,
int force, int only_stubs);
gpg_error_t agent_update_private_key (ctrl_t ctrl,
const unsigned char *grip, nvc_t pk);
/*-- call-pinentry.c --*/
void initialize_module_call_pinentry (void);
void agent_query_dump_state (void);
void agent_reset_query (ctrl_t ctrl);
int pinentry_active_p (ctrl_t ctrl, int waitseconds);
gpg_error_t agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *inital_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode);
int agent_get_passphrase (ctrl_t ctrl, char **retpass,
const char *desc, const char *prompt,
const char *errtext, int with_qualitybar,
const char *keyinfo, cache_mode_t cache_mode,
struct pin_entry_info_s *pininfo);
int agent_get_confirmation (ctrl_t ctrl, const char *desc, const char *ok,
const char *notokay, int with_cancel);
int agent_show_message (ctrl_t ctrl, const char *desc, const char *ok_btn);
int agent_popup_message_start (ctrl_t ctrl,
const char *desc, const char *ok_btn);
void agent_popup_message_stop (ctrl_t ctrl);
int agent_clear_passphrase (ctrl_t ctrl,
const char *keyinfo, cache_mode_t cache_mode);
/*-- cache.c --*/
void initialize_module_cache (void);
void deinitialize_module_cache (void);
struct timespec *agent_cache_expiration (void);
void agent_flush_cache (int pincache_only);
int agent_put_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode,
const char *data, int ttl);
char *agent_get_cache (ctrl_t ctrl, const char *key, cache_mode_t cache_mode);
void agent_store_cache_hit (const char *key);
/*-- pksign.c --*/
gpg_error_t agent_pksign_do (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
gcry_sexp_t *signature_sexp,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
const void *overridedata, size_t overridedatalen);
gpg_error_t agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
membuf_t *outbuf, cache_mode_t cache_mode);
/*-- pkdecrypt.c --*/
gpg_error_t agent_pkdecrypt (ctrl_t ctrl, const char *desc_text,
const unsigned char *ciphertext, size_t ciphertextlen,
membuf_t *outbuf, int *r_padding);
enum kemids
{
KEM_PQC_PGP,
KEM_PGP,
KEM_CMS
};
gpg_error_t agent_kem_decrypt (ctrl_t ctrl, const char *desc_text, int kemid,
const unsigned char *ct, size_t ctlen,
const unsigned char *option, size_t optionlen,
membuf_t *outbuf);
/*-- genkey.c --*/
#define CHECK_CONSTRAINTS_NOT_EMPTY 1
#define CHECK_CONSTRAINTS_NEW_SYMKEY 2
#define GENKEY_FLAG_NO_PROTECTION 1
#define GENKEY_FLAG_PRESET 2
void clear_ephemeral_keys (ctrl_t ctrl);
int check_passphrase_constraints (ctrl_t ctrl, const char *pw,
unsigned int flags,
char **failed_constraint);
gpg_error_t agent_ask_new_passphrase (ctrl_t ctrl, const char *prompt,
char **r_passphrase);
int agent_genkey (ctrl_t ctrl, unsigned int flags,
const char *cache_nonce, time_t timestamp,
const char *keyparam, size_t keyparmlen,
const char *override_passphrase,
membuf_t *outbuf);
gpg_error_t agent_protect_and_store (ctrl_t ctrl, gcry_sexp_t s_skey,
char **passphrase_addr);
/*-- protect.c --*/
void set_s2k_calibration_time (unsigned int milliseconds);
unsigned long get_calibrated_s2k_count (void);
unsigned long get_standard_s2k_count (void);
unsigned char get_standard_s2k_count_rfc4880 (void);
unsigned long get_standard_s2k_time (void);
int agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count);
gpg_error_t agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen);
int agent_private_key_type (const unsigned char *privatekey);
unsigned char *make_shadow_info (const char *serialno, const char *idstring);
int agent_shadow_key (const unsigned char *pubkey,
const unsigned char *shadow_info,
unsigned char **result);
int agent_shadow_key_type (const unsigned char *pubkey,
const unsigned char *shadow_info,
const unsigned char *type,
unsigned char **result);
gpg_error_t agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info);
gpg_error_t agent_get_shadow_info_type (const unsigned char *shadowkey,
unsigned char const **shadow_info,
unsigned char **shadow_type);
gpg_error_t parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr, int *r_pinlen);
gpg_error_t s2k_hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned int s2kcount,
unsigned char *key, size_t keylen);
gpg_error_t agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force,
const char *dispserialno);
/*-- trustlist.c --*/
void initialize_module_trustlist (void);
gpg_error_t agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled);
gpg_error_t agent_listtrusted (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-tpm2.c --*/
#ifdef HAVE_LIBTSS
int divert_tpm2_pksign (ctrl_t ctrl,
const unsigned char *digest, size_t digestlen, int algo,
const unsigned char *shadow_info, unsigned char **r_sig,
size_t *r_siglen);
int divert_tpm2_pkdecrypt (ctrl_t ctrl,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len, int *r_padding);
int divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t s_skey);
#else /*!HAVE_LIBTSS*/
static inline int
divert_tpm2_pksign (ctrl_t ctrl,
const unsigned char *digest,
size_t digestlen, int algo,
const unsigned char *shadow_info,
unsigned char **r_sig,
size_t *r_siglen)
{
(void)ctrl; (void)digest; (void)digestlen;
(void)algo; (void)shadow_info; (void)r_sig; (void)r_siglen;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
static inline int
divert_tpm2_pkdecrypt (ctrl_t ctrl,
const unsigned char *cipher,
const unsigned char *shadow_info,
char **r_buf, size_t *r_len,
int *r_padding)
{
(void)ctrl; (void)cipher; (void)shadow_info;
(void)r_buf; (void)r_len; (void)r_padding;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
static inline int
divert_tpm2_writekey (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t s_skey)
{
(void)ctrl; (void)grip; (void)s_skey;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
#endif /*!HAVE_LIBTSS*/
/*-- divert-scd.c --*/
int divert_pksign (ctrl_t ctrl,
const unsigned char *grip,
const unsigned char *digest, size_t digestlen, int algo,
unsigned char **r_sig,
size_t *r_siglen);
int divert_pkdecrypt (ctrl_t ctrl,
const unsigned char *grip,
const unsigned char *cipher,
char **r_buf, size_t *r_len, int *r_padding);
int divert_generic_cmd (ctrl_t ctrl,
const char *cmdline, void *assuan_context);
gpg_error_t divert_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen);
gpg_error_t agent_card_ecc_kem (ctrl_t ctrl, const unsigned char *ecc_ct,
size_t ecc_point_len, unsigned char *ecc_ecdh);
/*-- call-daemon.c --*/
gpg_error_t daemon_start (enum daemon_type type, ctrl_t ctrl);
assuan_context_t daemon_type_ctx (enum daemon_type type, ctrl_t ctrl);
gpg_error_t daemon_unlock (enum daemon_type type, ctrl_t ctrl, gpg_error_t rc);
void initialize_module_daemon (void);
void agent_daemon_dump_state (void);
int agent_daemon_check_running (enum daemon_type type);
void agent_daemon_check_aliveness (void);
void agent_reset_daemon (ctrl_t ctrl);
void agent_kill_daemon (enum daemon_type type);
/*-- call-tpm2d.c --*/
int agent_tpm2d_writekey (ctrl_t ctrl, unsigned char **shadow_info,
gcry_sexp_t s_skey);
int agent_tpm2d_pksign (ctrl_t ctrl, const unsigned char *digest,
size_t digestlen, const unsigned char *shadow_info,
unsigned char **r_sig, size_t *r_siglen);
int agent_tpm2d_pkdecrypt (ctrl_t ctrl, const unsigned char *cipher,
size_t cipherlen, const unsigned char *shadow_info,
char **r_buf, size_t *r_len);
/*-- call-scd.c --*/
int agent_card_learn (ctrl_t ctrl,
void (*kpinfo_cb)(void*, const char *),
void *kpinfo_cb_arg,
void (*certinfo_cb)(void*, const char *),
void *certinfo_cb_arg,
void (*sinfo_cb)(void*, const char *,
size_t, const char *),
void *sinfo_cb_arg);
int agent_card_serialno (ctrl_t ctrl, char **r_serialno, const char *demand);
int agent_card_pksign (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg,
const char *desc_text,
int mdalgo,
const unsigned char *indata, size_t indatalen,
unsigned char **r_buf, size_t *r_buflen);
int agent_card_pkdecrypt (ctrl_t ctrl,
const char *keyid,
int (*getpin_cb)(void *, const char *,
const char *, char*,size_t),
void *getpin_cb_arg,
const char *desc_text,
const unsigned char *indata, size_t indatalen,
char **r_buf, size_t *r_buflen, int *r_padding);
int agent_card_readcert (ctrl_t ctrl,
const char *id, char **r_buf, size_t *r_buflen);
int agent_card_readkey (ctrl_t ctrl, const char *id,
unsigned char **r_buf, char **r_keyref);
gpg_error_t agent_card_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref,
const char *keydata, size_t keydatalen,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg);
gpg_error_t agent_card_getattr (ctrl_t ctrl, const char *name, char **result,
const char *keygrip);
int agent_card_scd (ctrl_t ctrl, const char *cmdline,
int (*getpin_cb)(void *, const char *,
const char *, char*, size_t),
void *getpin_cb_arg, void *assuan_context);
void agent_card_free_keyinfo (struct card_key_info_s *l);
gpg_error_t agent_card_keyinfo (ctrl_t ctrl, const char *keygrip,
int cap, struct card_key_info_s **result);
/*-- learncard.c --*/
int agent_handle_learn (ctrl_t ctrl, int send, void *assuan_context, int force);
/*-- cvt-openpgp.c --*/
gpg_error_t
extract_private_key (gcry_sexp_t s_key, int req_private_key_data,
const char **r_algoname, int *r_npkey, int *r_nskey,
const char **r_format,
gcry_mpi_t *mpi_array, int arraysize,
gcry_sexp_t *r_curve, gcry_sexp_t *r_flags);
/*-- sexp-secret.c --*/
gpg_error_t fixup_when_ecc_private_key (unsigned char *buf, size_t *buflen_p);
gpg_error_t sexp_sscan_private_key (gcry_sexp_t *result, size_t *r_erroff,
unsigned char *buf);
#endif /*AGENT_H*/
diff --git a/agent/call-pinentry.c b/agent/call-pinentry.c
index 4a999ca9a..ba37a775e 100644
--- a/agent/call-pinentry.c
+++ b/agent/call-pinentry.c
@@ -1,2170 +1,2170 @@
/* call-pinentry.c - Spawn the pinentry to query stuff from the user
* Copyright (C) 2001, 2002, 2004, 2007, 2008,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>
#ifndef HAVE_W32_SYSTEM
# include <sys/wait.h>
# include <sys/types.h>
# include <signal.h>
# include <sys/utsname.h>
#endif
#include <npth.h>
#include "agent.h"
#include <assuan.h>
#include "../common/sysutils.h"
#include "../common/i18n.h"
#include "../common/zb32.h"
#ifdef _POSIX_OPEN_MAX
#define MAX_OPEN_FDS _POSIX_OPEN_MAX
#else
#define MAX_OPEN_FDS 20
#endif
/* Because access to the pinentry must be serialized (it is and shall
be a global mutually exclusive dialog) we better timeout pending
requests after some time. 1 minute seem to be a reasonable
time. */
#define LOCK_TIMEOUT (1*60)
/* Define the number of bits to use for a generated pin. The
* passphrase will be rendered as zbase32 which results for 150 bits
* in a string of 30 characters. That fits nicely into the 5
* character blocking which pinentry can do. 128 bits would actually
* be sufficient but can't be formatted nicely. Please do not change
* this value because pattern check files may let such passwords
* always pass. */
#define DEFAULT_GENPIN_BITS 150
/* The assuan context of the current pinentry. */
static assuan_context_t entry_ctx;
/* A list of features of the current pinentry. */
static struct
{
/* The Pinentry support RS+US tabbing. This means that a RS (0x1e)
* starts a new tabbing block in which a US (0x1f) followed by a
* colon marks a colon. A pinentry can use this to pretty print
* name value pairs. */
unsigned int tabbing:1;
} entry_features;
/* A mutex used to serialize access to the pinentry. */
static npth_mutex_t entry_lock;
/* The thread ID of the popup working thread. */
static npth_t popup_tid;
/* A flag used in communication between the popup working thread and
its stop function. */
static int popup_finished;
/* Data to be passed to our callbacks, */
struct entry_parm_s
{
int lines;
size_t size;
unsigned char *buffer;
int status;
unsigned int constraints_flags;
};
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
void
initialize_module_call_pinentry (void)
{
static int initialized;
int err;
if (!initialized)
{
err = npth_mutex_init (&entry_lock, NULL);
if (err)
log_fatal ("error initializing mutex: %s\n", strerror (err));
initialized = 1;
}
}
/* This function may be called to print information pertaining to the
current state of this module to the log. */
void
agent_query_dump_state (void)
{
log_info ("agent_query_dump_state: entry_ctx=%p pid=%ld popup_tid=%lx\n",
entry_ctx, (long)assuan_get_pid (entry_ctx),
(unsigned long)popup_tid);
}
/* Called to make sure that a popup window owned by the current
connection gets closed. */
void
agent_reset_query (ctrl_t ctrl)
{
if (entry_ctx && popup_tid && ctrl->pinentry_active)
{
agent_popup_message_stop (ctrl);
}
}
/* Unlock the pinentry so that another thread can start one and
disconnect that pinentry - we do this after the unlock so that a
stalled pinentry does not block other threads. Fixme: We should
have a timeout in Assuan for the disconnect operation. */
static gpg_error_t
unlock_pinentry (ctrl_t ctrl, gpg_error_t rc)
{
assuan_context_t ctx = entry_ctx;
int err;
if (rc)
{
if (DBG_IPC)
log_debug ("error calling pinentry: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
/* Change the source of the error to pinentry so that the final
consumer of the error code knows that the problem is with
pinentry. For backward compatibility we do not do that for
some common error codes. */
switch (gpg_err_code (rc))
{
case GPG_ERR_NO_PIN_ENTRY:
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
case GPG_ERR_ASS_UNKNOWN_INQUIRE:
case GPG_ERR_ASS_TOO_MUCH_DATA:
case GPG_ERR_NO_PASSPHRASE:
case GPG_ERR_BAD_PASSPHRASE:
case GPG_ERR_BAD_PIN:
case GPG_ERR_BAD_RESET_CODE:
break;
case GPG_ERR_CORRUPTED_PROTECTION:
/* This comes from gpg-agent. */
break;
default:
rc = gpg_err_make (GPG_ERR_SOURCE_PINENTRY, gpg_err_code (rc));
break;
}
}
if (--ctrl->pinentry_active == 0)
{
entry_ctx = NULL;
err = npth_mutex_unlock (&entry_lock);
if (err)
{
log_error ("failed to release the entry lock: %s\n", strerror (err));
if (!rc)
rc = gpg_error_from_errno (err);
}
assuan_release (ctx);
}
return rc;
}
/* Helper for at_fork_cb which can also be called by the parent to
* show which envvars will be set. */
static void
atfork_core (ctrl_t ctrl, int debug_mode)
{
int iterator = 0;
const char *name, *assname, *value;
while ((name = session_env_list_stdenvnames (&iterator, &assname)))
{
/* For all new envvars (!ASSNAME) and the two medium old ones
* which do have an assuan name but are conveyed using
* environment variables, update the environment of the forked
* process. We also pass DISPLAY despite that --display is also
* used when exec-ing the pinentry. The reason is that for
* example the qt5ct tool does not have any arguments and thus
* relies on the DISPLAY envvar. The use case here is a global
* envvar like "QT_QPA_PLATFORMTHEME=qt5ct" which for example is
* useful when using the Qt pinentry under GNOME or XFCE.
*/
if (!assname
|| (!opt.keep_display && !strcmp (name, "DISPLAY"))
|| !strcmp (name, "XAUTHORITY")
|| !strcmp (name, "PINENTRY_USER_DATA"))
{
value = session_env_getenv (ctrl->session_env, name);
if (value)
{
if (debug_mode)
log_debug ("pinentry: atfork used setenv(%s,%s)\n",name,value);
else
gnupg_setenv (name, value, 1);
}
}
}
}
/* To make sure we leave no secrets in our image after forking of the
pinentry, we use this callback. */
static void
atfork_cb (void *opaque, int where)
{
ctrl_t ctrl = opaque;
if (!where)
{
gcry_control (GCRYCTL_TERM_SECMEM);
atfork_core (ctrl, 0);
}
}
/* Status line callback for the FEATURES status. */
static gpg_error_t
getinfo_features_cb (void *opaque, const char *line)
{
const char *args;
char **tokens;
int i;
(void)opaque;
if ((args = has_leading_keyword (line, "FEATURES")))
{
tokens = strtokenize (args, " ");
if (!tokens)
return gpg_error_from_syserror ();
for (i=0; tokens[i]; i++)
if (!strcmp (tokens[i], "tabbing"))
entry_features.tabbing = 1;
xfree (tokens);
}
return 0;
}
static gpg_error_t
getinfo_pid_cb (void *opaque, const void *buffer, size_t length)
{
unsigned long *pid = opaque;
char pidbuf[50];
/* There is only the pid in the server's response. */
if (length >= sizeof pidbuf)
length = sizeof pidbuf -1;
if (length)
{
strncpy (pidbuf, buffer, length);
pidbuf[length] = 0;
*pid = strtoul (pidbuf, NULL, 10);
}
return 0;
}
/* Fork off the pin entry if this has not already been done. Note,
that this function must always be used to acquire the lock for the
pinentry - we will serialize _all_ pinentry calls.
*/
static gpg_error_t
start_pinentry (ctrl_t ctrl)
{
int rc = 0;
const char *full_pgmname;
const char *pgmname;
assuan_context_t ctx;
const char *argv[5];
assuan_fd_t no_close_list[3];
int i;
const char *tmpstr;
unsigned long pinentry_pid;
const char *value;
struct timespec abstime;
char *flavor_version;
int err;
if (ctrl->pinentry_active)
{
/* It's trying to use pinentry recursively. In this situation,
the thread holds ENTRY_LOCK already. */
ctrl->pinentry_active++;
return 0;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += LOCK_TIMEOUT;
err = npth_mutex_timedlock (&entry_lock, &abstime);
if (err)
{
if (err == ETIMEDOUT)
rc = gpg_error (GPG_ERR_TIMEOUT);
else
rc = gpg_error_from_errno (rc);
log_error (_("failed to acquire the pinentry lock: %s\n"),
gpg_strerror (rc));
return rc;
}
if (entry_ctx)
return 0;
if (opt.verbose)
log_info ("starting a new PIN Entry\n");
#ifdef HAVE_W32_SYSTEM
fflush (stdout);
fflush (stderr);
#endif
if (fflush (NULL))
{
#ifndef HAVE_W32_SYSTEM
gpg_error_t tmperr = gpg_error (gpg_err_code_from_errno (errno));
#endif
log_error ("error flushing pending output: %s\n", strerror (errno));
/* At least Windows XP fails here with EBADF. According to docs
and Wine an fflush(NULL) is the same as _flushall. However
the Wine implementation does not flush stdin,stdout and stderr
- see above. Let's try to ignore the error. */
#ifndef HAVE_W32_SYSTEM
return unlock_pinentry (ctrl, tmperr);
#endif
}
full_pgmname = opt.pinentry_program;
if (!full_pgmname || !*full_pgmname)
full_pgmname = gnupg_module_name (GNUPG_MODULE_NAME_PINENTRY);
if ( !(pgmname = strrchr (full_pgmname, '/')))
pgmname = full_pgmname;
else
pgmname++;
/* OS X needs the entire file name in argv[0], so that it can locate
the resource bundle. For other systems we stick to the usual
convention of supplying only the name of the program. */
#ifdef __APPLE__
argv[0] = full_pgmname;
#else /*!__APPLE__*/
argv[0] = pgmname;
#endif /*__APPLE__*/
if (!opt.keep_display
&& (value = session_env_getenv (ctrl->session_env, "DISPLAY")))
{
argv[1] = "--display";
argv[2] = value;
argv[3] = NULL;
}
else
argv[1] = NULL;
i=0;
if (!opt.running_detached)
no_close_list[i++] = assuan_fd_from_posix_fd (fileno (stderr));
no_close_list[i] = ASSUAN_INVALID_FD;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
return rc;
}
ctrl->pinentry_active = 1;
entry_ctx = ctx;
/* We don't want to log the pinentry communication to make the logs
easier to read. We might want to add a new debug option to enable
pinentry logging. */
#ifdef ASSUAN_NO_LOGGING
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, !opt.debug_pinentry);
#endif
/* Connect to the pinentry and perform initial handshaking. Note
that atfork is used to change the environment for pinentry. We
start the server in detached mode to suppress the console window
under Windows. */
rc = assuan_pipe_connect (entry_ctx, full_pgmname, argv,
no_close_list, atfork_cb, ctrl,
ASSUAN_PIPE_CONNECT_DETACHED);
if (rc)
{
log_error ("can't connect to the PIN entry module '%s': %s\n",
full_pgmname, gpg_strerror (rc));
return unlock_pinentry (ctrl, gpg_error (GPG_ERR_NO_PIN_ENTRY));
}
if (DBG_IPC)
log_debug ("connection to PIN entry established\n");
if (opt.debug_pinentry)
atfork_core (ctrl, 1); /* Just show the envvars set after the fork. */
value = session_env_getenv (ctrl->session_env, "PINENTRY_USER_DATA");
if (value != NULL)
{
char *optstr;
if (asprintf (&optstr, "OPTION pinentry-user-data=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
rc = assuan_transact (entry_ctx,
opt.no_grab? "OPTION no-grab":"OPTION grab",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED
|| gpg_err_code (rc) == GPG_ERR_UNKNOWN_OPTION)
{
if (opt.verbose)
log_info ("Option no-grab/grab is ignored by pinentry.\n");
/* Keep going even if the feature is not supported. */
}
else
return unlock_pinentry (ctrl, rc);
}
value = session_env_getenv (ctrl->session_env, "GPG_TTY");
if (value)
{
char *optstr;
if (asprintf (&optstr, "OPTION ttyname=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
value = session_env_getenv (ctrl->session_env, "TERM");
if (value && *value)
{
char *optstr;
if (asprintf (&optstr, "OPTION ttytype=%s", value) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (ctrl->lc_ctype)
{
char *optstr;
if (asprintf (&optstr, "OPTION lc-ctype=%s", ctrl->lc_ctype) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (ctrl->lc_messages)
{
char *optstr;
if (asprintf (&optstr, "OPTION lc-messages=%s", ctrl->lc_messages) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
rc = assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (opt.allow_external_cache)
{
/* Indicate to the pinentry that it may read from an external cache.
It is essential that the pinentry respect this. If the
cached password is not up to date and retry == 1, then, using
a version of GPG Agent that doesn't support this, won't issue
another pin request and the user won't get a chance to
correct the password. */
rc = assuan_transact (entry_ctx, "OPTION allow-external-password-cache",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
if (opt.allow_emacs_pinentry)
{
/* Indicate to the pinentry that it may read passphrase through
Emacs minibuffer, if possible. */
rc = assuan_transact (entry_ctx, "OPTION allow-emacs-prompt",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return unlock_pinentry (ctrl, rc);
}
{
/* Provide a few default strings for use by the pinentries. This
* may help a pinentry to avoid implementing localization code.
* Note that gpg-agent has been set to utf-8 so that the strings
* are in the expected encoding. */
static const struct { const char *key, *value; int what; } tbl[] = {
/* TRANSLATORS: These are labels for buttons etc as used in
* Pinentries. In your translation copy the text before the
* second vertical bar verbatim; translate only the following
* text. An underscore indicates that the next letter should be
* used as an accelerator. Double the underscore to have
* pinentry display a literal underscore. */
{ "ok", N_("|pinentry-label|_OK") },
{ "cancel", N_("|pinentry-label|_Cancel") },
{ "yes", N_("|pinentry-label|_Yes") },
{ "no", N_("|pinentry-label|_No") },
{ "prompt", N_("|pinentry-label|PIN:") },
{ "pwmngr", N_("|pinentry-label|_Save in password manager"), 1 },
{ "cf-visi",N_("Do you really want to make your "
"passphrase visible on the screen?") },
{ "tt-visi",N_("|pinentry-tt|Make passphrase visible") },
{ "tt-hide",N_("|pinentry-tt|Hide passphrase") },
{ "capshint", N_("Caps Lock is on") },
{ NULL, NULL}
};
char *optstr;
int idx;
const char *s, *s2;
for (idx=0; tbl[idx].key; idx++)
{
if (!opt.allow_external_cache && tbl[idx].what == 1)
continue; /* No need for it. */
s = L_(tbl[idx].value);
if (*s == '|' && (s2=strchr (s+1,'|')))
s = s2+1;
if (asprintf (&optstr, "OPTION default-%s=%s", tbl[idx].key, s) < 0 )
return unlock_pinentry (ctrl, out_of_core ());
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
}
}
/* Tell the pinentry that we would prefer that the given character
is used as the invisible character by the entry widget. */
if (opt.pinentry_invisible_char)
{
char *optstr;
if ((optstr = xtryasprintf ("OPTION invisible-char=%s",
opt.pinentry_invisible_char)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing and
older pinentries do not support this feature. */
xfree (optstr);
}
}
if (opt.pinentry_timeout)
{
char *optstr;
if ((optstr = xtryasprintf ("SETTIMEOUT %lu", opt.pinentry_timeout)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing. */
xfree (optstr);
}
}
/* Tell the pinentry the name of a file it shall touch after having
messed with the tty. This is optional and only supported by
newer pinentries and thus we do no error checking. */
tmpstr = opt.pinentry_touch_file;
if (tmpstr && !strcmp (tmpstr, "/dev/null"))
tmpstr = NULL;
else if (!tmpstr)
tmpstr = get_agent_socket_name ();
if (tmpstr)
{
char *optstr;
if (asprintf (&optstr, "OPTION touch-file=%s", tmpstr ) < 0 )
;
else
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
xfree (optstr);
}
}
/* Tell Pinentry about our client. */
if (ctrl->client_pid)
{
char *optstr;
const char *nodename = "";
#ifndef HAVE_W32_SYSTEM
struct utsname utsbuf;
if (!uname (&utsbuf))
nodename = utsbuf.nodename;
#endif /*!HAVE_W32_SYSTEM*/
if ((optstr = xtryasprintf ("OPTION owner=%lu/%d %s",
ctrl->client_pid, ctrl->client_uid,
nodename)))
{
assuan_transact (entry_ctx, optstr, NULL, NULL, NULL, NULL, NULL,
NULL);
/* We ignore errors because this is just a fancy thing and
older pinentries do not support this feature. */
xfree (optstr);
}
}
/* Ask the pinentry for its version and flavor and store that as a
* string in MB. This information is useful for helping users to
* figure out Pinentry problems. Note that "flavor" may also return
* a status line with the features; we use a dedicated handler for
* that. */
{
membuf_t mb;
init_membuf (&mb, 256);
if (assuan_transact (entry_ctx, "GETINFO flavor",
put_membuf_cb, &mb,
NULL, NULL,
getinfo_features_cb, NULL))
put_membuf_str (&mb, "unknown");
put_membuf_str (&mb, " ");
if (assuan_transact (entry_ctx, "GETINFO version",
put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
put_membuf_str (&mb, "unknown");
put_membuf_str (&mb, " ");
if (assuan_transact (entry_ctx, "GETINFO ttyinfo",
put_membuf_cb, &mb, NULL, NULL, NULL, NULL))
put_membuf_str (&mb, "? ? ?");
put_membuf (&mb, "", 1);
flavor_version = get_membuf (&mb, NULL);
}
/* Now ask the Pinentry for its PID. If the Pinentry is new enough
it will send the pid back and we will use an inquire to notify
our client. The client may answer the inquiry either with END or
with CAN to cancel the pinentry. */
rc = assuan_transact (entry_ctx, "GETINFO pid",
getinfo_pid_cb, &pinentry_pid,
NULL, NULL, NULL, NULL);
if (rc)
{
log_info ("You may want to update to a newer pinentry\n");
rc = 0;
}
else if (!rc && pinentry_pid == (unsigned long)(-1L))
log_error ("pinentry did not return a PID\n");
else
{
rc = agent_inq_pinentry_launched (ctrl, pinentry_pid, flavor_version);
if (gpg_err_code (rc) == GPG_ERR_CANCELED
|| gpg_err_code (rc) == GPG_ERR_FULLY_CANCELED)
return unlock_pinentry (ctrl, gpg_err_make (GPG_ERR_SOURCE_DEFAULT,
gpg_err_code (rc)));
rc = 0;
}
xfree (flavor_version);
return rc;
}
/* Returns True if the pinentry is currently active. If WAITSECONDS is
greater than zero the function will wait for this many seconds
before returning. */
int
pinentry_active_p (ctrl_t ctrl, int waitseconds)
{
int err;
(void)ctrl;
if (waitseconds > 0)
{
struct timespec abstime;
int rc;
npth_clock_gettime (&abstime);
abstime.tv_sec += waitseconds;
err = npth_mutex_timedlock (&entry_lock, &abstime);
if (err)
{
if (err == ETIMEDOUT)
rc = gpg_error (GPG_ERR_TIMEOUT);
else
rc = gpg_error (GPG_ERR_INTERNAL);
return rc;
}
}
else
{
err = npth_mutex_trylock (&entry_lock);
if (err)
return gpg_error (GPG_ERR_LOCKED);
}
err = npth_mutex_unlock (&entry_lock);
if (err)
log_error ("failed to release the entry lock at %d: %s\n", __LINE__,
strerror (errno));
return 0;
}
static gpg_error_t
getpin_cb (void *opaque, const void *buffer, size_t length)
{
struct entry_parm_s *parm = opaque;
if (!buffer)
return 0;
/* we expect the pin to fit on one line */
if (parm->lines || length >= parm->size)
return gpg_error (GPG_ERR_ASS_TOO_MUCH_DATA);
/* fixme: we should make sure that the assuan buffer is allocated in
secure memory or read the response byte by byte */
memcpy (parm->buffer, buffer, length);
parm->buffer[length] = 0;
parm->lines++;
return 0;
}
static int
all_digitsp( const char *s)
{
for (; *s && *s >= '0' && *s <= '9'; s++)
;
return !*s;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary Nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. Parsing stops at the end of the string or
a white space character. */
static char *
unescape_passphrase_string (const unsigned char *s)
{
char *buffer, *d;
buffer = d = xtrymalloc_secure (strlen ((const char*)s)+1);
if (!buffer)
return NULL;
while (*s && !spacep (s))
{
if (*s == '%' && s[1] && s[2])
{
s++;
*d = xtoi_2 (s);
if (!*d)
*d = '\xff';
d++;
s += 2;
}
else if (*s == '+')
{
*d++ = ' ';
s++;
}
else
*d++ = *s++;
}
*d = 0;
return buffer;
}
/* Estimate the quality of the passphrase PW and return a value in the
range 0..100. */
static int
estimate_passphrase_quality (const char *pw)
{
int goodlength = opt.min_passphrase_len + opt.min_passphrase_len/3;
int length;
const char *s;
if (goodlength < 1)
return 0;
for (length = 0, s = pw; *s; s++)
if (!spacep (s))
length ++;
if (length > goodlength)
return 100;
return ((length*10) / goodlength)*10;
}
/* Generate a random passphrase in zBase32 encoding (RFC-6189) to be
* used by Pinentry to suggest a passphrase. Note that we have the
* same algorithm in gpg.c for --gen-random at level 30. It is
* important that we always output exactly 30 characters to match the
* special exception we have in the pattern file for symmetric
* encryption. */
static char *
generate_pin (void)
{
unsigned int nbits = DEFAULT_GENPIN_BITS;
size_t nbytes = nbytes = (nbits + 7) / 8;
void *rand;
char *generated;
rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM);
if (!rand)
{
log_error ("failed to generate random pin\n");
return NULL;
}
generated = zb32_encode (rand, nbits);
gcry_free (rand);
return generated;
}
/* Handle inquiries. */
struct inq_cb_parm_s
{
assuan_context_t ctx;
unsigned int flags; /* CHECK_CONSTRAINTS_... */
int genpinhash_valid;
char genpinhash[32]; /* Hash of the last generated pin. */
};
-/* Return true if PIN is indentical to the last generated pin. */
+/* Return true if PIN is identical to the last generated pin. */
static int
is_generated_pin (struct inq_cb_parm_s *parm, const char *pin)
{
char hashbuf[32];
if (!parm->genpinhash_valid)
return 0;
if (!*pin)
return 0;
/* Note that we compare the hash so that we do not need to save the
* generated PIN longer than needed. */
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, pin, strlen (pin));
if (!memcmp (hashbuf, parm->genpinhash, 32))
return 1; /* yes, it is the same. */
return 0;
}
static gpg_error_t
inq_cb (void *opaque, const char *line)
{
struct inq_cb_parm_s *parm = opaque;
gpg_error_t err;
const char *s;
char *pin;
if ((s = has_leading_keyword (line, "QUALITY")))
{
char numbuf[20];
int percent;
pin = unescape_passphrase_string (s);
if (!pin)
err = gpg_error_from_syserror ();
else
{
percent = estimate_passphrase_quality (pin);
if (check_passphrase_constraints (NULL, pin, parm->flags, NULL))
percent = -percent;
snprintf (numbuf, sizeof numbuf, "%d", percent);
err = assuan_send_data (parm->ctx, numbuf, strlen (numbuf));
xfree (pin);
}
}
else if ((s = has_leading_keyword (line, "CHECKPIN")))
{
char *errtext = NULL;
size_t errtextlen;
if (!opt.enforce_passphrase_constraints)
{
log_error ("unexpected inquiry 'CHECKPIN' without enforced "
"passphrase constraints\n");
err = gpg_error (GPG_ERR_ASS_UNEXPECTED_CMD);
goto leave;
}
pin = unescape_passphrase_string (s);
if (!pin)
err = gpg_error_from_syserror ();
else
{
if (!is_generated_pin (parm, pin)
&& check_passphrase_constraints (NULL, pin,parm->flags, &errtext))
{
if (errtext)
{
/* Unescape the percent-escaped errtext because
assuan_send_data escapes it again. */
errtextlen = percent_unescape_inplace (errtext, 0);
err = assuan_send_data (parm->ctx, errtext, errtextlen);
}
else
{
log_error ("passphrase check failed without error text\n");
err = gpg_error (GPG_ERR_GENERAL);
}
}
else
{
err = assuan_send_data (parm->ctx, NULL, 0);
}
xfree (errtext);
xfree (pin);
}
}
else if ((s = has_leading_keyword (line, "GENPIN")))
{
int wasconf;
parm->genpinhash_valid = 0;
pin = generate_pin ();
if (!pin)
{
log_error ("failed to generate a passphrase\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
assuan_begin_confidential (parm->ctx);
err = assuan_send_data (parm->ctx, pin, strlen (pin));
if (!wasconf)
assuan_end_confidential (parm->ctx);
gcry_md_hash_buffer (GCRY_MD_SHA256, parm->genpinhash, pin, strlen (pin));
parm->genpinhash_valid = 1;
xfree (pin);
}
else
{
log_error ("unsupported inquiry '%s' from pinentry\n", line);
err = gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
}
leave:
return err;
}
/* Helper to setup pinentry for genpin action. */
static gpg_error_t
setup_genpin (ctrl_t ctrl)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *tmpstr, *tmpstr2;
const char *tooltip;
(void)ctrl;
/* TRANSLATORS: This string is displayed by Pinentry as the label
for generating a passphrase. */
tmpstr = try_percent_escape (L_("Suggest"), "\t\r\n\f\v");
snprintf (line, DIM(line), "SETGENPIN %s", tmpstr? tmpstr:"");
xfree (tmpstr);
err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == 103 /*(Old assuan error code)*/
|| gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old Pinentry versions. */
else if (err)
return err;
tmpstr2 = gnupg_get_help_string ("pinentry.genpin.tooltip", 0);
if (tmpstr2)
tooltip = tmpstr2;
else
{
/* TRANSLATORS: This string is a tooltip, shown by pinentry when
hovering over the generate button. Please use an appropriate
string to describe what this is about. The length of the
tooltip is limited to about 900 characters. If you do not
translate this entry, a default English text (see source)
will be used. The strcmp thingy is there to detect a
non-translated string. */
tooltip = L_("pinentry.genpin.tooltip");
if (!strcmp ("pinentry.genpin.tooltip", tooltip))
tooltip = "Suggest a random passphrase.";
}
tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
xfree (tmpstr2);
snprintf (line, DIM(line), "SETGENPIN_TT %s", tmpstr? tmpstr:"");
xfree (tmpstr);
err = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == 103 /*(Old assuan error code)*/
|| gpg_err_code (err) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old pinentry versions. */
else if (err)
return err;
return 0;
}
/* Helper to setup pinentry for formatted passphrase. */
static gpg_error_t
setup_formatted_passphrase (ctrl_t ctrl)
{
static const struct { const char *key, *help_id, *value; } tbl[] = {
{ "hint", "pinentry.formatted_passphrase.hint",
/* TRANSLATORS: This is a text shown by pinentry if the option
for formatted passphrase is enabled. The length is
limited to about 900 characters. */
N_("Note: The blanks are not part of the passphrase.") },
{ NULL, NULL }
};
gpg_error_t rc;
char line[ASSUAN_LINELENGTH];
int idx;
char *tmpstr;
const char *s;
char *escapedstr;
(void)ctrl;
if (opt.pinentry_formatted_passphrase)
{
snprintf (line, DIM(line), "OPTION formatted-passphrase");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL,
NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
for (idx=0; tbl[idx].key; idx++)
{
tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0);
if (tmpstr)
s = tmpstr;
else
s = L_(tbl[idx].value);
escapedstr = try_percent_escape (s, "\t\r\n\f\v");
xfree (tmpstr);
if (escapedstr && *escapedstr)
{
snprintf (line, DIM(line), "OPTION formatted-passphrase-%s=%s",
tbl[idx].key, escapedstr);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
rc = 0;
xfree (escapedstr);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
}
}
return 0;
}
/* Helper to setup pinentry for enforced passphrase constraints. */
static gpg_error_t
setup_enforced_constraints (ctrl_t ctrl)
{
static const struct { const char *key, *help_id, *value; } tbl[] = {
{ "hint-short", "pinentry.constraints.hint.short", NULL },
{ "hint-long", "pinentry.constraints.hint.long", NULL },
/* TRANSLATORS: This is a text shown by pinentry as title of a dialog
telling the user that the entered new passphrase does not satisfy
the passphrase constraints. Please keep it short. */
{ "error-title", NULL, N_("Passphrase Not Allowed") },
{ NULL, NULL }
};
gpg_error_t rc;
char line[ASSUAN_LINELENGTH];
int idx;
char *tmpstr;
const char *s;
char *escapedstr;
(void)ctrl;
if (opt.enforce_passphrase_constraints)
{
snprintf (line, DIM(line), "OPTION constraints-enforce");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL,
NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
for (idx=0; tbl[idx].key; idx++)
{
tmpstr = gnupg_get_help_string (tbl[idx].help_id, 0);
if (tmpstr)
s = tmpstr;
else if (tbl[idx].value)
s = L_(tbl[idx].value);
else
{
log_error ("no help string found for %s\n", tbl[idx].help_id);
continue;
}
escapedstr = try_percent_escape (s, "\t\r\n\f\v");
xfree (tmpstr);
if (escapedstr && *escapedstr)
{
snprintf (line, DIM(line), "OPTION constraints-%s=%s",
tbl[idx].key, escapedstr);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
rc = 0; /* Ignore an empty string (would give an IPC error). */
xfree (escapedstr);
if (rc && gpg_err_code (rc) != GPG_ERR_UNKNOWN_OPTION)
return rc;
}
}
return 0;
}
/* Helper for agent_askpin and agent_get_passphrase. */
static gpg_error_t
setup_qualitybar (ctrl_t ctrl)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *tmpstr, *tmpstr2;
const char *tooltip;
(void)ctrl;
/* TRANSLATORS: This string is displayed by Pinentry as the label
for the quality bar. */
tmpstr = try_percent_escape (L_("Quality:"), "\t\r\n\f\v");
snprintf (line, DIM(line), "SETQUALITYBAR %s", tmpstr? tmpstr:"");
xfree (tmpstr);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc == 103 /*(Old assuan error code)*/
|| gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old Pinentry versions. */
else if (rc)
return rc;
tmpstr2 = gnupg_get_help_string ("pinentry.qualitybar.tooltip", 0);
if (tmpstr2)
tooltip = tmpstr2;
else
{
/* TRANSLATORS: This string is a tooltip, shown by pinentry when
hovering over the quality bar. Please use an appropriate
string to describe what this is about. The length of the
tooltip is limited to about 900 characters. If you do not
translate this entry, a default english text (see source)
will be used. */
tooltip = L_("pinentry.qualitybar.tooltip");
if (!strcmp ("pinentry.qualitybar.tooltip", tooltip))
tooltip = ("The quality of the text entered above.\n"
"Please ask your administrator for "
"details about the criteria.");
}
tmpstr = try_percent_escape (tooltip, "\t\r\n\f\v");
xfree (tmpstr2);
snprintf (line, DIM(line), "SETQUALITYBAR_TT %s", tmpstr? tmpstr:"");
xfree (tmpstr);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc == 103 /*(Old assuan error code)*/
|| gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
; /* Ignore Unknown Command from old pinentry versions. */
else if (rc)
return rc;
return 0;
}
/* Check the button_info line for a close action. Also check for the
PIN_REPEATED flag. */
static gpg_error_t
pinentry_status_cb (void *opaque, const char *line)
{
unsigned int *flag = opaque;
const char *args;
if ((args = has_leading_keyword (line, "BUTTON_INFO")))
{
if (!strcmp (args, "close"))
*flag |= PINENTRY_STATUS_CLOSE_BUTTON;
}
else if (has_leading_keyword (line, "PIN_REPEATED"))
{
*flag |= PINENTRY_STATUS_PIN_REPEATED;
}
else if (has_leading_keyword (line, "PASSWORD_FROM_CACHE"))
{
*flag |= PINENTRY_STATUS_PASSWORD_FROM_CACHE;
}
return 0;
}
/* Build a SETDESC command line. This is a dedicated function so that
* it can remove control characters which are not supported by the
* current Pinentry. */
static void
build_cmd_setdesc (char *line, size_t linelen, const char *desc)
{
char *src, *dst;
snprintf (line, linelen, "SETDESC %s", desc);
if (!entry_features.tabbing)
{
/* Remove RS and US. */
for (src=dst=line; *src; src++)
if (!strchr ("\x1e\x1f", *src))
*dst++ = *src;
*dst = 0;
}
}
/* Watch the socket's EOF condition, while checking finish of
foreground thread. When EOF condition is detected, terminate
the pinentry process behind the assuan pipe.
*/
static void *
watch_sock (void *arg)
{
while (1)
{
int err;
fd_set fdset;
struct timeval timeout = { 0, 500000 };
gnupg_fd_t sock = *(gnupg_fd_t *)arg;
if (sock == GNUPG_INVALID_FD)
return NULL;
FD_ZERO (&fdset);
FD_SET (FD2INT (sock), &fdset);
err = npth_select (FD2NUM (sock)+1, &fdset, NULL, NULL, &timeout);
if (err < 0)
{
if (errno == EINTR)
continue;
else
return NULL;
}
/* Possibly, it's EOF. */
if (err > 0)
break;
}
assuan_pipe_kill_server (entry_ctx);
return NULL;
}
static gpg_error_t
watch_sock_start (gnupg_fd_t *sock_p, npth_t *thread_p)
{
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
{
log_error ("do_getpin: error npth_attr_init: %s\n", strerror (err));
return gpg_error_from_errno (err);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
err = npth_create (thread_p, &tattr, watch_sock, sock_p);
npth_attr_destroy (&tattr);
if (err)
{
log_error ("do_getpin: error spawning thread: %s\n", strerror (err));
return gpg_error_from_errno (err);
}
return 0;
}
static void
watch_sock_end (gnupg_fd_t *sock_p, npth_t *thread_p)
{
int err;
*sock_p = GNUPG_INVALID_FD;
err = npth_join (*thread_p, NULL);
if (err)
log_error ("watch_sock_end: error joining thread: %s\n", strerror (err));
}
/* Ask pinentry to get a pin by "GETPIN" command, spawning a thread
detecting the socket's EOF.
*/
static gpg_error_t
do_getpin (ctrl_t ctrl, struct entry_parm_s *parm)
{
gpg_error_t rc;
gnupg_fd_t sock_watched = ctrl->thread_startup.fd;
npth_t thread;
int wasconf;
struct inq_cb_parm_s inq_cb_parm;
rc = watch_sock_start (&sock_watched, &thread);
if (rc)
return rc;
inq_cb_parm.ctx = entry_ctx;
inq_cb_parm.flags = parm->constraints_flags;
inq_cb_parm.genpinhash_valid = 0;
wasconf = assuan_get_flag (entry_ctx, ASSUAN_CONFIDENTIAL);
assuan_begin_confidential (entry_ctx);
rc = assuan_transact (entry_ctx, "GETPIN", getpin_cb, parm,
inq_cb, &inq_cb_parm,
pinentry_status_cb, &parm->status);
if (!wasconf)
assuan_end_confidential (entry_ctx);
if (!rc && parm->buffer && is_generated_pin (&inq_cb_parm, parm->buffer))
parm->status |= PINENTRY_STATUS_PASSWORD_GENERATED;
else
parm->status &= ~PINENTRY_STATUS_PASSWORD_GENERATED;
/* Most pinentries out in the wild return the old Assuan error code
for canceled which gets translated to an assuan Cancel error and
not to the code for a user cancel. Fix this here. */
if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
/* Change error code in case the window close button was clicked
to cancel the operation. */
if ((parm->status & PINENTRY_STATUS_CLOSE_BUTTON)
&& gpg_err_code (rc) == GPG_ERR_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_FULLY_CANCELED);
watch_sock_end (&sock_watched, &thread);
return rc;
}
/* Call the Entry and ask for the PIN. We do check for a valid PIN
number here and repeat it as long as we have invalid formed
numbers. KEYINFO and CACHE_MODE are used to tell pinentry something
about the key. */
gpg_error_t
agent_askpin (ctrl_t ctrl,
const char *desc_text, const char *prompt_text,
const char *initial_errtext,
struct pin_entry_info_s *pininfo,
const char *keyinfo, cache_mode_t cache_mode)
{
gpg_error_t rc;
char line[ASSUAN_LINELENGTH];
struct entry_parm_s parm;
const char *errtext = NULL;
int is_pin = 0;
int is_generated;
if (opt.batch)
return 0; /* fixme: we should return BAD PIN */
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
unsigned char *passphrase;
size_t size;
*pininfo->pin = 0; /* Reset the PIN. */
rc = pinentry_loopback (ctrl, "PASSPHRASE", &passphrase, &size,
pininfo->max_length - 1);
if (rc)
return rc;
memcpy(&pininfo->pin, passphrase, size);
xfree(passphrase);
pininfo->pin[size] = 0;
if (pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
}
return rc;
}
return gpg_error(GPG_ERR_NO_PIN_ENTRY);
}
if (!pininfo || pininfo->max_length < 1)
return gpg_error (GPG_ERR_INV_VALUE);
if (!desc_text && pininfo->min_digits)
desc_text = L_("Please enter your PIN, so that the secret key "
"can be unlocked for this session");
else if (!desc_text)
desc_text = L_("Please enter your passphrase, so that the secret key "
"can be unlocked for this session");
if (prompt_text)
is_pin = !!strstr (prompt_text, "PIN");
else
is_pin = desc_text && strstr (desc_text, "PIN");
rc = start_pinentry (ctrl);
if (rc)
return rc;
/* If we have a KEYINFO string and are normal, user, or ssh cache
mode, we tell that the Pinentry so it may use it for own caching
purposes. Most pinentries won't have this implemented and thus
we do not error out in this case. */
if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH))
snprintf (line, DIM(line), "SETKEYINFO %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
else
snprintf (line, DIM(line), "SETKEYINFO --clear");
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
return unlock_pinentry (ctrl, rc);
build_cmd_setdesc (line, DIM(line), desc_text);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
snprintf (line, DIM(line), "SETPROMPT %s",
prompt_text? prompt_text : is_pin? L_("PIN:") : L_("Passphrase:"));
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
/* If a passphrase quality indicator has been requested and a
minimum passphrase length has not been disabled, send the command
to the pinentry. */
if (pininfo->with_qualitybar && opt.min_passphrase_len )
{
rc = setup_qualitybar (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (initial_errtext)
{
snprintf (line, DIM(line), "SETERROR %s", initial_errtext);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATERROR %s",
L_("does not match - try again"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
pininfo->with_repeat = 0; /* Pinentry does not support it. */
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATOK %s",
L_("Passphrases match."));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
rc = 0; /* Pinentry does not support it. */
}
}
pininfo->repeat_okay = 0;
pininfo->status = 0;
for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
{
memset (&parm, 0, sizeof parm);
parm.size = pininfo->max_length;
*pininfo->pin = 0; /* Reset the PIN. */
parm.buffer = (unsigned char*)pininfo->pin;
parm.constraints_flags = pininfo->constraints_flags;
if (errtext)
{
/* TRANSLATORS: The string is appended to an error message in
the pinentry. The %s is the actual error message, the
two %d give the current and maximum number of tries. */
snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
errtext, pininfo->failed_tries+1, pininfo->max_tries);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
errtext = NULL;
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
rc = do_getpin (ctrl, &parm);
pininfo->status = parm.status;
is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED);
if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
errtext = is_pin? L_("PIN too long")
: L_("Passphrase too long");
else if (rc)
return unlock_pinentry (ctrl, rc);
if (!errtext && pininfo->min_digits && !is_generated)
{
/* do some basic checks on the entered PIN. */
if (!all_digitsp (pininfo->pin))
errtext = L_("Invalid characters in PIN");
else if (pininfo->max_digits
&& strlen (pininfo->pin) > pininfo->max_digits)
errtext = L_("PIN too long");
else if (strlen (pininfo->pin) < pininfo->min_digits)
errtext = L_("PIN too short");
}
if (!errtext && pininfo->check_cb && !is_generated)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
/* When pinentry cache causes an error, return now. */
if (rc
&& (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
return unlock_pinentry (ctrl, rc);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
|| gpg_err_code (rc) == GPG_ERR_BAD_PIN
|| gpg_err_code (rc) == GPG_ERR_BAD_RESET_CODE)
{
if (pininfo->cb_errtext)
errtext = pininfo->cb_errtext;
else
errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
}
else if (rc)
return unlock_pinentry (ctrl, rc);
}
if (!errtext)
{
if (pininfo->with_repeat
&& (pininfo->status & PINENTRY_STATUS_PIN_REPEATED))
pininfo->repeat_okay = 1;
return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */
}
if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
/* The password was read from the cache. Don't count this
against the retry count. */
pininfo->failed_tries --;
}
}
return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
: GPG_ERR_BAD_PASSPHRASE));
}
/* Ask for the passphrase using the supplied arguments. The returned
passphrase needs to be freed by the caller. PININFO is optional
and can be used to have constraints checking while the pinentry
dialog is open (like what we do in agent_askpin). This is very
similar to agent_askpin and we should eventually merge the two
functions. */
int
agent_get_passphrase (ctrl_t ctrl,
char **retpass, const char *desc, const char *prompt,
const char *errtext, int with_qualitybar,
const char *keyinfo, cache_mode_t cache_mode,
struct pin_entry_info_s *pininfo)
{
int rc;
int is_pin;
int is_generated;
char line[ASSUAN_LINELENGTH];
struct entry_parm_s parm;
*retpass = NULL;
if (opt.batch)
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
unsigned char *passphrase;
size_t size;
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK && pininfo)
{
*pininfo->pin = 0; /* Reset the PIN. */
rc = pinentry_loopback (ctrl, "PASSPHRASE",
&passphrase, &size,
pininfo->max_length - 1);
if (rc)
return rc;
memcpy (&pininfo->pin, passphrase, size);
wipememory (passphrase, size);
xfree (passphrase);
pininfo->pin[size] = 0;
if (pininfo->check_cb)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
}
return rc;
}
else if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
/* Legacy variant w/o PININFO. */
return pinentry_loopback (ctrl, "PASSPHRASE",
(unsigned char **)retpass, &size,
MAX_PASSPHRASE_LEN);
}
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
/* Set IS_PIN and if needed a default prompt. */
if (prompt)
is_pin = !!strstr (prompt, "PIN");
else
{
is_pin = desc && strstr (desc, "PIN");
prompt = is_pin? L_("PIN:"): L_("Passphrase:");
}
/* If we have a KEYINFO string and are normal, user, or ssh cache
mode, we tell that the Pinentry so it may use it for own caching
purposes. Most pinentries won't have this implemented and thus
we do not error out in this case. */
if (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH))
snprintf (line, DIM(line), "SETKEYINFO %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
else
snprintf (line, DIM(line), "SETKEYINFO --clear");
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_code (rc) != GPG_ERR_ASS_UNKNOWN_CMD)
return unlock_pinentry (ctrl, rc);
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
snprintf (line, DIM(line), "SETPROMPT %s", prompt);
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
if ((with_qualitybar || (pininfo && pininfo->with_qualitybar))
&& opt.min_passphrase_len)
{
rc = setup_qualitybar (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (errtext)
{
snprintf (line, DIM(line), "SETERROR %s", errtext);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
rc = setup_formatted_passphrase (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
if (!pininfo)
{
/* Legacy method without PININFO. */
memset (&parm, 0, sizeof parm);
parm.size = ASSUAN_LINELENGTH/2 - 5;
parm.buffer = gcry_malloc_secure (parm.size+10);
if (!parm.buffer)
return unlock_pinentry (ctrl, out_of_core ());
rc = do_getpin (ctrl, &parm);
if (rc)
xfree (parm.buffer);
else
*retpass = parm.buffer;
return unlock_pinentry (ctrl, rc);
}
/* We got PININFO. */
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATERROR %s",
L_("does not match - try again"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
pininfo->with_repeat = 0; /* Pinentry does not support it. */
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEATOK %s",
L_("Passphrases match."));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
rc = 0; /* Pinentry does not support it. */
}
(void)setup_genpin (ctrl);
rc = setup_enforced_constraints (ctrl);
if (rc)
return unlock_pinentry (ctrl, rc);
}
pininfo->repeat_okay = 0;
pininfo->status = 0;
for (;pininfo->failed_tries < pininfo->max_tries; pininfo->failed_tries++)
{
memset (&parm, 0, sizeof parm);
parm.constraints_flags = pininfo->constraints_flags;
parm.size = pininfo->max_length;
parm.buffer = (unsigned char*)pininfo->pin;
*pininfo->pin = 0; /* Reset the PIN. */
if (errtext)
{
/* TRANSLATORS: The string is appended to an error message in
the pinentry. The %s is the actual error message, the
two %d give the current and maximum number of tries. */
snprintf (line, DIM(line), L_("SETERROR %s (try %d of %d)"),
errtext, pininfo->failed_tries+1, pininfo->max_tries);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
errtext = NULL;
}
if (pininfo->with_repeat)
{
snprintf (line, DIM(line), "SETREPEAT %s", L_("Repeat:"));
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
rc = do_getpin (ctrl, &parm);
pininfo->status = parm.status;
is_generated = !!(parm.status & PINENTRY_STATUS_PASSWORD_GENERATED);
if (gpg_err_code (rc) == GPG_ERR_ASS_TOO_MUCH_DATA)
errtext = is_pin? L_("PIN too long")
: L_("Passphrase too long");
else if (rc)
return unlock_pinentry (ctrl, rc);
if (!errtext && pininfo->min_digits && !is_generated)
{
/* do some basic checks on the entered PIN. */
if (!all_digitsp (pininfo->pin))
errtext = L_("Invalid characters in PIN");
else if (pininfo->max_digits
&& strlen (pininfo->pin) > pininfo->max_digits)
errtext = L_("PIN too long");
else if (strlen (pininfo->pin) < pininfo->min_digits)
errtext = L_("PIN too short");
}
if (!errtext && pininfo->check_cb && !is_generated)
{
/* More checks by utilizing the optional callback. */
pininfo->cb_errtext = NULL;
rc = pininfo->check_cb (pininfo);
/* When pinentry cache causes an error, return now. */
if (rc && (pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
return unlock_pinentry (ctrl, rc);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE
|| gpg_err_code (rc) == GPG_ERR_BAD_PIN
|| gpg_err_code (rc) == GPG_ERR_BAD_RESET_CODE)
{
if (pininfo->cb_errtext)
errtext = pininfo->cb_errtext;
else
errtext = (is_pin? L_("Bad PIN") : L_("Bad Passphrase"));
}
else if (rc)
return unlock_pinentry (ctrl, rc);
}
if (!errtext)
{
if (pininfo->with_repeat
&& (pininfo->status & PINENTRY_STATUS_PIN_REPEATED))
pininfo->repeat_okay = 1;
return unlock_pinentry (ctrl, 0); /* okay, got a PIN or passphrase */
}
if ((pininfo->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
/* The password was read from the Pinentry's own cache.
Don't count this against the retry count. */
pininfo->failed_tries--;
}
}
return unlock_pinentry (ctrl, gpg_error (pininfo->min_digits? GPG_ERR_BAD_PIN
: GPG_ERR_BAD_PASSPHRASE));
}
/* Pop up the PIN-entry, display the text and the prompt and ask the
user to confirm this. We return 0 for success, ie. the user
confirmed it, GPG_ERR_NOT_CONFIRMED for what the text says or an
other error. If WITH_CANCEL it true an extra cancel button is
displayed to allow the user to easily return a GPG_ERR_CANCELED.
if the Pinentry does not support this, the user can still cancel by
closing the Pinentry window. */
int
agent_get_confirmation (ctrl_t ctrl,
const char *desc, const char *ok,
const char *notok, int with_cancel)
{
int rc;
char line[ASSUAN_LINELENGTH];
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return pinentry_loopback_confirm (ctrl, desc, 1, ok, notok);
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
/* Most pinentries out in the wild return the old Assuan error code
for canceled which gets translated to an assuan Cancel error and
not to the code for a user cancel. Fix this here. */
if (rc && gpg_err_source (rc) && gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
if (rc)
return unlock_pinentry (ctrl, rc);
if (ok)
{
snprintf (line, DIM(line), "SETOK %s", ok);
rc = assuan_transact (entry_ctx,
line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
if (notok)
{
/* Try to use the newer NOTOK feature if a cancel button is
requested. If no cancel button is requested we keep on using
the standard cancel. */
if (with_cancel)
{
snprintf (line, DIM(line), "SETNOTOK %s", notok);
rc = assuan_transact (entry_ctx,
line, NULL, NULL, NULL, NULL, NULL, NULL);
}
else
rc = GPG_ERR_ASS_UNKNOWN_CMD;
if (gpg_err_code (rc) == GPG_ERR_ASS_UNKNOWN_CMD)
{
snprintf (line, DIM(line), "SETCANCEL %s", notok);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
if (rc)
return unlock_pinentry (ctrl, rc);
}
{
gnupg_fd_t sock_watched = ctrl->thread_startup.fd;
npth_t thread;
rc = watch_sock_start (&sock_watched, &thread);
if (!rc)
{
rc = assuan_transact (entry_ctx, "CONFIRM",
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc && gpg_err_source (rc)
&& gpg_err_code (rc) == GPG_ERR_ASS_CANCELED)
rc = gpg_err_make (gpg_err_source (rc), GPG_ERR_CANCELED);
watch_sock_end (&sock_watched, &thread);
}
return unlock_pinentry (ctrl, rc);
}
}
/* The thread running the popup message. */
static void *
popup_message_thread (void *arg)
{
gpg_error_t rc;
gnupg_fd_t sock_watched = *(gnupg_fd_t *)arg;
npth_t thread;
rc = watch_sock_start (&sock_watched, &thread);
if (rc)
return NULL;
/* We use the --one-button hack instead of the MESSAGE command to
allow the use of old Pinentries. Those old Pinentries will then
show an additional Cancel button but that is mostly a visual
annoyance. */
assuan_transact (entry_ctx, "CONFIRM --one-button",
NULL, NULL, NULL, NULL, NULL, NULL);
watch_sock_end (&sock_watched, &thread);
popup_finished = 1;
return NULL;
}
/* Pop up a message window similar to the confirm one but keep it open
until agent_popup_message_stop has been called. It is crucial for
the caller to make sure that the stop function gets called as soon
as the message is not anymore required because the message is
system modal and all other attempts to use the pinentry will fail
(after a timeout). */
int
agent_popup_message_start (ctrl_t ctrl, const char *desc, const char *ok_btn)
{
int rc;
char line[ASSUAN_LINELENGTH];
npth_attr_t tattr;
int err;
if (ctrl->pinentry_mode != PINENTRY_MODE_ASK)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_CANCEL)
return gpg_error (GPG_ERR_CANCELED);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return pinentry_loopback_confirm (ctrl, desc, 0, ok_btn, NULL);
return gpg_error (GPG_ERR_NO_PIN_ENTRY);
}
rc = start_pinentry (ctrl);
if (rc)
return rc;
if (desc)
build_cmd_setdesc (line, DIM(line), desc);
else
snprintf (line, DIM(line), "RESET");
rc = assuan_transact (entry_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
if (ok_btn)
{
snprintf (line, DIM(line), "SETOK %s", ok_btn);
rc = assuan_transact (entry_ctx, line, NULL,NULL,NULL,NULL,NULL,NULL);
if (rc)
return unlock_pinentry (ctrl, rc);
}
err = npth_attr_init (&tattr);
if (err)
return unlock_pinentry (ctrl, gpg_error_from_errno (err));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
popup_finished = 0;
err = npth_create (&popup_tid, &tattr, popup_message_thread,
&ctrl->thread_startup.fd);
npth_attr_destroy (&tattr);
if (err)
{
rc = gpg_error_from_errno (err);
log_error ("error spawning popup message handler: %s\n",
strerror (err) );
return unlock_pinentry (ctrl, rc);
}
npth_setname_np (popup_tid, "popup-message");
return 0;
}
/* Close a popup window. */
void
agent_popup_message_stop (ctrl_t ctrl)
{
int rc;
(void)ctrl;
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
return;
if (!popup_tid || !entry_ctx)
{
log_debug ("agent_popup_message_stop called with no active popup\n");
return;
}
if (popup_finished)
; /* Already finished and ready for joining. */
else
assuan_pipe_kill_server (entry_ctx);
/* Now wait for the thread to terminate. */
rc = npth_join (popup_tid, NULL);
if (rc)
log_debug ("agent_popup_message_stop: pth_join failed: %s\n",
strerror (rc));
/* Thread IDs are opaque, but we try our best here by resetting it
to the same content that a static global variable has. */
memset (&popup_tid, '\0', sizeof (popup_tid));
/* Now we can close the connection. */
unlock_pinentry (ctrl, 0);
}
int
agent_clear_passphrase (ctrl_t ctrl,
const char *keyinfo, cache_mode_t cache_mode)
{
int rc;
char line[ASSUAN_LINELENGTH];
if (! (keyinfo && (cache_mode == CACHE_MODE_NORMAL
|| cache_mode == CACHE_MODE_USER
|| cache_mode == CACHE_MODE_SSH)))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
rc = start_pinentry (ctrl);
if (rc)
return rc;
snprintf (line, DIM(line), "CLEARPASSPHRASE %c/%s",
cache_mode == CACHE_MODE_USER? 'u' :
cache_mode == CACHE_MODE_SSH? 's' : 'n',
keyinfo);
rc = assuan_transact (entry_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return unlock_pinentry (ctrl, rc);
}
diff --git a/agent/command.c b/agent/command.c
index 417a8815f..3012680a1 100644
--- a/agent/command.c
+++ b/agent/command.c
@@ -1,4574 +1,4574 @@
/* command.c - gpg-agent command handler
* Copyright (C) 2001-2011 Free Software Foundation, Inc.
* Copyright (C) 2001-2013 Werner Koch
* Copyright (C) 2015-2021 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* FIXME: we should not use the default assuan buffering but setup
some buffering in secure mempory to protect session keys etc. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <dirent.h>
#include "agent.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "cvt-openpgp.h"
#include "../common/ssh-utils.h"
#include "../common/asshelp.h"
#include "../common/server-help.h"
/* Maximum allowed size of the inquired ciphertext. */
#define MAXLEN_CIPHERTEXT 4096
/* Maximum allowed size of the key parameters. */
#define MAXLEN_KEYPARAM 1024
/* Maximum allowed size of key data as used in inquiries (bytes). */
#define MAXLEN_KEYDATA 8192
/* Maximum length of a secret to store under one key. */
#define MAXLEN_PUT_SECRET 4096
/* The size of the import/export KEK key (in bytes). */
#define KEYWRAP_KEYSIZE (128/8)
/* A shortcut to call assuan_set_error using an gpg_err_code_t and a
text string. */
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
/* Check that the maximum digest length we support has at least the
length of the keygrip. */
#if MAX_DIGEST_LEN < 20
#error MAX_DIGEST_LEN shorter than keygrip
#endif
/* Data used to associate an Assuan context with local server data.
This is this modules local part of the server_control_s struct. */
struct server_local_s
{
/* Our Assuan context. */
assuan_context_t assuan_ctx;
/* If this flag is true, the passphrase cache is used for signing
operations. It defaults to true but may be set on a per
connection base. The global option opt.ignore_cache_for_signing
takes precedence over this flag. */
unsigned int use_cache_for_signing : 1;
/* Flag to suppress I/O logging during a command. */
unsigned int pause_io_logging : 1;
/* Flag indicating that the connection is from ourselves. */
unsigned int connect_from_self : 1;
/* Helper flag for io_monitor to allow suppressing of our own
* greeting in some cases. See io_monitor for details. */
unsigned int greeting_seen : 1;
/* If this flag is set to true the agent will be terminated after
the end of the current session. */
unsigned int stopme : 1;
/* Flag indicating whether pinentry notifications shall be done. */
unsigned int allow_pinentry_notify : 1;
/* An allocated description for the next key operation. This is
used if a pinnetry needs to be popped up. */
char *keydesc;
/* Malloced KEK (Key-Encryption-Key) for the import_key command. */
void *import_key;
/* Malloced KEK for the export_key command. */
void *export_key;
/* Client is aware of the error code GPG_ERR_FULLY_CANCELED. */
int allow_fully_canceled;
/* Last CACHE_NONCE sent as status (malloced). */
char *last_cache_nonce;
/* Last PASSWD_NONCE sent as status (malloced). */
char *last_passwd_nonce;
/* Per connection cache of the keyinfo from the cards. The
* eventcounters for cards at the time the info was fetched is
* stored here as a freshness indicator. */
struct {
struct card_key_info_s *ki;
unsigned int eventno;
unsigned int maybe_key_change;
} last_card_keyinfo;
};
/* An entry for the getval/putval commands. */
struct putval_item_s
{
struct putval_item_s *next;
size_t off; /* Offset to the value into DATA. */
size_t len; /* Length of the value. */
char d[1]; /* Key | Nul | value. */
};
/* A list of key value pairs fpr the getval/putval commands. */
static struct putval_item_s *putval_list;
/* To help polling clients, we keep track of the number of certain
events. This structure keeps those counters. The counters are
integers and there should be no problem if they are overflowing as
callers need to check only whether a counter changed. The actual
values are not meaningful. */
struct
{
/* Incremented if any of the other counters below changed. */
unsigned int any;
/* Incremented if a key is added or removed from the internal privat
key database. */
unsigned int key;
/* Incremented if a change of the card readers stati has been
detected. */
unsigned int card;
/* Internal counter to track possible changes to a key.
* FIXME: This should be replaced by generic notifications from scd.
*/
unsigned int maybe_key_change;
} eventcounter;
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/* Release the memory buffer MB but first wipe out the used memory. */
static void
clear_outbuf (membuf_t *mb)
{
void *p;
size_t n;
p = get_membuf (mb, &n);
if (p)
{
wipememory (p, n);
xfree (p);
}
}
/* Write the content of memory buffer MB as assuan data to CTX and
wipe the buffer out afterwards. */
static gpg_error_t
write_and_clear_outbuf (assuan_context_t ctx, membuf_t *mb)
{
gpg_error_t ae;
void *p;
size_t n;
p = get_membuf (mb, &n);
if (!p)
return out_of_core ();
ae = assuan_send_data (ctx, p, n);
memset (p, 0, n);
xfree (p);
return ae;
}
/* Clear the nonces used to enable the passphrase cache for certain
multi-command command sequences. */
static void
clear_nonce_cache (ctrl_t ctrl)
{
if (ctrl->server_local->last_cache_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_cache_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = NULL;
}
if (ctrl->server_local->last_passwd_nonce)
{
agent_put_cache (ctrl, ctrl->server_local->last_passwd_nonce,
CACHE_MODE_NONCE, NULL, 0);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = NULL;
}
}
/* This function is called by Libassuan whenever the client sends a
reset. It has been registered similar to the other Assuan
commands. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) line;
memset (ctrl->keygrip, 0, 20);
ctrl->have_keygrip = ctrl->have_keygrip1 = 0;
ctrl->digest.valuelen = 0;
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
clear_nonce_cache (ctrl);
- /* Note that a RESET does not clear the ephemeral store becuase
+ /* Note that a RESET does not clear the ephemeral store because
* clients are used to issue a RESET on a connection. */
return 0;
}
/* Replace all '+' by a blank in the string S. */
static void
plus_to_blank (char *s)
{
for (; *s; s++)
{
if (*s == '+')
*s = ' ';
}
}
/* Parse a hex string. Return an Assuan error code or 0 on success and the
length of the parsed string in LEN. */
static int
parse_hexstring (assuan_context_t ctx, const char *string, size_t *len)
{
const char *p;
size_t n;
/* parse the hash value */
for (p=string, n=0; hexdigitp (p); p++, n++)
;
if (*p != ' ' && *p != '\t' && *p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
*len = n;
return 0;
}
/* Parse the keygrip in STRING into the provided buffer BUF. BUF must
provide space for 20 bytes. BUF is not changed if the function
returns an error. */
static int
parse_keygrip (assuan_context_t ctx, const char *string, unsigned char *buf)
{
int rc;
size_t n = 0;
rc = parse_hexstring (ctx, string, &n);
if (rc)
return rc;
n /= 2;
if (n != 20)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of keygrip");
if (hex2bin (string, buf, 20) < 0)
return set_error (GPG_ERR_BUG, "hex2bin");
return 0;
}
/* Parse the TTL from STRING. Leading and trailing spaces are
* skipped. The value is constrained to -1 .. MAXINT. On error 0 is
* returned, else the number of bytes scanned. */
static size_t
parse_ttl (const char *string, int *r_ttl)
{
const char *string_orig = string;
long ttl;
char *pend;
ttl = strtol (string, &pend, 10);
string = pend;
if (string == string_orig || !(spacep (string) || !*string)
|| ttl < -1L || (int)ttl != (long)ttl)
{
*r_ttl = 0;
return 0;
}
while (spacep (string) || *string== '\n')
string++;
*r_ttl = (int)ttl;
return string - string_orig;
}
/* Write an Assuan status line. KEYWORD is the first item on the
* status line. The following arguments are all separated by a space
* in the output. The last argument must be a NULL. Linefeeds and
* carriage returns characters (which are not allowed in an Assuan
* status line) are silently quoted in C-style. */
gpg_error_t
agent_write_status (ctrl_t ctrl, const char *keyword, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
va_end (arg_ptr);
return err;
}
/* This function is similar to print_assuan_status but takes a CTRL
arg instead of an assuan context as first argument. */
gpg_error_t
agent_print_status (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Helper to notify the client about a launched Pinentry. Because
that might disturb some older clients, this is only done if enabled
via an option. Returns an gpg error code. */
gpg_error_t
agent_inq_pinentry_launched (ctrl_t ctrl, unsigned long pid, const char *extra)
{
char line[256];
if (!ctrl || !ctrl->server_local
|| !ctrl->server_local->allow_pinentry_notify)
return 0;
snprintf (line, DIM(line), "PINENTRY_LAUNCHED %lu%s%s",
pid, extra?" ":"", extra? extra:"");
return assuan_inquire (ctrl->server_local->assuan_ctx, line, NULL, NULL, 0);
}
/* An agent progress callback for Libgcrypt. This has been registered
* to be called via the progress dispatcher mechanism from
* gpg-agent.c */
static void
progress_cb (ctrl_t ctrl, const char *what, int printchar,
int current, int total)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
;
else if (printchar == '\n' && what && !strcmp (what, "primegen"))
agent_print_status (ctrl, "PROGRESS", "%.20s X 100 100", what);
else
agent_print_status (ctrl, "PROGRESS", "%.20s %c %d %d",
what, printchar=='\n'?'X':printchar, current, total);
}
/* Helper to print a message while leaving a command. Note that this
* function does not call assuan_set_error; the caller may do this
* prior to calling us. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
/* Not all users of gpg-agent know about the fully canceled
error code; map it back if needed. */
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!ctrl->server_local->allow_fully_canceled)
err = gpg_err_make (gpg_err_source (err), GPG_ERR_CANCELED);
}
/* Most code from common/ does not know the error source, thus
we fix this here. */
if (gpg_err_source (err) == GPG_ERR_SOURCE_UNKNOWN)
err = gpg_err_make (GPG_ERR_SOURCE_DEFAULT, gpg_err_code (err));
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* Take the keyinfo for cards from our local cache. Actually this
* cache could be a global one but then we would need to employ
* reference counting. */
static struct card_key_info_s *
get_keyinfo_on_cards (ctrl_t ctrl)
{
struct card_key_info_s *keyinfo_on_cards = NULL;
if (opt.disable_daemon[DAEMON_SCD])
return NULL;
if (ctrl->server_local->last_card_keyinfo.ki
&& ctrl->server_local->last_card_keyinfo.eventno == eventcounter.card
&& (ctrl->server_local->last_card_keyinfo.maybe_key_change
== eventcounter.maybe_key_change))
{
keyinfo_on_cards = ctrl->server_local->last_card_keyinfo.ki;
}
else if (!agent_card_keyinfo (ctrl, NULL, 0, &keyinfo_on_cards))
{
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
ctrl->server_local->last_card_keyinfo.ki = keyinfo_on_cards;
ctrl->server_local->last_card_keyinfo.eventno = eventcounter.card;
ctrl->server_local->last_card_keyinfo.maybe_key_change
= eventcounter.maybe_key_change;
}
return keyinfo_on_cards;
}
static const char hlp_geteventcounter[] =
"GETEVENTCOUNTER\n"
"\n"
"Return a status line named EVENTCOUNTER with the current values\n"
"of all event counters. The values are decimal numbers in the range\n"
"0 to UINT_MAX and wrapping around to 0. The actual values should\n"
"not be relied upon, they shall only be used to detect a change.\n"
"\n"
"The currently defined counters are:\n"
"\n"
"ANY - Incremented with any change of any of the other counters.\n"
"KEY - Incremented for added or removed private keys.\n"
"CARD - Incremented for changes of the card readers stati.";
static gpg_error_t
cmd_geteventcounter (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
return agent_print_status (ctrl, "EVENTCOUNTER", "%u %u %u",
eventcounter.any,
eventcounter.key,
eventcounter.card);
}
/* This function should be called once for all key removals or
additions. This function is assured not to do any context
switches. */
void
bump_key_eventcounter (void)
{
eventcounter.key++;
eventcounter.any++;
}
/* This function should be called for all card reader status
changes. This function is assured not to do any context
switches. */
void
bump_card_eventcounter (void)
{
eventcounter.card++;
eventcounter.any++;
}
static const char hlp_istrusted[] =
"ISTRUSTED <hexstring_with_fingerprint>\n"
"\n"
"Return OK when we have an entry with this fingerprint in our\n"
"trustlist";
static gpg_error_t
cmd_istrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
/* Parse the fingerprint value. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
rc = agent_istrusted (ctrl, fpr, NULL);
if (!rc || gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
return rc;
else if (rc == -1 || gpg_err_code (rc) == GPG_ERR_EOF )
return gpg_error (GPG_ERR_NOT_TRUSTED);
else
return leave_cmd (ctx, rc);
}
static const char hlp_listtrusted[] =
"LISTTRUSTED\n"
"\n"
"List all entries from the trustlist.";
static gpg_error_t
cmd_listtrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
rc = agent_listtrusted (ctx);
return leave_cmd (ctx, rc);
}
static const char hlp_martrusted[] =
"MARKTRUSTED <hexstring_with_fingerprint> <flag> <display_name>\n"
"\n"
"Store a new key in into the trustlist.";
static gpg_error_t
cmd_marktrusted (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc, n, i;
char *p;
char fpr[41];
int flag;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the fingerprint value */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (!spacep (p) || !(n == 40 || n == 32))
return set_error (GPG_ERR_ASS_PARAMETER, "invalid fingerprint");
i = 0;
if (n==32)
{
strcpy (fpr, "00000000");
i += 8;
}
for (p=line; i < 40; p++, i++)
fpr[i] = *p >= 'a'? (*p & 0xdf): *p;
fpr[i] = 0;
while (spacep (p))
p++;
flag = *p++;
if ( (flag != 'S' && flag != 'P') || !spacep (p) )
return set_error (GPG_ERR_ASS_PARAMETER, "invalid flag - must be P or S");
while (spacep (p))
p++;
rc = agent_marktrusted (ctrl, p, fpr, flag);
return leave_cmd (ctx, rc);
}
static const char hlp_havekey[] =
"HAVEKEY <hexstrings_with_keygrips>\n"
"HAVEKEY --list[=<limit>]\n"
"HAVEKEY --info <hexkeygrip>\n"
"\n"
"Return success if at least one of the secret keys with the given\n"
"keygrips is available. With --list return all available keygrips\n"
"as binary data; with <limit> bail out at this number of keygrips.\n"
"In --info mode check just one keygrip.";
static gpg_error_t
cmd_havekey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char grip[20];
char *p;
int list_mode = 0; /* Less than 0 for no limit. */
int info_mode = 0;
int counter;
char *dirname = NULL;
gnupg_dir_t dir = NULL;
gnupg_dirent_t dir_entry;
char hexgrip[41];
struct card_key_info_s *keyinfo_on_cards, *l;
if (has_option (line, "--info"))
info_mode = 1;
else if (has_option_name (line, "--list"))
{
if ((p = option_value (line, "--list")))
list_mode = atoi (p);
else
list_mode = -1;
}
line = skip_options (line);
if (info_mode)
{
int keytype;
const char *infostring;
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
err = agent_key_info_from_file (ctrl, grip, &keytype, NULL, NULL);
if (err)
goto leave;
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE: infostring = "clear"; break;
case PRIVATE_KEY_PROTECTED: infostring = "protected"; break;
case PRIVATE_KEY_SHADOWED: infostring = "shadowed"; break;
default: infostring = "unknown"; break;
}
err = agent_write_status (ctrl, "KEYFILEINFO", infostring, NULL);
goto leave;
}
if (!list_mode)
{
do
{
err = parse_keygrip (ctx, line, grip);
if (err)
return err;
if (!agent_key_available (ctrl, grip))
return 0; /* Found. */
while (*line && *line != ' ' && *line != '\t')
line++;
while (*line == ' ' || *line == '\t')
line++;
}
while (*line);
/* No leave_cmd() here because errors are expected and would clutter
* the log. */
return gpg_error (GPG_ERR_NO_SECKEY);
}
/* List mode. */
dir = NULL;
dirname = NULL;
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = gnupg_opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
goto leave;
}
counter = 0;
while ((dir_entry = gnupg_readdir (dir)))
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
if (list_mode > 0 && ++counter > list_mode)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
err = assuan_send_data (ctx, grip, 20);
if (err)
goto leave;
}
/* And now the keys from the current cards. If they already got a
* stub, they are listed twice but we don't care. */
keyinfo_on_cards = get_keyinfo_on_cards (ctrl);
for (l = keyinfo_on_cards; l; l = l->next)
{
if ( hex2bin (l->keygrip, grip, 20) < 0 )
continue; /* Bad hex string. */
if (list_mode > 0 && ++counter > list_mode)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave;
}
err = assuan_send_data (ctx, grip, 20);
if (err)
goto leave;
}
err = 0;
leave:
gnupg_closedir (dir);
xfree (dirname);
return leave_cmd (ctx, err);
}
static const char hlp_sigkey[] =
"SIGKEY [--another] <hexstring_with_keygrip>\n"
"SETKEY [--another] <hexstring_with_keygrip>\n"
"\n"
"Set the key used for a sign or decrypt operation.";
static gpg_error_t
cmd_sigkey (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_another;
opt_another = has_option (line, "--another");
line = skip_options (line);
rc = parse_keygrip (ctx, line, opt_another? ctrl->keygrip1 : ctrl->keygrip);
if (rc)
return rc;
if (opt_another)
ctrl->have_keygrip1 = 1;
else
ctrl->have_keygrip = 1;
return 0;
}
static const char hlp_setkeydesc[] =
"SETKEYDESC plus_percent_escaped_string\n"
"\n"
"Set a description to be used for the next PKSIGN, PKDECRYPT, IMPORT_KEY\n"
"or EXPORT_KEY operation if this operation requires a passphrase. If\n"
"this command is not used a default text will be used. Note, that\n"
"this description implicitly selects the label used for the entry\n"
"box; if the string contains the string PIN (which in general will\n"
"not be translated), \"PIN\" is used, otherwise the translation of\n"
"\"passphrase\" is used. The description string should not contain\n"
"blanks unless they are percent or '+' escaped.\n"
"\n"
"The description is only valid for the next PKSIGN, PKDECRYPT,\n"
"IMPORT_KEY, EXPORT_KEY, or DELETE_KEY operation.";
static gpg_error_t
cmd_setkeydesc (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *desc, *p;
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage; we might late use it for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
plus_to_blank (desc);
xfree (ctrl->server_local->keydesc);
if (ctrl->restricted)
{
ctrl->server_local->keydesc = strconcat
((ctrl->restricted == 2
? _("Note: Request from the web browser.")
: _("Note: Request from a remote site.") ), "%0A%0A", desc, NULL);
}
else
ctrl->server_local->keydesc = xtrystrdup (desc);
if (!ctrl->server_local->keydesc)
return out_of_core ();
return 0;
}
static const char hlp_sethash[] =
"SETHASH (--hash=<name>)|(<algonumber>) <hexstring>]\n"
"SETHASH [--pss] --inquire\n"
"\n"
"The client can use this command to tell the server about the data\n"
"(which usually is a hash) to be signed. The option --inquire is\n"
"used to ask back for to-be-signed data in case of PureEdDSA or\n"
"with --pss for pre-formatted rsaPSS.";
static gpg_error_t
cmd_sethash (assuan_context_t ctx, char *line)
{
gpg_error_t err;
size_t n;
char *p;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *buf;
char *endp;
int algo;
int opt_inquire, opt_pss;
/* Parse the alternative hash options which may be used instead of
the algo number. */
if (has_option_name (line, "--hash"))
{
if (has_option (line, "--hash=sha1"))
algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=rmd160"))
algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=md5"))
algo = GCRY_MD_MD5;
else if (has_option (line, "--hash=tls-md5sha1"))
algo = MD_USER_TLS_MD5SHA1;
else if (has_option (line, "--hash=none"))
algo = 0;
else
{
err = set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
goto leave;
}
}
else
algo = 0;
opt_pss = has_option (line, "--pss");
opt_inquire = has_option (line, "--inquire");
line = skip_options (line);
if (!algo && !opt_inquire)
{
/* No hash option has been given: require an algo number instead */
algo = (int)strtoul (line, &endp, 10);
for (line = endp; *line == ' ' || *line == '\t'; line++)
;
if (!algo || gcry_md_test_algo (algo))
{
err = set_error (GPG_ERR_UNSUPPORTED_ALGORITHM, NULL);
goto leave;
}
}
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
ctrl->digest.algo = algo;
ctrl->digest.raw_value = 0;
ctrl->digest.is_pss = opt_pss;
if (opt_inquire)
{
/* We limit the to-be-signed data to some reasonable size which
* may eventually allow us to pass that even to smartcards. */
size_t maxlen = 2048;
if (algo)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and an algo are specified");
goto leave;
}
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!err)
err = assuan_inquire (ctx, "TBSDATA", &buf, &n, maxlen);
if (err)
goto leave;
ctrl->digest.data = buf;
ctrl->digest.valuelen = n;
}
else
{
/* Parse the hash value. */
n = 0;
err = parse_hexstring (ctx, line, &n);
if (err)
goto leave;
n /= 2;
if (algo == MD_USER_TLS_MD5SHA1 && n == 36)
;
else if (n != 16 && n != 20 && n != 24
&& n != 28 && n != 32 && n != 48 && n != 64)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "unsupported length of hash");
goto leave;
}
if (n > MAX_DIGEST_LEN)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "hash value to long");
goto leave;
}
buf = ctrl->digest.value;
ctrl->digest.valuelen = n;
for (p=line, n=0; n < ctrl->digest.valuelen; p += 2, n++)
buf[n] = xtoi_2 (p);
for (; n < ctrl->digest.valuelen; n++)
buf[n] = 0;
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_pksign[] =
"PKSIGN [<options>] [<cache_nonce>]\n"
"\n"
"Perform the actual sign operation. Neither input nor output are\n"
"sensitive to eavesdropping.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
gpg_error_t err;
cache_mode_t cache_mode = CACHE_MODE_NORMAL;
ctrl_t ctrl = assuan_get_pointer (ctx);
membuf_t outbuf;
char *cache_nonce = NULL;
char *p;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
if (opt.ignore_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
else if (!ctrl->server_local->use_cache_for_signing)
cache_mode = CACHE_MODE_IGNORE;
init_membuf (&outbuf, 512);
err = agent_pksign (ctrl, cache_nonce, ctrl->server_local->keydesc,
&outbuf, cache_mode);
if (err)
clear_outbuf (&outbuf);
else
err = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT [--kem[=<kemid>] [<options>]\n"
"\n"
"Perform the actual decrypt operation. Input is not\n"
"sensitive to eavesdropping.\n"
"If the --kem option is used, decryption is done with the KEM,\n"
"inquiring upper-layer option, when needed. KEMID can be\n"
"specified with --kem option; Valid value is: PQC-PGP, PGP, or CMS.\n"
"Default is PQC-PGP.";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
int rc;
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *value;
size_t valuelen;
membuf_t outbuf;
int padding = -1;
unsigned char *option = NULL;
size_t optionlen = 0;
const char *p;
int kemid = -1;
p = has_option_name (line, "--kem");
if (p)
{
kemid = KEM_PQC_PGP;
if (*p == '=')
{
p++;
if (!strcmp (p, "PQC-PGP"))
kemid = KEM_PQC_PGP;
else if (!strcmp (p, "PGP"))
kemid = KEM_PGP;
else if (!strcmp (p, "CMS"))
kemid = KEM_CMS;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid KEM algorithm");
}
}
/* First inquire the data to decrypt */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_CIPHERTEXT);
if (!rc)
rc = assuan_inquire (ctx, "CIPHERTEXT",
&value, &valuelen, MAXLEN_CIPHERTEXT);
if (!rc && kemid > KEM_PQC_PGP)
rc = assuan_inquire (ctx, "OPTION",
&option, &optionlen, MAXLEN_CIPHERTEXT);
if (rc)
return rc;
init_membuf (&outbuf, 512);
if (kemid < 0)
rc = agent_pkdecrypt (ctrl, ctrl->server_local->keydesc,
value, valuelen, &outbuf, &padding);
else
{
rc = agent_kem_decrypt (ctrl, ctrl->server_local->keydesc, kemid,
value, valuelen, option, optionlen, &outbuf);
xfree (option);
}
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
{
if (padding != -1)
rc = print_assuan_status (ctx, "PADDING", "%d", padding);
else
rc = 0;
if (!rc)
rc = write_and_clear_outbuf (ctx, &outbuf);
}
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, rc);
}
static const char hlp_genkey[] =
"GENKEY [--no-protection] [--preset] [--timestamp=<isodate>]\n"
" [--inq-passwd] [--passwd-nonce=<s>] [<cache_nonce>]\n"
"\n"
"Generate a new key, store the secret part and return the public\n"
"part. Here is an example transaction:\n"
"\n"
" C: GENKEY\n"
" S: INQUIRE KEYPARAM\n"
" C: D (genkey (rsa (nbits 3072)))\n"
" C: END\n"
" S: D (public-key\n"
" S: D (rsa (n 326487324683264) (e 10001)))\n"
" S: OK key created\n"
"\n"
"If the --preset option is used the passphrase for the generated\n"
"key will be added to the cache. If --inq-passwd is used an inquire\n"
"with the keyword NEWPASSWD is used to request the passphrase for the\n"
"new key. If a --passwd-nonce is used, the corresponding cached\n"
"passphrase is used to protect the new key. If --timestamp is given\n"
"its value is recorded as the key's creation time; the value is\n"
"expected in ISO format (e.g. \"20030316T120000\").";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *value = NULL;
size_t valuelen;
unsigned char *newpasswd = NULL;
membuf_t outbuf;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int opt_inq_passwd;
size_t n;
char *p, *pend;
const char *s;
time_t opt_timestamp;
int c;
unsigned int flags = 0;
init_membuf (&outbuf, 512);
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--no-protection"))
flags |= GENKEY_FLAG_NO_PROTECTION;
if (has_option (line, "--preset"))
flags |= GENKEY_FLAG_PRESET;
opt_inq_passwd = has_option (line, "--inq-passwd");
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
{
rc = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
goto leave;
}
opt_timestamp = isotime2epoch (s+1);
if (opt_timestamp < 1)
{
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
goto leave;
}
}
else
opt_timestamp = 0;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
/* First inquire the parameters */
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u", MAXLEN_KEYPARAM);
if (rc)
goto leave;
rc = assuan_inquire (ctx, "KEYPARAM", &value, &valuelen, MAXLEN_KEYPARAM);
if (rc)
goto leave;
/* If requested, ask for the password to be used for the key. If
this is not used the regular Pinentry mechanism is used. */
if (opt_inq_passwd && !(flags & GENKEY_FLAG_NO_PROTECTION))
{
/* (N is used as a dummy) */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "NEWPASSWD", &newpasswd, &n, 256);
assuan_end_confidential (ctx);
if (rc)
goto leave;
if (!*newpasswd)
{
/* Empty password given - switch to no-protection mode. */
xfree (newpasswd);
newpasswd = NULL;
flags |= GENKEY_FLAG_NO_PROTECTION;
}
}
else if (passwd_nonce)
newpasswd = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
rc = agent_genkey (ctrl, flags, cache_nonce, opt_timestamp,
(char*)value, valuelen,
newpasswd, &outbuf);
leave:
if (newpasswd)
{
/* Assuan_inquire does not allow us to read into secure memory
thus we need to wipe it ourself. */
wipememory (newpasswd, strlen (newpasswd));
xfree (newpasswd);
}
xfree (value);
if (rc)
clear_outbuf (&outbuf);
else
rc = write_and_clear_outbuf (ctx, &outbuf);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, rc);
}
static const char hlp_keyattr[] =
"KEYATTR [--delete] <hexstring_with_keygrip> <ATTRNAME> [<VALUE>]\n"
"\n"
"For the secret key, show the attribute of ATTRNAME. With VALUE,\n"
"put the value to the attribute. Use --delete option to delete.";
static gpg_error_t
cmd_keyattr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
const char *argv[3];
int argc;
unsigned char grip[20];
int opt_delete;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_delete = has_option (line, "--delete");
line = skip_options (line);
argc = split_fields (line, argv, DIM (argv));
if (argc < 2)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
if (!strcmp (argv[1], "Key:") /* It allows only access to attribute */
/* Make sure ATTRNAME ends with colon. */
|| argv[1][strlen (argv[1]) - 1] != ':')
{
err = gpg_error (GPG_ERR_INV_PARAMETER);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (!err)
{
gcry_sexp_t s_key = NULL;
nvc_t keymeta = NULL;
const char *p;
err = agent_raw_key_from_file (ctrl, grip, &s_key, &keymeta);
if (err)
goto leave;
if (argc == 2)
{
nve_t e = NULL;
if (keymeta)
e = nvc_lookup (keymeta, argv[1]);
if (opt_delete)
{
if (e)
{
nvc_delete (keymeta, e);
goto key_attr_write;
}
}
else if (e)
{
p = nve_value (e);
if (p)
err = assuan_send_data (ctx, p, strlen (p));
}
}
else if (argc == 3)
{
if (!keymeta)
keymeta = nvc_new_private_key ();
err = nvc_set (keymeta, argv[1], argv[2]);
key_attr_write:
if (!err)
err = nvc_set_private_key (keymeta, s_key);
if (!err)
err = agent_update_private_key (ctrl, grip, keymeta);
}
nvc_release (keymeta);
gcry_sexp_release (s_key);
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_readkey[] =
"READKEY [--no-data] [--format=ssh] <hexstring_with_keygrip>\n"
" --card <keyid>\n"
"\n"
"Return the public key for the given keygrip or keyid.\n"
"With --card, private key file with card information will be created.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char grip[20];
gcry_sexp_t s_pkey = NULL;
unsigned char *pkbuf = NULL;
size_t pkbuflen;
int opt_card, opt_no_data, opt_format_ssh;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_no_data = has_option (line, "--no-data");
opt_card = has_option (line, "--card");
opt_format_ssh = has_option (line, "--format=ssh");
line = skip_options (line);
if (opt_card)
{
char *serialno = NULL;
char *keyidbuf = NULL;
const char *keyid = line;
rc = agent_card_getattr (ctrl, "SERIALNO", &serialno, NULL);
if (rc)
{
log_error (_("error getting serial number of card: %s\n"),
gpg_strerror (rc));
goto leave;
}
/* Hack to create the shadow key for the OpenPGP standard keys. */
if ((!strcmp (keyid, "$SIGNKEYID") || !strcmp (keyid, "$ENCRKEYID"))
&& !agent_card_getattr (ctrl, keyid, &keyidbuf, NULL))
keyid = keyidbuf;
rc = agent_card_readkey (ctrl, keyid, &pkbuf, NULL);
if (rc)
goto leave;
pkbuflen = gcry_sexp_canon_len (pkbuf, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_pkey, NULL, (char*)pkbuf, pkbuflen);
if (rc)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
rc = gcry_pk_testkey (s_pkey);
if (rc == 0)
rc = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
if (!ctrl->ephemeral_mode && agent_key_available (ctrl, grip))
{
/* (Shadow)-key is not available in our key storage. */
char *dispserialno;
char hexgrip[40+1];
bin2hex (grip, 20, hexgrip);
agent_card_getattr (ctrl, "$DISPSERIALNO", &dispserialno, hexgrip);
rc = agent_write_shadow_key (ctrl, grip, serialno, keyid, pkbuf, 0,
dispserialno);
xfree (dispserialno);
if (rc)
goto leave;
}
xfree (serialno);
xfree (keyidbuf);
}
else
{
rc = parse_keygrip (ctx, line, grip);
if (rc)
goto leave;
rc = agent_public_key_from_file (ctrl, grip, &s_pkey);
if (rc)
goto leave;
else
{
if (opt_format_ssh)
{
estream_t stream = NULL;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
rc = gpg_error_from_syserror ();
goto leave;
}
rc = ssh_public_key_in_base64 (s_pkey, stream, "(none)");
if (rc)
{
es_fclose (stream);
goto leave;
}
rc = es_fclose_snatch (stream, (void **)&pkbuf, &pkbuflen);
if (rc)
goto leave;
}
else
{
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
log_assert (pkbuflen);
pkbuf = xtrymalloc (pkbuflen);
if (!pkbuf)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pkbuflen = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON,
pkbuf, pkbuflen);
}
}
}
rc = opt_no_data? 0 : assuan_send_data (ctx, pkbuf, pkbuflen);
leave:
xfree (pkbuf);
gcry_sexp_release (s_pkey);
return leave_cmd (ctx, rc);
}
static const char hlp_keyinfo[] =
"KEYINFO [--[ssh-]list] [--data] [--ssh-fpr[=algo]] [--with-ssh]\n"
" [--need-attr=ATTRNAME] <keygrip>\n"
"\n"
"Return information about the key specified by the KEYGRIP. If the\n"
"key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
"--list is given the keygrip is ignored and information about all\n"
"available keys are returned. If --ssh-list is given information\n"
"about all keys listed in the sshcontrol are returned. With --with-ssh\n"
"information from sshcontrol is always added to the info. If --need-attr\n"
"is used the key is only listed if the value of the given attribute name\n"
"(e.g. \"Use-for-ssh\") is true. Unless --data is given, the information\n"
"is returned as a status line using the format:\n"
"\n"
" KEYINFO <keygrip> <type> <serialno> <idstr> <cached> <protection> <fpr>\n"
"\n"
"KEYGRIP is the keygrip.\n"
"\n"
"TYPE is describes the type of the key:\n"
" 'D' - Regular key stored on disk,\n"
" 'T' - Key is stored on a smartcard (token),\n"
" 'X' - Unknown type,\n"
" '-' - Key is missing.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"IDSTR is the IDSTR used to distinguish keys on a smartcard. If it\n"
" is not known a dash is used instead.\n"
"\n"
"CACHED is 1 if the passphrase for the key was found in the key cache.\n"
" If not, a '-' is used instead.\n"
"\n"
"PROTECTION describes the key protection type:\n"
" 'P' - The key is protected with a passphrase,\n"
" 'C' - The key is not protected,\n"
" '-' - Unknown protection.\n"
"\n"
"FPR returns the formatted ssh-style fingerprint of the key. It is only\n"
" printed if the option --ssh-fpr has been used. If ALGO is not given\n"
" to that option the default ssh fingerprint algo is used. Without the\n"
" option a '-' is printed.\n"
"\n"
"TTL is the TTL in seconds for that key or '-' if n/a.\n"
"\n"
"FLAGS is a word consisting of one-letter flags:\n"
" 'D' - The key has been disabled,\n"
" 'S' - The key is listed in sshcontrol (requires --with-ssh),\n"
" 'c' - Use of the key needs to be confirmed,\n"
" 'A' - The key is available on card,\n"
" '-' - No flags given.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
do_one_keyinfo (ctrl_t ctrl, const unsigned char *grip, assuan_context_t ctx,
int data, int with_ssh_fpr, int in_ssh,
int ttl, int disabled, int confirm, int on_card,
const char *need_attr, int list_mode)
{
gpg_error_t err;
char hexgrip[40+1];
char *fpr = NULL;
int keytype;
unsigned char *shadow_info = NULL;
unsigned char *shadow_info_type = NULL;
char *serialno = NULL;
char *idstr = NULL;
const char *keytypestr;
const char *cached;
const char *protectionstr;
char *pw;
int missing_key = 0;
char ttlbuf[20];
char flagsbuf[5];
err = agent_key_info_from_file (ctrl, grip, &keytype, &shadow_info,
&shadow_info_type);
if (err)
{
if (in_ssh && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
missing_key = 1;
else
goto leave;
}
if (need_attr || (ctrl->restricted && list_mode))
{
gcry_sexp_t s_key = NULL;
nvc_t keymeta = NULL;
int istrue, has_rl;
if (missing_key)
goto leave; /* No attribute available. */
err = agent_raw_key_from_file (ctrl, grip, &s_key, &keymeta);
if (!keymeta)
istrue = 0;
else
{
has_rl = 0;
if (ctrl->restricted && list_mode
&& !(has_rl = nvc_get_boolean (keymeta, "Remote-list:")))
istrue = 0;
else if (need_attr)
istrue = nvc_get_boolean (keymeta, need_attr);
else
istrue = has_rl;
nvc_release (keymeta);
}
gcry_sexp_release (s_key);
if (!istrue)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
}
/* Reformat the grip so that we use uppercase as good style. */
bin2hex (grip, 20, hexgrip);
if (ttl > 0)
snprintf (ttlbuf, sizeof ttlbuf, "%d", ttl);
else
strcpy (ttlbuf, "-");
*flagsbuf = 0;
if (disabled)
strcat (flagsbuf, "D");
if (in_ssh)
strcat (flagsbuf, "S");
if (confirm)
strcat (flagsbuf, "c");
if (on_card)
strcat (flagsbuf, "A");
if (!*flagsbuf)
strcpy (flagsbuf, "-");
if (missing_key)
{
protectionstr = "-"; keytypestr = "-";
}
else
{
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
protectionstr = "C"; keytypestr = "D";
break;
case PRIVATE_KEY_PROTECTED: protectionstr = "P"; keytypestr = "D";
break;
case PRIVATE_KEY_SHADOWED: protectionstr = "-"; keytypestr = "T";
break;
default: protectionstr = "-"; keytypestr = "X";
break;
}
}
/* Compute the ssh fingerprint if requested. */
if (with_ssh_fpr)
{
gcry_sexp_t key;
if (!agent_raw_key_from_file (ctrl, grip, &key, NULL))
{
ssh_get_fingerprint_string (key, with_ssh_fpr, &fpr);
gcry_sexp_release (key);
}
}
/* Here we have a little race by doing the cache check separately
from the retrieval function. Given that the cache flag is only a
hint, it should not really matter. */
pw = agent_get_cache (ctrl, hexgrip, CACHE_MODE_NORMAL);
cached = pw ? "1" : "-";
xfree (pw);
if (shadow_info)
{
if (strcmp (shadow_info_type, "t1-v1") == 0)
{
err = parse_shadow_info (shadow_info, &serialno, &idstr, NULL);
if (err)
goto leave;
}
else if (strcmp (shadow_info_type, "tpm2-v1") == 0)
{
serialno = xstrdup("TPM-Protected");
idstr = NULL;
}
else
{
log_error ("unrecognised shadow key type %s\n", shadow_info_type);
err = GPG_ERR_BAD_KEY;
goto leave;
}
}
if (!data)
err = agent_write_status (ctrl, "KEYINFO",
hexgrip,
keytypestr,
serialno? serialno : "-",
idstr? idstr : "-",
cached,
protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf,
NULL);
else
{
char *string;
string = xtryasprintf ("%s %s %s %s %s %s %s %s %s\n",
hexgrip, keytypestr,
serialno? serialno : "-",
idstr? idstr : "-", cached, protectionstr,
fpr? fpr : "-",
ttlbuf,
flagsbuf);
if (!string)
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, string, strlen(string));
xfree (string);
}
leave:
xfree (fpr);
xfree (shadow_info_type);
xfree (shadow_info);
xfree (serialno);
xfree (idstr);
return err;
}
/* Entry into the command KEYINFO. This function handles the
* command option processing. For details see hlp_keyinfo above. */
static gpg_error_t
cmd_keyinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int err;
unsigned char grip[20];
gnupg_dir_t dir = NULL;
int list_mode;
int opt_data, opt_ssh_fpr, opt_with_ssh;
ssh_control_file_t cf = NULL;
char hexgrip[41];
int disabled, ttl, confirm, is_ssh;
struct card_key_info_s *keyinfo_on_cards;
struct card_key_info_s *l;
int on_card;
char *need_attr = NULL;
size_t n;
if (has_option (line, "--ssh-list"))
list_mode = 2;
else
list_mode = has_option (line, "--list");
opt_data = has_option (line, "--data");
if (has_option_name (line, "--ssh-fpr"))
{
if (has_option (line, "--ssh-fpr=md5"))
opt_ssh_fpr = GCRY_MD_MD5;
else if (has_option (line, "--ssh-fpr=sha1"))
opt_ssh_fpr = GCRY_MD_SHA1;
else if (has_option (line, "--ssh-fpr=sha256"))
opt_ssh_fpr = GCRY_MD_SHA256;
else
opt_ssh_fpr = opt.ssh_fingerprint_digest;
}
else
opt_ssh_fpr = 0;
opt_with_ssh = has_option (line, "--with-ssh");
err = get_option_value (line, "--need-attr", &need_attr);
if (err)
goto leave;
if (need_attr && (n=strlen (need_attr)) && need_attr[n-1] != ':')
{
/* We need to append a colon. */
char *tmp = strconcat (need_attr, ":", NULL);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (need_attr);
need_attr = tmp;
}
line = skip_options (line);
if (opt_with_ssh || list_mode == 2)
cf = ssh_open_control_file ();
keyinfo_on_cards = get_keyinfo_on_cards (ctrl);
if (list_mode == 2)
{
if (cf)
{
while (!ssh_read_control_file (cf, hexgrip,
&disabled, &ttl, &confirm))
{
if (hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, 1,
ttl, disabled, confirm, on_card, need_attr,
list_mode);
if ((need_attr || ctrl->restricted)
&& gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
goto leave;
}
}
err = 0;
}
else if (list_mode)
{
char *dirname;
gnupg_dirent_t dir_entry;
dirname = make_filename_try (gnupg_homedir (),
GNUPG_PRIVATE_KEYS_DIR, NULL);
if (!dirname)
{
err = gpg_error_from_syserror ();
goto leave;
}
dir = gnupg_opendir (dirname);
if (!dir)
{
err = gpg_error_from_syserror ();
xfree (dirname);
goto leave;
}
xfree (dirname);
while ( (dir_entry = gnupg_readdir (dir)) )
{
if (strlen (dir_entry->d_name) != 44
|| strcmp (dir_entry->d_name + 40, ".key"))
continue;
strncpy (hexgrip, dir_entry->d_name, 40);
hexgrip[40] = 0;
if ( hex2bin (hexgrip, grip, 20) < 0 )
continue; /* Bad hex string. */
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, hexgrip,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, hexgrip, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card, need_attr,
list_mode);
if ((need_attr || ctrl->restricted)
&& gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
goto leave;
}
err = 0;
}
else
{
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
disabled = ttl = confirm = is_ssh = 0;
if (opt_with_ssh)
{
err = ssh_search_control_file (cf, line,
&disabled, &ttl, &confirm);
if (!err)
is_ssh = 1;
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
goto leave;
}
on_card = 0;
for (l = keyinfo_on_cards; l; l = l->next)
if (!memcmp (l->keygrip, line, 40))
on_card = 1;
err = do_one_keyinfo (ctrl, grip, ctx, opt_data, opt_ssh_fpr, is_ssh,
ttl, disabled, confirm, on_card, need_attr, 0);
}
leave:
xfree (need_attr);
ssh_close_control_file (cf);
gnupg_closedir (dir);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
leave_cmd (ctx, err);
return err;
}
/* Helper for cmd_get_passphrase. */
static int
send_back_passphrase (assuan_context_t ctx, int via_data, const char *pw)
{
size_t n;
int rc;
assuan_begin_confidential (ctx);
n = strlen (pw);
if (via_data)
rc = assuan_send_data (ctx, pw, n);
else
{
char *p = xtrymalloc_secure (n*2+1);
if (!p)
rc = gpg_error_from_syserror ();
else
{
bin2hex (pw, n, p);
rc = assuan_set_okay_line (ctx, p);
xfree (p);
}
}
assuan_end_confidential (ctx);
return rc;
}
/* Callback function to compare the first entered PIN with the one
currently being entered. */
static gpg_error_t
reenter_passphrase_cmp_cb (struct pin_entry_info_s *pi)
{
const char *pin1 = pi->check_cb_arg;
if (!strcmp (pin1, pi->pin))
return 0; /* okay */
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
static const char hlp_get_passphrase[] =
"GET_PASSPHRASE [--data] [--check] [--no-ask] [--repeat[=N]]\n"
" [--qualitybar] [--newsymkey] <cache_id>\n"
" [<error_message> <prompt> <description>]\n"
"\n"
"This function is usually used to ask for a passphrase to be used\n"
"for conventional encryption, but may also be used by programs which\n"
"need specal handling of passphrases. This command uses a syntax\n"
"which helps clients to use the agent with minimum effort. The\n"
"agent either returns with an error or with a OK followed by the hex\n"
"encoded passphrase. Note that the length of the strings is\n"
"implicitly limited by the maximum length of a command.\n"
"\n"
"If the option \"--data\" is used the passphrase is returned by usual\n"
"data lines and not on the okay line.\n"
"\n"
"If the option \"--check\" is used the passphrase constraints checks as\n"
"implemented by gpg-agent are applied. A check is not done if the\n"
"passphrase has been found in the cache.\n"
"\n"
"If the option \"--no-ask\" is used and the passphrase is not in the\n"
"cache the user will not be asked to enter a passphrase but the error\n"
"code GPG_ERR_NO_DATA is returned. \n"
"\n"
"If the option\"--newsymkey\" is used the agent asks for a new passphrase\n"
"to be used in symmetric-only encryption. This must not be empty.\n"
"\n"
"If the option \"--qualitybar\" is used a visual indication of the\n"
"entered passphrase quality is shown. (Unless no minimum passphrase\n"
"length has been configured.)";
static gpg_error_t
cmd_get_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *pw;
char *response = NULL;
char *response2 = NULL;
char *cacheid = NULL; /* May point into LINE. */
char *desc = NULL; /* Ditto */
char *prompt = NULL; /* Ditto */
char *errtext = NULL; /* Ditto */
const char *desc2 = _("Please re-enter this passphrase");
char *p;
int opt_data, opt_check, opt_no_ask, opt_qualbar, opt_newsymkey;
int opt_repeat = 0;
char *entry_errtext = NULL;
struct pin_entry_info_s *pi = NULL;
struct pin_entry_info_s *pi2 = NULL;
int is_generated;
opt_data = has_option (line, "--data");
opt_check = has_option (line, "--check");
opt_no_ask = has_option (line, "--no-ask");
if (has_option_name (line, "--repeat"))
{
p = option_value (line, "--repeat");
if (p)
opt_repeat = atoi (p);
else
opt_repeat = 1;
}
opt_qualbar = has_option (line, "--qualitybar");
opt_newsymkey = has_option (line, "--newsymkey");
line = skip_options (line);
cacheid = line;
p = strchr (cacheid, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
errtext = p;
p = strchr (errtext, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
prompt = p;
p = strchr (prompt, ' ');
if (p)
{
*p++ = 0;
while (*p == ' ')
p++;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* Ignore trailing garbage. */
}
}
}
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
if (!desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
/* The only limitation in restricted mode is that we don't consider
* the cache. */
if (ctrl->restricted || !strcmp (cacheid, "X"))
cacheid = NULL;
if (!strcmp (errtext, "X"))
errtext = NULL;
if (!strcmp (prompt, "X"))
prompt = NULL;
if (!strcmp (desc, "X"))
desc = NULL;
pw = cacheid ? agent_get_cache (ctrl, cacheid, CACHE_MODE_USER) : NULL;
if (pw)
{
rc = send_back_passphrase (ctx, opt_data, pw);
xfree (pw);
goto leave;
}
else if (opt_no_ask)
{
rc = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
/* Note, that we only need to replace the + characters and should
* leave the other escaping in place because the escaped string is
* send verbatim to the pinentry which does the unescaping (but not
* the + replacing) */
if (errtext)
plus_to_blank (errtext);
if (prompt)
plus_to_blank (prompt);
if (desc)
plus_to_blank (desc);
/* If opt_repeat is 2 or higher we can't use our pin_entry_info_s
* based method but fallback to the old simple method. It is
* anyway questionable whether this extra repeat count makes any
* real sense. */
if (opt_newsymkey && opt_repeat < 2)
{
/* We do not want to break any existing usage of this command
* and thus we introduced the option --newsymkey to make this
* command more useful to query the passphrase for symmetric
* encryption. */
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pi2 = gcry_calloc_secure (1, sizeof (*pi2) + MAX_PASSPHRASE_LEN + 1);
if (!pi2)
{
rc = gpg_error_from_syserror ();
goto leave;
}
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->max_tries = 3;
pi->with_qualitybar = opt_qualbar;
pi->with_repeat = opt_repeat;
pi->constraints_flags = (CHECK_CONSTRAINTS_NOT_EMPTY
| CHECK_CONSTRAINTS_NEW_SYMKEY);
pi2->max_length = MAX_PASSPHRASE_LEN + 1;
pi2->max_tries = 3;
pi2->check_cb = reenter_passphrase_cmp_cb;
pi2->check_cb_arg = pi->pin;
for (;;) /* (degenerated for-loop) */
{
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response,
desc,
prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER,
pi);
if (rc)
goto leave;
xfree (entry_errtext);
entry_errtext = NULL;
is_generated = !!(pi->status & PINENTRY_STATUS_PASSWORD_GENERATED);
/* We don't allow an empty passphrase in this mode. */
if (!is_generated
&& check_passphrase_constraints (ctrl, pi->pin,
pi->constraints_flags,
&entry_errtext))
{
pi->failed_tries = 0;
pi2->failed_tries = 0;
continue;
}
if (*pi->pin && !pi->repeat_okay
&& ctrl->pinentry_mode != PINENTRY_MODE_LOOPBACK
&& opt_repeat)
{
/* The passphrase is empty and the pinentry did not
* already run the repetition check, do it here. This
* is only called when using an old and simple pinentry.
* It is neither called in loopback mode because the
* caller does any passphrase repetition by herself nor if
* no repetition was requested. */
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response,
L_("Please re-enter this passphrase"),
prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER,
pi2);
if (gpg_err_code (rc) == GPG_ERR_BAD_PASSPHRASE)
{ /* The re-entered passphrase one did not match and
* the user did not hit cancel. */
entry_errtext = xtrystrdup (L_("does not match - try again"));
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
goto leave;
}
continue;
}
}
break;
}
if (!rc && *pi->pin)
{
/* Return the passphrase. */
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, pi->pin, 0);
rc = send_back_passphrase (ctx, opt_data, pi->pin);
}
}
else
{
next_try:
xfree (response);
response = NULL;
rc = agent_get_passphrase (ctrl, &response, desc, prompt,
entry_errtext? entry_errtext:errtext,
opt_qualbar, cacheid, CACHE_MODE_USER, NULL);
xfree (entry_errtext);
entry_errtext = NULL;
is_generated = 0;
if (!rc)
{
int i;
if (opt_check
&& !is_generated
&& check_passphrase_constraints
(ctrl, response,
(opt_newsymkey? CHECK_CONSTRAINTS_NEW_SYMKEY:0),
&entry_errtext))
{
goto next_try;
}
for (i = 0; i < opt_repeat; i++)
{
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK)
break;
xfree (response2);
response2 = NULL;
rc = agent_get_passphrase (ctrl, &response2, desc2, prompt,
errtext, 0,
cacheid, CACHE_MODE_USER, NULL);
if (rc)
break;
if (strcmp (response2, response))
{
entry_errtext = try_percent_escape
(_("does not match - try again"), NULL);
if (!entry_errtext)
{
rc = gpg_error_from_syserror ();
break;
}
goto next_try;
}
}
if (!rc)
{
if (cacheid)
agent_put_cache (ctrl, cacheid, CACHE_MODE_USER, response, 0);
rc = send_back_passphrase (ctx, opt_data, response);
}
}
}
leave:
xfree (response);
xfree (response2);
xfree (entry_errtext);
xfree (pi2);
xfree (pi);
return leave_cmd (ctx, rc);
}
static const char hlp_clear_passphrase[] =
"CLEAR_PASSPHRASE [--mode=normal] <cache_id>\n"
"\n"
"may be used to invalidate the cache entry for a passphrase. The\n"
"function returns with OK even when there is no cached passphrase.\n"
"The --mode=normal option is used to clear an entry for a cacheid\n"
"added by the agent. The --mode=ssh option is used for a cacheid\n"
"added for ssh.\n";
static gpg_error_t
cmd_clear_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *cacheid = NULL;
char *p;
cache_mode_t cache_mode = CACHE_MODE_USER;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (has_option (line, "--mode=normal"))
cache_mode = CACHE_MODE_NORMAL;
else if (has_option (line, "--mode=ssh"))
cache_mode = CACHE_MODE_SSH;
line = skip_options (line);
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
cacheid = p;
p = strchr (cacheid, ' ');
if (p)
*p = 0; /* ignore garbage */
if (!*cacheid || strlen (cacheid) > 50)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid length of cacheID");
agent_put_cache (ctrl, cacheid, cache_mode, NULL, 0);
agent_clear_passphrase (ctrl, cacheid, cache_mode);
return 0;
}
static const char hlp_get_confirmation[] =
"GET_CONFIRMATION <description>\n"
"\n"
"This command may be used to ask for a simple confirmation.\n"
"DESCRIPTION is displayed along with a Okay and Cancel button. This\n"
"command uses a syntax which helps clients to use the agent with\n"
"minimum effort. The agent either returns with an error or with a\n"
"OK. Note, that the length of DESCRIPTION is implicitly limited by\n"
"the maximum length of a command. DESCRIPTION should not contain\n"
"any spaces, those must be encoded either percent escaped or simply\n"
"as '+'.";
static gpg_error_t
cmd_get_confirmation (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *desc = NULL;
char *p;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
/* parse the stuff */
for (p=line; *p == ' '; p++)
;
desc = p;
p = strchr (desc, ' ');
if (p)
*p = 0; /* We ignore any garbage -may be later used for other args. */
if (!*desc)
return set_error (GPG_ERR_ASS_PARAMETER, "no description given");
if (!strcmp (desc, "X"))
desc = NULL;
/* Note, that we only need to replace the + characters and should
leave the other escaping in place because the escaped string is
send verbatim to the pinentry which does the unescaping (but not
the + replacing) */
if (desc)
plus_to_blank (desc);
rc = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
return leave_cmd (ctx, rc);
}
static const char hlp_learn[] =
"LEARN [--send] [--sendinfo] [--force]\n"
"\n"
"Learn something about the currently inserted smartcard. With\n"
"--sendinfo information about the card is returned; with --send\n"
"the available certificates are returned as D lines; with --force\n"
"private key storage will be updated by the result.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int send, sendinfo, force;
send = has_option (line, "--send");
sendinfo = send? 1 : has_option (line, "--sendinfo");
force = has_option (line, "--force");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = agent_handle_learn (ctrl, send, sendinfo? ctx : NULL, force);
return leave_cmd (ctx, err);
}
static const char hlp_passwd[] =
"PASSWD [--cache-nonce=<c>] [--passwd-nonce=<s>] [--preset]\n"
" [--verify] <hexkeygrip>\n"
"\n"
"Change the passphrase/PIN for the key identified by keygrip in LINE. If\n"
"--preset is used then the new passphrase will be added to the cache.\n"
"If --verify is used the command asks for the passphrase and verifies\n"
"that the passphrase valid.\n";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int c;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *shadow_info = NULL;
char *passphrase = NULL;
char *pend;
int opt_preset, opt_verify;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
opt_preset = has_option (line, "--preset");
cache_nonce = option_value (line, "--cache-nonce");
opt_verify = has_option (line, "--verify");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
passwd_nonce = option_value (line, "--passwd-nonce");
if (passwd_nonce)
{
for (pend = passwd_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
passwd_nonce = xtrystrdup (passwd_nonce);
*pend = c;
if (!passwd_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
ctrl->in_passwd++;
err = agent_key_from_file (ctrl,
opt_verify? NULL : cache_nonce,
ctrl->server_local->keydesc,
grip, &shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, &passphrase, NULL);
if (err)
;
else if (shadow_info)
{
log_error ("changing a smartcard PIN is not yet supported\n");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
else if (opt_verify)
{
/* All done. */
if (passphrase)
{
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
else
{
char *newpass = NULL;
if (passwd_nonce)
newpass = agent_get_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE);
err = agent_protect_and_store (ctrl, s_skey, &newpass);
if (!err && passphrase)
{
/* A passphrase existed on the old key and the change was
successful. Return a nonce for that old passphrase to
let the caller try to unprotect the other subkeys with
the same key. */
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
if (newpass)
{
/* If we have a new passphrase (which might be empty) we
store it under a passwd nonce so that the caller may
send that nonce again to use it for another key. */
if (!passwd_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
passwd_nonce = bin2hex (buf, 12, NULL);
}
if (passwd_nonce
&& !agent_put_cache (ctrl, passwd_nonce, CACHE_MODE_NONCE,
newpass, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "PASSWD_NONCE", passwd_nonce);
xfree (ctrl->server_local->last_passwd_nonce);
ctrl->server_local->last_passwd_nonce = passwd_nonce;
passwd_nonce = NULL;
}
}
}
if (!err && opt_preset)
{
char hexgrip[40+1];
bin2hex(grip, 20, hexgrip);
err = agent_put_cache (ctrl, hexgrip, CACHE_MODE_ANY, newpass,
ctrl->cache_ttl_opt_preset);
}
xfree (newpass);
}
ctrl->in_passwd--;
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
leave:
xfree (passphrase);
gcry_sexp_release (s_skey);
xfree (shadow_info);
xfree (cache_nonce);
xfree (passwd_nonce);
return leave_cmd (ctx, err);
}
static const char hlp_preset_passphrase[] =
"PRESET_PASSPHRASE [--inquire] [--restricted] \\\n"
" <string_or_keygrip> <timeout> [<hexstring>]\n"
"\n"
"Set the cached passphrase/PIN for the key identified by the keygrip\n"
"to passwd for the given time, where -1 means infinite and 0 means\n"
"the default (currently only a timeout of -1 is allowed, which means\n"
"to never expire it). If passwd is not provided, ask for it via the\n"
"pinentry module unless --inquire is passed in which case the passphrase\n"
"is retrieved from the client via a server inquire. The option\n"
"--restricted can be used to put the passphrase into the cache used\n"
"by restricted connections.";
static gpg_error_t
cmd_preset_passphrase (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *grip_clear = NULL;
unsigned char *passphrase = NULL;
int ttl;
size_t len;
int opt_inquire;
int opt_restricted;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!opt.allow_preset_passphrase)
return set_error (GPG_ERR_NOT_SUPPORTED, "no --allow-preset-passphrase");
opt_inquire = has_option (line, "--inquire");
opt_restricted = has_option (line, "--restricted");
line = skip_options (line);
grip_clear = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
return gpg_error (GPG_ERR_MISSING_VALUE);
*line = '\0';
line++;
while (*line && (*line == ' ' || *line == '\t'))
line++;
/* Currently, only infinite timeouts are allowed. */
ttl = -1;
if (line[0] != '-' || line[1] != '1')
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
line++;
line++;
while (!(*line != ' ' && *line != '\t'))
line++;
/* Syntax check the hexstring. */
len = 0;
rc = parse_hexstring (ctx, line, &len);
if (rc)
return rc;
line[len] = '\0';
/* If there is a passphrase, use it. Currently, a passphrase is
required. */
if (*line)
{
if (opt_inquire)
{
rc = set_error (GPG_ERR_ASS_PARAMETER,
"both --inquire and passphrase specified");
goto leave;
}
/* Do in-place conversion. */
passphrase = line;
if (!hex2str (passphrase, passphrase, strlen (passphrase)+1, NULL))
rc = set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
}
else if (opt_inquire)
{
/* Note that the passphrase will be truncated at any null byte and the
* limit is 480 characters. */
size_t maxlen = 480;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", maxlen);
if (!rc)
{
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "PASSPHRASE", &passphrase, &len, maxlen);
assuan_end_confidential (ctx);
}
}
else
rc = set_error (GPG_ERR_NOT_IMPLEMENTED, "passphrase is required");
if (!rc)
{
int save_restricted = ctrl->restricted;
if (opt_restricted)
ctrl->restricted = 1;
rc = agent_put_cache (ctrl, grip_clear, CACHE_MODE_ANY, passphrase, ttl);
ctrl->restricted = save_restricted;
if (opt_inquire)
{
wipememory (passphrase, len);
xfree (passphrase);
}
}
leave:
return leave_cmd (ctx, rc);
}
static const char hlp_scd[] =
"SCD <commands to pass to the scdaemon>\n"
" \n"
"This is a general quote command to redirect everything to the\n"
"SCdaemon.";
static gpg_error_t
cmd_scd (assuan_context_t ctx, char *line)
{
int rc;
#ifdef BUILD_WITH_SCDAEMON
ctrl_t ctrl = assuan_get_pointer (ctx);
if (ctrl->restricted)
{
const char *argv[5];
int argc;
char *l;
l = xtrystrdup (line);
if (!l)
return gpg_error_from_syserror ();
argc = split_fields (l, argv, DIM (argv));
/* These commands are allowed. */
if ((argc >= 1 && !strcmp (argv[0], "SERIALNO"))
|| (argc == 2
&& !strcmp (argv[0], "GETINFO")
&& !strcmp (argv[1], "version"))
|| (argc == 2
&& !strcmp (argv[0], "GETATTR")
&& !strcmp (argv[1], "KEY-FPR"))
|| (argc == 2
&& !strcmp (argv[0], "KEYINFO")
&& !strcmp (argv[1], "--list=encr")))
xfree (l);
else
{
xfree (l);
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
}
}
/* All SCD prefixed commands may change a key. */
eventcounter.maybe_key_change++;
rc = divert_generic_cmd (ctrl, line, ctx);
#else
(void)ctx; (void)line;
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif
return rc;
}
static const char hlp_keywrap_key[] =
"KEYWRAP_KEY [--clear] <mode>\n"
"\n"
"Return a key to wrap another key. For now the key is returned\n"
"verbatim and thus makes not much sense because an eavesdropper on\n"
"the gpg-agent connection will see the key as well as the wrapped key.\n"
"However, this function may either be equipped with a public key\n"
"mechanism or not used at all if the key is a pre-shared key. In any\n"
"case wrapping the import and export of keys is a requirement for\n"
"certain cryptographic validations and thus useful. The key persists\n"
"until a RESET command but may be cleared using the option --clear.\n"
"\n"
"Supported modes are:\n"
" --import - Return a key to import a key into gpg-agent\n"
" --export - Return a key to export a key from gpg-agent";
static gpg_error_t
cmd_keywrap_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int clearopt = has_option (line, "--clear");
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
assuan_begin_confidential (ctx);
if (has_option (line, "--import"))
{
xfree (ctrl->server_local->import_key);
if (clearopt)
ctrl->server_local->import_key = NULL;
else if (!(ctrl->server_local->import_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->import_key,
KEYWRAP_KEYSIZE);
}
else if (has_option (line, "--export"))
{
xfree (ctrl->server_local->export_key);
if (clearopt)
ctrl->server_local->export_key = NULL;
else if (!(ctrl->server_local->export_key =
gcry_random_bytes (KEYWRAP_KEYSIZE, GCRY_STRONG_RANDOM)))
err = gpg_error_from_syserror ();
else
err = assuan_send_data (ctx, ctrl->server_local->export_key,
KEYWRAP_KEYSIZE);
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for MODE");
assuan_end_confidential (ctx);
return leave_cmd (ctx, err);
}
static const char hlp_import_key[] =
"IMPORT_KEY [--unattended] [--force] [--timestamp=<isodate>]\n"
" [<cache_nonce>]\n"
"\n"
"Import a secret key into the key store. The key is expected to be\n"
"encrypted using the current session's key wrapping key (cf. command\n"
"KEYWRAP_KEY) using the AESWRAP-128 algorithm. This function takes\n"
"no arguments but uses the inquiry \"KEYDATA\" to ask for the actual\n"
"key data. The unwrapped key must be a canonical S-expression. The\n"
"option --unattended tries to import the key as-is without any\n"
"re-encryption. An existing key can be overwritten with --force.\n"
"If --timestamp is given its value is recorded as the key's creation\n"
"time; the value is expected in ISO format (e.g. \"20030316T120000\").";
static gpg_error_t
cmd_import_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int opt_unattended;
time_t opt_timestamp;
int force;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *key = NULL;
size_t keylen, realkeylen;
char *passphrase = NULL;
unsigned char *finalkey = NULL;
size_t finalkeylen;
unsigned char grip[20];
gcry_sexp_t openpgp_sexp = NULL;
char *cache_nonce = NULL;
char *p;
const char *s;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
if (!ctrl->server_local->import_key)
{
err = gpg_error (GPG_ERR_MISSING_KEY);
goto leave;
}
opt_unattended = has_option (line, "--unattended");
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
{
err = set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
goto leave;
}
opt_timestamp = isotime2epoch (s+1);
if (opt_timestamp < 1)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
goto leave;
}
}
else
opt_timestamp = 0;
line = skip_options (line);
for (p=line; *p && *p != ' ' && *p != '\t'; p++)
;
*p = '\0';
if (*line)
cache_nonce = xtrystrdup (line);
eventcounter.maybe_key_change++;
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "KEYDATA",
&wrappedkey, &wrappedkeylen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (err)
goto leave;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->import_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
xfree (wrappedkey);
wrappedkey = NULL;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
err = keygrip_from_canon_sexp (key, realkeylen, grip);
if (err)
{
/* This might be due to an unsupported S-expression format.
Check whether this is openpgp-private-key and trigger that
import code. */
if (!gcry_sexp_sscan (&openpgp_sexp, NULL, key, realkeylen))
{
const char *tag;
size_t taglen;
tag = gcry_sexp_nth_data (openpgp_sexp, 0, &taglen);
if (tag && taglen == 19 && !memcmp (tag, "openpgp-private-key", 19))
;
else
{
gcry_sexp_release (openpgp_sexp);
openpgp_sexp = NULL;
}
}
if (!openpgp_sexp)
goto leave; /* Note that ERR is still set. */
}
if (openpgp_sexp)
{
/* In most cases the key is encrypted and thus the conversion
function from the OpenPGP format to our internal format will
ask for a passphrase. That passphrase will be returned and
used to protect the key using the same code as for regular
key import. */
xfree (key);
key = NULL;
err = convert_from_openpgp (ctrl, openpgp_sexp, force, grip,
ctrl->server_local->keydesc, cache_nonce,
&key, opt_unattended? NULL : &passphrase);
if (err)
goto leave;
realkeylen = gcry_sexp_canon_len (key, 0, NULL, &err);
if (!realkeylen)
goto leave; /* Invalid canonical encoded S-expression. */
if (passphrase)
{
log_assert (!opt_unattended);
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
}
}
else if (opt_unattended)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"\"--unattended\" may only be used with OpenPGP keys");
goto leave;
}
else
{
if (!force && !agent_key_available (ctrl, grip))
err = gpg_error (GPG_ERR_EEXIST);
else
{
char *prompt = xtryasprintf
(_("Please enter the passphrase to protect the "
"imported object within the %s system."), GNUPG_NAME);
if (!prompt)
err = gpg_error_from_syserror ();
else
err = agent_ask_new_passphrase (ctrl, prompt, &passphrase);
xfree (prompt);
}
if (err)
goto leave;
}
if (passphrase)
{
err = agent_protect (key, passphrase, &finalkey, &finalkeylen,
ctrl->s2k_count);
if (!err)
err = agent_write_private_key (ctrl, grip, finalkey, finalkeylen, force,
NULL, NULL, NULL, opt_timestamp);
}
else
err = agent_write_private_key (ctrl, grip, key, realkeylen, force,
NULL, NULL, NULL, opt_timestamp);
leave:
gcry_sexp_release (openpgp_sexp);
xfree (finalkey);
xfree (passphrase);
xfree (key);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (cache_nonce);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
static const char hlp_export_key[] =
"EXPORT_KEY [--cache-nonce=<nonce>] \\\n"
" [--openpgp|--mode1003] <hexkeygrip>\n"
"\n"
"Export a secret key from the key store. The key will be encrypted\n"
"using the current session's key wrapping key (cf. command KEYWRAP_KEY)\n"
"using the AESWRAP-128 algorithm. The caller needs to retrieve that key\n"
"prior to using this command. The function takes the keygrip as argument.\n"
"\n"
"If --openpgp is used, the secret key material will be exported in RFC 4880\n"
"compatible passphrase-protected form. In --mode1003 the secret key\n"
"is exported as s-expression as stored locally. Without those options,\n"
"the secret key material will be exported in the clear (after prompting\n"
"the user to unlock it, if needed).\n";
static gpg_error_t
cmd_export_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char grip[20];
gcry_sexp_t s_skey = NULL;
unsigned char *key = NULL;
size_t keylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
int openpgp, mode1003;
char *cache_nonce;
char *passphrase = NULL;
unsigned char *shadow_info = NULL;
char *pend;
int c;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
openpgp = has_option (line, "--openpgp");
mode1003 = has_option (line, "--mode1003");
if (mode1003)
openpgp = 0;
cache_nonce = option_value (line, "--cache-nonce");
if (cache_nonce)
{
for (pend = cache_nonce; *pend && !spacep (pend); pend++)
;
c = *pend;
*pend = '\0';
cache_nonce = xtrystrdup (cache_nonce);
*pend = c;
if (!cache_nonce)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
line = skip_options (line);
if (!ctrl->server_local->export_key)
{
err = set_error (GPG_ERR_MISSING_KEY, "did you run KEYWRAP_KEY ?");
goto leave;
}
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (ctrl, grip))
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Get the key from the file. With the openpgp flag we also ask for
* the passphrase so that we can use it to re-encrypt it. In
* mode1003 we return the key as-is. FIXME: if the key is still in
* OpenPGP-native mode we should first convert it to our internal
* protection. */
if (mode1003)
err = agent_raw_key_from_file (ctrl, grip, &s_skey, NULL);
else
err = agent_key_from_file (ctrl, cache_nonce,
ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL, &s_skey,
openpgp ? &passphrase : NULL, NULL);
if (err)
goto leave;
if (shadow_info)
{
/* Key is on a smartcard. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
if (openpgp)
{
/* The openpgp option changes the key format into the OpenPGP
key transfer format. The result is already a padded
canonical S-expression. */
if (!passphrase)
{
err = agent_ask_new_passphrase
(ctrl, _("This key (or subkey) is not protected with a passphrase."
" Please enter a new passphrase to export it."),
&passphrase);
if (err)
goto leave;
}
err = convert_to_openpgp (ctrl, s_skey, passphrase, &key, &keylen);
if (!err && passphrase)
{
if (!cache_nonce)
{
char buf[12];
gcry_create_nonce (buf, 12);
cache_nonce = bin2hex (buf, 12, NULL);
}
if (cache_nonce
&& !agent_put_cache (ctrl, cache_nonce, CACHE_MODE_NONCE,
passphrase, CACHE_TTL_NONCE))
{
assuan_write_status (ctx, "CACHE_NONCE", cache_nonce);
xfree (ctrl->server_local->last_cache_nonce);
ctrl->server_local->last_cache_nonce = cache_nonce;
cache_nonce = NULL;
}
}
}
else
{
/* Convert into a canonical S-expression and wrap that. */
err = make_canon_sexp_pad (s_skey, 1, &key, &keylen);
}
if (err)
goto leave;
gcry_sexp_release (s_skey);
s_skey = NULL;
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd,
ctrl->server_local->export_key, KEYWRAP_KEYSIZE);
if (err)
goto leave;
wrappedkeylen = keylen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
goto leave;
xfree (key);
key = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
assuan_begin_confidential (ctx);
err = assuan_send_data (ctx, wrappedkey, wrappedkeylen);
assuan_end_confidential (ctx);
leave:
xfree (cache_nonce);
xfree (passphrase);
xfree (wrappedkey);
gcry_cipher_close (cipherhd);
xfree (key);
gcry_sexp_release (s_skey);
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_delete_key[] =
"DELETE_KEY [--force|--stub-only] <hexstring_with_keygrip>\n"
"\n"
"Delete a secret key from the key store. If --force is used\n"
"and a loopback pinentry is allowed, the agent will not ask\n"
"the user for confirmation. If --stub-only is used the key will\n"
"only be deleted if it is a reference to a token.";
static gpg_error_t
cmd_delete_key (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int force, stub_only;
unsigned char grip[20];
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
stub_only = has_option (line, "--stub-only");
line = skip_options (line);
eventcounter.maybe_key_change++;
/* If the use of a loopback pinentry has been disabled, we assume
* that a silent deletion of keys shall also not be allowed. */
if (!opt.allow_loopback_pinentry)
force = 0;
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
err = agent_delete_key (ctrl, ctrl->server_local->keydesc, grip,
force, stub_only);
if (err)
goto leave;
leave:
xfree (ctrl->server_local->keydesc);
ctrl->server_local->keydesc = NULL;
return leave_cmd (ctx, err);
}
#if SIZEOF_TIME_T > SIZEOF_UNSIGNED_LONG
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010llu))"
#else
#define KEYTOCARD_TIMESTAMP_FORMAT "(10:created-at10:%010lu))"
#endif
static const char hlp_keytocard[] =
"KEYTOCARD [--force] <hexgrip> <serialno> <keyref> [<timestamp> [<ecdh>]]\n"
"\n"
"TIMESTAMP is required for OpenPGP and defaults to the Epoch.\n"
"ECDH are the hexified ECDH parameters for OpenPGP.\n"
"SERIALNO is used for checking; use \"-\" to disable the check.";
static gpg_error_t
cmd_keytocard (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int force;
gpg_error_t err = 0;
const char *argv[5];
int argc;
unsigned char grip[20];
const char *serialno, *keyref;
gcry_sexp_t s_skey = NULL;
unsigned char *keydata;
size_t keydatalen;
unsigned char *shadow_info = NULL;
time_t timestamp;
char *ecdh_params = NULL;
unsigned int ecdh_params_len;
unsigned int extralen1, extralen2;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
force = has_option (line, "--force");
line = skip_options (line);
argc = split_fields (line, argv, DIM (argv));
if (argc < 3)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = parse_keygrip (ctx, argv[0], grip);
if (err)
goto leave;
if (agent_key_available (ctrl, grip))
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* Note that checking of the s/n is currently not implemented but we
* want to provide a clean interface if we ever implement it. */
serialno = argv[1];
if (!strcmp (serialno, "-"))
serialno = NULL;
keyref = argv[2];
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL, &timestamp);
if (err)
goto leave;
if (shadow_info)
{
/* Key is already on a smartcard - we can't extract it. */
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
/* Default to the creation time as stored in the private key. The
* parameter is here so that gpg can make sure that the timestamp as
* used. It is also important for OpenPGP cards to allow computing
* of the fingerprint. Same goes for the ECDH params. */
if (argc > 3)
{
timestamp = isotime2epoch (argv[3]);
if (argc > 4)
{
size_t n;
err = parse_hexstring (ctx, argv[4], &n);
if (err)
goto leave; /* Badly formatted ecdh params. */
n /= 2;
if (n < 4)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "ecdh param too short");
goto leave;
}
ecdh_params_len = n;
ecdh_params = xtrymalloc (ecdh_params_len);
if (!ecdh_params)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (hex2bin (argv[4], ecdh_params, ecdh_params_len) < 0)
{
err = set_error (GPG_ERR_BUG, "hex2bin");
goto leave;
}
}
}
else if (timestamp == (time_t)(-1))
timestamp = isotime2epoch ("19700101T000000");
if (timestamp == (time_t)(-1))
{
err = gpg_error (GPG_ERR_INV_TIME);
goto leave;
}
/* Note: We can't use make_canon_sexp because we need to allocate a
* few extra bytes for our hack below. The 20 for extralen2
* accounts for the sexp length of ecdh_params. */
keydatalen = gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, NULL, 0);
extralen1 = 30;
extralen2 = ecdh_params? (20+20+ecdh_params_len) : 0;
keydata = xtrymalloc_secure (keydatalen + extralen1 + extralen2);
if (keydata == NULL)
{
err = gpg_error_from_syserror ();
goto leave;
}
gcry_sexp_sprint (s_skey, GCRYSEXP_FMT_CANON, keydata, keydatalen);
gcry_sexp_release (s_skey);
s_skey = NULL;
keydatalen--; /* Decrement for last '\0'. */
/* Hack to insert the timestamp "created-at" into the private key. */
snprintf (keydata+keydatalen-1, extralen1, KEYTOCARD_TIMESTAMP_FORMAT,
timestamp);
keydatalen += 10 + 19 - 1;
/* Hack to insert the timestamp "ecdh-params" into the private key. */
if (ecdh_params)
{
snprintf (keydata+keydatalen-1, extralen2, "(11:ecdh-params%u:",
ecdh_params_len);
keydatalen += strlen (keydata+keydatalen-1) -1;
memcpy (keydata+keydatalen, ecdh_params, ecdh_params_len);
keydatalen += ecdh_params_len;
memcpy (keydata+keydatalen, "))", 3);
keydatalen += 2;
}
err = divert_writekey (ctrl, force, serialno, keyref, keydata, keydatalen);
xfree (keydata);
leave:
xfree (ecdh_params);
gcry_sexp_release (s_skey);
xfree (shadow_info);
return leave_cmd (ctx, err);
}
static const char hlp_get_secret[] =
"GET_SECRET <key>\n"
"\n"
"Return the secret value stored under KEY\n";
static gpg_error_t
cmd_get_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *p, *key;
char *value = NULL;
size_t valuelen;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
goto leave;
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
value = agent_get_cache (ctrl, key, CACHE_MODE_DATA);
if (!value)
{
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
valuelen = percent_unescape_inplace (value, 0);
err = assuan_send_data (ctx, value, valuelen);
wipememory (value, valuelen);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_put_secret[] =
"PUT_SECRET [--clear] <key> <ttl> [<percent_escaped_value>]\n"
"\n"
"This commands stores a secret under KEY in gpg-agent's in-memory\n"
"cache. The TTL must be explicitly given by TTL and the options\n"
"from the configuration file are not used. The value is either given\n"
"percent-escaped as 3rd argument or if not given inquired by gpg-agent\n"
"using the keyword \"SECRET\".\n"
"The option --clear removes the secret from the cache."
"";
static gpg_error_t
cmd_put_secret (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int opt_clear;
unsigned char *value = NULL;
size_t valuelen = 0;
size_t n;
char *p, *key, *ttlstr;
unsigned char *valstr;
int ttl;
char *string = NULL;
/* For now we allow this only for local connections. */
if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
opt_clear = has_option (line, "--clear");
line = skip_options (line);
for (p=line; *p == ' '; p++)
;
key = p;
ttlstr = NULL;
valstr = NULL;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
ttlstr = p;
p = strchr (ttlstr, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
valstr = p;
}
}
}
if (!*key)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key given");
goto leave;
}
if (!ttlstr || !*ttlstr || !(n = parse_ttl (ttlstr, &ttl)))
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no or invalid TTL given");
goto leave;
}
if (valstr && opt_clear)
{
err = set_error (GPG_ERR_ASS_PARAMETER,
"value not expected with --clear");
goto leave;
}
if (valstr)
{
valuelen = percent_unescape_inplace (valstr, 0);
value = NULL;
}
else /* Inquire the value to store */
{
err = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%u",MAXLEN_PUT_SECRET);
if (!err)
{
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "SECRET",
&value, &valuelen, MAXLEN_PUT_SECRET);
assuan_end_confidential (ctx);
}
if (err)
goto leave;
}
/* Our cache expects strings and thus we need to turn the buffer
* into a string. Instead of resorting to base64 encoding we use a
* special percent escaping which only quoted the Nul and the
* percent character. */
string = percent_data_escape (0, NULL, value? value : valstr, valuelen);
if (!string)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = agent_put_cache (ctrl, key, CACHE_MODE_DATA, string, ttl);
leave:
if (string)
{
wipememory (string, strlen (string));
xfree (string);
}
if (value)
{
wipememory (value, valuelen);
xfree (value);
}
return leave_cmd (ctx, err);
}
static const char hlp_keytotpm[] =
"KEYTOTPM <hexstring_with_keygrip>\n"
"\n";
static gpg_error_t
cmd_keytotpm (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char grip[20];
gcry_sexp_t s_skey;
unsigned char *shadow_info = NULL;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
err = parse_keygrip (ctx, line, grip);
if (err)
goto leave;
if (agent_key_available (ctrl, grip))
{
err =gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
err = agent_key_from_file (ctrl, NULL, ctrl->server_local->keydesc, grip,
&shadow_info, CACHE_MODE_IGNORE, NULL,
&s_skey, NULL, NULL);
if (err)
{
xfree (shadow_info);
goto leave;
}
if (shadow_info)
{
/* Key is on a TPM or smartcard already. */
xfree (shadow_info);
gcry_sexp_release (s_skey);
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
goto leave;
}
err = divert_tpm2_writekey (ctrl, grip, s_skey);
gcry_sexp_release (s_skey);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_getval[] =
"GETVAL <key>\n"
"\n"
"Return the value for KEY from the special environment as created by\n"
"PUTVAL.";
static gpg_error_t
cmd_getval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *p;
struct putval_item_s *vl;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "too many arguments");
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list; vl; vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Got an entry. */
rc = assuan_send_data (ctx, vl->d+vl->off, vl->len);
else
return gpg_error (GPG_ERR_NO_DATA);
return leave_cmd (ctx, rc);
}
static const char hlp_putval[] =
"PUTVAL <key> [<percent_escaped_value>]\n"
"\n"
"The gpg-agent maintains a kind of environment which may be used to\n"
"store key/value pairs in it, so that they can be retrieved later.\n"
"This may be used by helper daemons to daemonize themself on\n"
"invocation and register them with gpg-agent. Callers of the\n"
"daemon's service may now first try connect to get the information\n"
"for that service from gpg-agent through the GETVAL command and then\n"
"try to connect to that daemon. Only if that fails they may start\n"
"an own instance of the service daemon. \n"
"\n"
"KEY is an arbitrary symbol with the same syntax rules as keys\n"
"for shell environment variables. PERCENT_ESCAPED_VALUE is the\n"
"corresponding value; they should be similar to the values of\n"
"envronment variables but gpg-agent does not enforce any\n"
"restrictions. If that value is not given any value under that KEY\n"
"is removed from this special environment.";
static gpg_error_t
cmd_putval (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
char *key = NULL;
char *value = NULL;
size_t valuelen = 0;
char *p;
struct putval_item_s *vl, *vlprev;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
for (p=line; *p == ' '; p++)
;
key = p;
p = strchr (key, ' ');
if (p)
{
*p++ = 0;
for (; *p == ' '; p++)
;
if (*p)
{
value = p;
p = strchr (value, ' ');
if (p)
*p = 0;
valuelen = percent_plus_unescape_inplace (value, 0);
}
}
if (!*key)
return set_error (GPG_ERR_ASS_PARAMETER, "no key given");
for (vl=putval_list,vlprev=NULL; vl; vlprev=vl, vl = vl->next)
if ( !strcmp (vl->d, key) )
break;
if (vl) /* Delete old entry. */
{
if (vlprev)
vlprev->next = vl->next;
else
putval_list = vl->next;
xfree (vl);
}
if (valuelen) /* Add entry. */
{
vl = xtrymalloc (sizeof *vl + strlen (key) + valuelen);
if (!vl)
rc = gpg_error_from_syserror ();
else
{
vl->len = valuelen;
vl->off = strlen (key) + 1;
strcpy (vl->d, key);
memcpy (vl->d + vl->off, value, valuelen);
vl->next = putval_list;
putval_list = vl;
}
}
return leave_cmd (ctx, rc);
}
static const char hlp_updatestartuptty[] =
"UPDATESTARTUPTTY\n"
"\n"
"Set startup TTY and X11 DISPLAY variables to the values of this\n"
"session. This command is useful to pull future pinentries to\n"
"another screen. It is only required because there is no way in the\n"
"ssh-agent protocol to convey this information.";
static gpg_error_t
cmd_updatestartuptty (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
session_env_t se;
char *lc_ctype = NULL;
char *lc_messages = NULL;
int iterator;
const char *name;
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
se = session_env_new ();
if (!se)
err = gpg_error_from_syserror ();
iterator = 0;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
const char *value = session_env_getenv (ctrl->session_env, name);
if (value)
err = session_env_setenv (se, name, value);
}
if (!err && ctrl->lc_ctype)
if (!(lc_ctype = xtrystrdup (ctrl->lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && ctrl->lc_messages)
if (!(lc_messages = xtrystrdup (ctrl->lc_messages)))
err = gpg_error_from_syserror ();
if (err)
{
session_env_release (se);
xfree (lc_ctype);
xfree (lc_messages);
}
else
{
session_env_release (opt.startup_env);
opt.startup_env = se;
xfree (opt.startup_lc_ctype);
opt.startup_lc_ctype = lc_ctype;
xfree (opt.startup_lc_messages);
opt.startup_lc_messages = lc_messages;
}
return err;
}
static const char hlp_killagent[] =
"KILLAGENT\n"
"\n"
"Stop the agent.";
static gpg_error_t
cmd_killagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_reloadagent[] =
"RELOADAGENT\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadagent (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
if (ctrl->restricted)
return leave_cmd (ctx, gpg_error (GPG_ERR_FORBIDDEN));
agent_sighup_action ();
return 0;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multipurpose function to return a variety of information.\n"
"Supported values for WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" ssh_socket_name - Return the name of the ssh socket.\n"
" scd_running - Return OK if the SCdaemon is already running.\n"
" s2k_time - Return the time in milliseconds required for S2K.\n"
" s2k_count - Return the standard S2K count.\n"
" s2k_count_cal - Return the calibrated S2K count.\n"
" std_env_names - List the names of the standard environment.\n"
" std_session_env - List the standard session environment.\n"
" std_startup_env - List the standard startup environment.\n"
" getenv NAME - Return value of envvar NAME.\n"
" connections - Return number of active connections.\n"
" jent_active - Returns OK if Libgcrypt's JENT is active.\n"
" ephemeral - Returns OK if the connection is in ephemeral mode.\n"
" restricted - Returns OK if the connection is in restricted mode.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if command CMD has option OPT.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
if (!strcmp (line, "version"))
{
const char *s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "s2k_count"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "ephemeral"))
{
rc = ctrl->ephemeral_mode? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (!strcmp (line, "restricted"))
{
rc = ctrl->restricted? 0 : gpg_error (GPG_ERR_FALSE);
}
else if (ctrl->restricted)
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All sub-commands below are not allowed in restricted mode. */
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_agent_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "ssh_socket_name"))
{
const char *s = get_agent_ssh_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "scd_running"))
{
rc = agent_daemon_check_running (DAEMON_SCD)? 0:gpg_error (GPG_ERR_FALSE);
}
else if (!strcmp (line, "std_env_names"))
{
int iterator;
const char *name;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
rc = assuan_send_data (ctx, name, strlen (name)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
if (rc)
break;
}
}
else if (!strcmp (line, "std_session_env")
|| !strcmp (line, "std_startup_env"))
{
int iterator;
const char *name, *value;
char *string;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
value = session_env_getenv_or_default
(line[5] == 't'? opt.startup_env:ctrl->session_env, name, NULL);
if (value)
{
string = xtryasprintf ("%s=%s", name, value);
if (!string)
rc = gpg_error_from_syserror ();
else
{
rc = assuan_send_data (ctx, string, strlen (string)+1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (rc)
break;
}
}
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
rc = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
rc = assuan_send_data (ctx, s, strlen (s));
}
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d",
get_agent_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "jent_active"))
{
char *buf;
const char *fields[5];
buf = gcry_get_config (0, "rng-type");
if (buf
&& split_fields_colon (buf, fields, DIM (fields)) >= 5
&& atoi (fields[4]) > 0)
rc = 0;
else
rc = gpg_error (GPG_ERR_FALSE);
gcry_free (buf);
}
else if (!strcmp (line, "s2k_count_cal"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_calibrated_s2k_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "s2k_time"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", get_standard_s2k_time ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
/* This function is called by Libassuan to parse the OPTION command.
It has been registered similar to the other Assuan commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "agent-awareness"))
{
/* The value is a version string telling us of which agent
version the caller is aware of. */
ctrl->server_local->allow_fully_canceled =
gnupg_compare_version (value, "2.1.0");
}
else if (!strcmp (key, "ephemeral"))
{
ctrl->ephemeral_mode = *value? atoi (value) : 0;
}
else if (ctrl->restricted)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
}
/* All options below are not allowed in restricted mode. */
else if (!strcmp (key, "putenv"))
{
/* Change the session's environment to be used for the
Pinentry. Valid values are:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
*/
err = session_env_putenv (ctrl->session_env, value);
}
else if (!strcmp (key, "display"))
{
err = session_env_setenv (ctrl->session_env, "DISPLAY", value);
}
else if (!strcmp (key, "ttyname"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "GPG_TTY", value);
}
else if (!strcmp (key, "ttytype"))
{
if (!opt.keep_tty)
err = session_env_setenv (ctrl->session_env, "TERM", value);
}
else if (!strcmp (key, "lc-ctype"))
{
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = xtrystrdup (value);
if (!ctrl->lc_ctype)
return out_of_core ();
}
else if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else if (!strcmp (key, "xauthority"))
{
err = session_env_setenv (ctrl->session_env, "XAUTHORITY", value);
}
else if (!strcmp (key, "pinentry-user-data"))
{
err = session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", value);
}
else if (!strcmp (key, "use-cache-for-signing"))
ctrl->server_local->use_cache_for_signing = *value? !!atoi (value) : 0;
else if (!strcmp (key, "allow-pinentry-notify"))
ctrl->server_local->allow_pinentry_notify = 1;
else if (!strcmp (key, "pinentry-mode"))
{
int tmp = parse_pinentry_mode (value);
if (tmp == -1)
err = gpg_error (GPG_ERR_INV_VALUE);
else if (tmp == PINENTRY_MODE_LOOPBACK && !opt.allow_loopback_pinentry)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
ctrl->pinentry_mode = tmp;
}
else if (!strcmp (key, "cache-ttl-opt-preset"))
{
ctrl->cache_ttl_opt_preset = *value? atoi (value) : 0;
}
else if (!strcmp (key, "s2k-count"))
{
ctrl->s2k_count = *value? strtoul(value, NULL, 10) : 0;
if (ctrl->s2k_count && ctrl->s2k_count < 65536)
{
ctrl->s2k_count = 0;
}
}
else if (!strcmp (key, "pretend-request-origin"))
{
log_assert (!ctrl->restricted);
switch (parse_request_origin (value))
{
case REQUEST_ORIGIN_LOCAL: ctrl->restricted = 0; break;
case REQUEST_ORIGIN_REMOTE: ctrl->restricted = 1; break;
case REQUEST_ORIGIN_BROWSER: ctrl->restricted = 2; break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
/* Better pretend to be remote in case of a bad value. */
ctrl->restricted = 1;
break;
}
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
/* Called by libassuan after all commands. ERR is the error from the
last assuan operation and not the one returned from the command. */
static void
post_cmd_notify (assuan_context_t ctx, gpg_error_t err)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)err;
/* Switch off any I/O monitor controlled logging pausing. */
ctrl->server_local->pause_io_logging = 0;
}
/* This function is called by libassuan for all I/O. We use it here
to disable logging for the GETEVENTCOUNTER commands. This is so
that the debug output won't get cluttered by this primitive
command. */
static unsigned int
io_monitor (assuan_context_t ctx, void *hook, int direction,
const char *line, size_t linelen)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void) hook;
/* We want to suppress all Assuan log messages for connections from
* self. However, assuan_get_pid works only after
* assuan_accept. Now, assuan_accept already logs a line ending with
* the process id. We use this hack here to get the peers pid so
* that we can compare it to our pid. We should add an assuan
* function to return the pid for a file descriptor and use that to
* detect connections to self. */
if (ctx && !ctrl->server_local->greeting_seen
&& direction == ASSUAN_IO_TO_PEER)
{
ctrl->server_local->greeting_seen = 1;
if (linelen > 32
&& !strncmp (line, "OK Pleased to meet you, process ", 32)
&& strtoul (line+32, NULL, 10) == getpid ())
return ASSUAN_IO_MONITOR_NOLOG;
}
/* Do not log self-connections. This makes the log cleaner because
* we won't see the check-our-own-socket calls. */
if (ctx && ctrl->server_local->connect_from_self)
return ASSUAN_IO_MONITOR_NOLOG;
/* Note that we only check for the uppercase name. This allows the user to
see the logging for debugging if using a non-upercase command
name. */
if (ctx && direction == ASSUAN_IO_FROM_PEER
&& linelen >= 15
&& !strncmp (line, "GETEVENTCOUNTER", 15)
&& (linelen == 15 || spacep (line+15)))
{
ctrl->server_local->pause_io_logging = 1;
}
return ctrl->server_local->pause_io_logging? ASSUAN_IO_MONITOR_NOLOG : 0;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "GET_PASSPHRASE"))
{
if (!strcmp (cmdopt, "repeat"))
return 1;
if (!strcmp (cmdopt, "newsymkey"))
return 1;
}
else if (!strcmp (cmd, "EXPORT_KEY"))
{
if (!strcmp (cmdopt, "mode1003"))
return 1;
}
return 0;
}
/* Tell Libassuan about our commands. Also register the other Assuan
handlers. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "GETEVENTCOUNTER",cmd_geteventcounter, hlp_geteventcounter },
{ "ISTRUSTED", cmd_istrusted, hlp_istrusted },
{ "HAVEKEY", cmd_havekey, hlp_havekey },
{ "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ "SIGKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEY", cmd_sigkey, hlp_sigkey },
{ "SETKEYDESC", cmd_setkeydesc,hlp_setkeydesc },
{ "SETHASH", cmd_sethash, hlp_sethash },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKDECRYPT", cmd_pkdecrypt, hlp_pkdecrypt },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "GET_PASSPHRASE", cmd_get_passphrase, hlp_get_passphrase },
{ "PRESET_PASSPHRASE", cmd_preset_passphrase, hlp_preset_passphrase },
{ "CLEAR_PASSPHRASE", cmd_clear_passphrase, hlp_clear_passphrase },
{ "GET_CONFIRMATION", cmd_get_confirmation, hlp_get_confirmation },
{ "LISTTRUSTED", cmd_listtrusted, hlp_listtrusted },
{ "MARKTRUSTED", cmd_marktrusted, hlp_martrusted },
{ "LEARN", cmd_learn, hlp_learn },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "SCD", cmd_scd, hlp_scd },
{ "KEYWRAP_KEY", cmd_keywrap_key, hlp_keywrap_key },
{ "IMPORT_KEY", cmd_import_key, hlp_import_key },
{ "EXPORT_KEY", cmd_export_key, hlp_export_key },
{ "DELETE_KEY", cmd_delete_key, hlp_delete_key },
{ "GET_SECRET", cmd_get_secret, hlp_get_secret },
{ "PUT_SECRET", cmd_put_secret, hlp_put_secret },
{ "GETVAL", cmd_getval, hlp_getval },
{ "PUTVAL", cmd_putval, hlp_putval },
{ "UPDATESTARTUPTTY", cmd_updatestartuptty, hlp_updatestartuptty },
{ "KILLAGENT", cmd_killagent, hlp_killagent },
{ "RELOADAGENT", cmd_reloadagent,hlp_reloadagent },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "KEYTOCARD", cmd_keytocard, hlp_keytocard },
{ "KEYTOTPM", cmd_keytotpm, hlp_keytotpm },
{ "KEYATTR", cmd_keyattr, hlp_keyattr },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_register_post_cmd_notify (ctx, post_cmd_notify);
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If LISTEN_FD and FD is given as -1, this is a
simple piper server, otherwise it is a regular server. CTRL is the
control structure for this connection; it has only the basic
initialization. */
void
start_command_handler (ctrl_t ctrl, gnupg_fd_t listen_fd, gnupg_fd_t fd)
{
int rc;
assuan_context_t ctx = NULL;
if (ctrl->restricted)
{
if (agent_copy_startup_env (ctrl))
return;
}
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n", gpg_strerror (rc));
agent_exit (2);
}
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else if (listen_fd != GNUPG_INVALID_FD)
{
rc = assuan_init_socket_server (ctx, listen_fd, 0);
/* FIXME: Need to call assuan_sock_set_nonce for Windows. But
this branch is currently not used. */
}
else
{
rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
agent_exit (2);
}
assuan_set_pointer (ctx, ctrl);
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->assuan_ctx = ctx;
ctrl->server_local->use_cache_for_signing = 1;
ctrl->digest.data = NULL;
ctrl->digest.raw_value = 0;
ctrl->digest.is_pss = 0;
assuan_set_io_monitor (ctx, io_monitor, NULL);
agent_set_progress_cb (progress_cb, ctrl);
for (;;)
{
assuan_peercred_t client_creds; /* Note: Points into CTX. */
pid_t pid;
rc = assuan_accept (ctx);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_get_peercred (ctx, &client_creds);
if (rc)
{
/* Note that on Windows we don't get the peer credentials
* and thus we silence the error. */
if (listen_fd == GNUPG_INVALID_FD && fd == GNUPG_INVALID_FD)
;
#ifdef HAVE_W32_SYSTEM
else if (gpg_err_code (rc) == GPG_ERR_ASS_GENERAL)
;
#endif
else
log_info ("Assuan get_peercred failed: %s\n", gpg_strerror (rc));
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
}
else
{
#ifdef HAVE_W32_SYSTEM
pid = assuan_get_pid (ctx);
ctrl->client_uid = -1;
#else
pid = client_creds->pid;
ctrl->client_uid = client_creds->uid;
#endif
}
ctrl->client_pid = (pid == ASSUAN_INVALID_PID)? 0 : (unsigned long)pid;
ctrl->server_local->connect_from_self = (pid == getpid ());
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Clear the keyinfo cache. */
agent_card_free_keyinfo (ctrl->server_local->last_card_keyinfo.ki);
/* Reset the nonce caches. */
clear_nonce_cache (ctrl);
/* Reset the SCD if needed. */
agent_reset_daemon (ctrl);
/* Reset the pinentry (in case of popup messages). */
agent_reset_query (ctrl);
/* Cleanup. */
assuan_release (ctx);
xfree (ctrl->server_local->keydesc);
xfree (ctrl->server_local->import_key);
xfree (ctrl->server_local->export_key);
if (ctrl->server_local->stopme)
agent_exit (0);
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
/* Helper for the pinentry loopback mode. It merely passes the
parameters on to the client. */
gpg_error_t
pinentry_loopback(ctrl_t ctrl, const char *keyword,
unsigned char **buffer, size_t *size,
size_t max_length)
{
gpg_error_t rc;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
rc = print_assuan_status (ctx, "INQUIRE_MAXLEN", "%zu", max_length);
if (rc)
return rc;
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, keyword, buffer, size, max_length);
assuan_end_confidential (ctx);
return rc;
}
/* Helper for the pinentry loopback mode to ask confirmation
or just to show message. */
gpg_error_t
pinentry_loopback_confirm (ctrl_t ctrl, const char *desc,
int ask_confirmation,
const char *ok, const char *notok)
{
gpg_error_t err = 0;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (desc)
err = print_assuan_status (ctx, "SETDESC", "%s", desc);
if (!err && ok)
err = print_assuan_status (ctx, "SETOK", "%s", ok);
if (!err && notok)
err = print_assuan_status (ctx, "SETNOTOK", "%s", notok);
if (!err)
err = assuan_inquire (ctx, ask_confirmation ? "CONFIRM 1" : "CONFIRM 0",
NULL, NULL, 0);
return err;
}
diff --git a/agent/divert-scd.c b/agent/divert-scd.c
index d8c2bcca7..d390ebb06 100644
--- a/agent/divert-scd.c
+++ b/agent/divert-scd.c
@@ -1,530 +1,530 @@
/* divert-scd.c - divert operations to the scdaemon
* Copyright (C) 2002, 2003, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#include "agent.h"
#include "../common/i18n.h"
#include "../common/sexp-parse.h"
/* Put the DIGEST into an DER encoded container and return it in R_VAL. */
static int
encode_md_for_card (const unsigned char *digest, size_t digestlen, int algo,
unsigned char **r_val, size_t *r_len)
{
unsigned char *frame;
unsigned char asn[100];
size_t asnlen;
*r_val = NULL;
*r_len = 0;
asnlen = DIM(asn);
if (!algo || gcry_md_test_algo (algo))
return gpg_error (GPG_ERR_DIGEST_ALGO);
if (gcry_md_algo_info (algo, GCRYCTL_GET_ASNOID, asn, &asnlen))
{
log_error ("no object identifier for algo %d\n", algo);
return gpg_error (GPG_ERR_INTERNAL);
}
frame = xtrymalloc (asnlen + digestlen);
if (!frame)
return out_of_core ();
memcpy (frame, asn, asnlen);
memcpy (frame+asnlen, digest, digestlen);
if (DBG_CRYPTO)
log_printhex (frame, asnlen+digestlen, "encoded hash:");
*r_val = frame;
*r_len = asnlen+digestlen;
return 0;
}
/* Return true if STRING ends in "%0A". */
static int
has_percent0A_suffix (const char *string)
{
size_t n;
return (string
&& (n = strlen (string)) >= 3
&& !strcmp (string + n - 3, "%0A"));
}
/* Callback used to ask for the PIN which should be set into BUF. The
buf has been allocated by the caller and is of size MAXBUF which
includes the terminating null. The function should return an UTF-8
string with the passphrase, the buffer may optionally be padded
with arbitrary characters.
If DESC_TEXT is not NULL it can be used as further information shown
atop of the INFO message.
INFO gets displayed as part of a generic string. However if the
first character of INFO is a vertical bar all up to the next
- verical bar are considered flags and only everything after the
+ vertical bar are considered flags and only everything after the
second vertical bar gets displayed as the full prompt.
Flags:
'N' = New PIN, this requests a second prompt to repeat the
PIN. If the PIN is not correctly repeated it starts from
all over.
'A' = The PIN is an Admin PIN, SO-PIN or alike.
'P' = The PIN is a PUK (Personal Unblocking Key).
'R' = The PIN is a Reset Code.
Example:
"|AN|Please enter the new security officer's PIN"
The text "Please ..." will get displayed and the flags 'A' and 'N'
are considered.
*/
static int
getpin_cb (void *opaque, const char *desc_text, const char *info,
char *buf, size_t maxbuf)
{
struct pin_entry_info_s *pi;
int rc;
ctrl_t ctrl = opaque;
const char *ends, *s;
int any_flags = 0;
int newpin = 0;
int resetcode = 0;
int is_puk = 0;
const char *again_text = NULL;
const char *prompt = "PIN";
if (buf && maxbuf < 2)
return gpg_error (GPG_ERR_INV_VALUE);
/* Parse the flags. */
if (info && *info =='|' && (ends=strchr (info+1, '|')))
{
for (s=info+1; s < ends; s++)
{
if (*s == 'A')
prompt = L_("Admin PIN");
else if (*s == 'P')
{
/* TRANSLATORS: A PUK is the Personal Unblocking Code
used to unblock a PIN. */
prompt = L_("PUK");
is_puk = 1;
}
else if (*s == 'N')
newpin = 1;
else if (*s == 'R')
{
prompt = L_("Reset Code");
resetcode = 1;
}
}
info = ends+1;
any_flags = 1;
}
else if (info && *info == '|')
log_debug ("pin_cb called without proper PIN info hack\n");
/* If BUF has been passed as NULL, we are in pinpad mode: The
callback opens the popup and immediately returns. */
if (!buf)
{
if (maxbuf == 0) /* Close the pinentry. */
{
agent_popup_message_stop (ctrl);
rc = 0;
}
else if (maxbuf == 1) /* Open the pinentry. */
{
if (info)
{
char *desc;
const char *desc2;
if (!strcmp (info, "--ack"))
{
desc2 = L_("Push ACK button on card/token.");
if (desc_text)
{
desc = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
desc2, NULL);
desc2 = NULL;
}
else
desc = NULL;
}
else
{
desc2 = NULL;
if (desc_text)
desc = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
info, "%0A%0A",
L_("Use the reader's pinpad for input."),
NULL);
else
desc = strconcat (info, "%0A%0A",
L_("Use the reader's pinpad for input."),
NULL);
}
if (!desc2 && !desc)
rc = gpg_error_from_syserror ();
else
{
rc = agent_popup_message_start (ctrl,
desc2? desc2:desc, NULL);
xfree (desc);
}
}
else
rc = agent_popup_message_start (ctrl, desc_text, NULL);
}
else
rc = gpg_error (GPG_ERR_INV_VALUE);
return rc;
}
/* FIXME: keep PI and TRIES in OPAQUE. Frankly this is a whole
mess because we should call the card's verify function from the
pinentry check pin CB. */
again:
pi = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = maxbuf-1;
pi->min_digits = 0; /* we want a real passphrase */
pi->max_digits = 16;
pi->max_tries = 3;
if (any_flags)
{
{
char *desc2;
if (desc_text)
desc2 = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
info, NULL);
else
desc2 = NULL;
rc = agent_askpin (ctrl, desc2? desc2 : info,
prompt, again_text, pi, NULL, 0);
xfree (desc2);
}
again_text = NULL;
if (!rc && newpin)
{
struct pin_entry_info_s *pi2;
pi2 = gcry_calloc_secure (1, sizeof (*pi) + maxbuf + 10);
if (!pi2)
{
rc = gpg_error_from_syserror ();
xfree (pi);
return rc;
}
pi2->max_length = maxbuf-1;
pi2->min_digits = 0;
pi2->max_digits = 16;
pi2->max_tries = 1;
rc = agent_askpin (ctrl,
(resetcode?
L_("Repeat this Reset Code"):
is_puk?
L_("Repeat this PUK"):
L_("Repeat this PIN")),
prompt, NULL, pi2, NULL, 0);
if (!rc && strcmp (pi->pin, pi2->pin))
{
again_text = (resetcode?
L_("Reset Code not correctly repeated; try again"):
is_puk?
L_("PUK not correctly repeated; try again"):
L_("PIN not correctly repeated; try again"));
xfree (pi2);
xfree (pi);
goto again;
}
xfree (pi2);
}
}
else
{
char *desc, *desc2;
if ( asprintf (&desc,
L_("Please enter the PIN%s%s%s to unlock the card"),
info? " (":"",
info? info:"",
info? ")":"") < 0)
desc = NULL;
if (desc_text)
desc2 = strconcat (desc_text,
has_percent0A_suffix (desc_text)
? "%0A" : "%0A%0A",
desc, NULL);
else
desc2 = NULL;
rc = agent_askpin (ctrl, desc2? desc2 : desc? desc : info,
prompt, NULL, pi, NULL, 0);
xfree (desc2);
xfree (desc);
}
if (!rc)
{
strncpy (buf, pi->pin, maxbuf-1);
buf[maxbuf-1] = 0;
}
xfree (pi);
return rc;
}
/* This function is used when a sign operation has been diverted to a
* smartcard.
*
* Note: If SHADOW_INFO is NULL the user can't be asked to insert the
* card, we simply try to use an inserted card with the given keygrip.
*
* FIXME: Explain the other args. */
int
divert_pksign (ctrl_t ctrl, const unsigned char *grip,
const unsigned char *digest, size_t digestlen, int algo,
unsigned char **r_sig,
size_t *r_siglen)
{
int rc;
char hexgrip[41];
size_t siglen;
unsigned char *sigval = NULL;
bin2hex (grip, 20, hexgrip);
if (!algo)
{
/* This is the PureEdDSA case. (DIGEST,DIGESTLEN) this the
* entire data which will be signed. */
rc = agent_card_pksign (ctrl, hexgrip, getpin_cb, ctrl, NULL,
0, digest, digestlen, &sigval, &siglen);
}
else if (algo == MD_USER_TLS_MD5SHA1)
{
int save = ctrl->use_auth_call;
ctrl->use_auth_call = 1;
rc = agent_card_pksign (ctrl, hexgrip, getpin_cb, ctrl, NULL,
algo, digest, digestlen, &sigval, &siglen);
ctrl->use_auth_call = save;
}
else
{
unsigned char *data;
size_t ndata;
rc = encode_md_for_card (digest, digestlen, algo, &data, &ndata);
if (!rc)
{
rc = agent_card_pksign (ctrl, hexgrip, getpin_cb, ctrl, NULL,
algo, data, ndata, &sigval, &siglen);
xfree (data);
}
}
if (!rc)
{
*r_sig = sigval;
*r_siglen = siglen;
}
return rc;
}
/* Decrypt the value given as an s-expression in CIPHER using the
key identified by SHADOW_INFO and return the plaintext in an
allocated buffer in R_BUF. The padding information is stored at
R_PADDING with -1 for not known, when it's not NULL. */
int
divert_pkdecrypt (ctrl_t ctrl,
const unsigned char *grip,
const unsigned char *cipher,
char **r_buf, size_t *r_len, int *r_padding)
{
int rc;
char hexgrip[41];
const unsigned char *s;
size_t n;
int depth;
const unsigned char *ciphertext;
size_t ciphertextlen;
char *plaintext;
size_t plaintextlen;
bin2hex (grip, 20, hexgrip);
if (r_padding)
*r_padding = -1;
s = cipher;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "enc-val"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
/* First check whether we have a flags parameter and skip it. */
if (smatch (&s, n, "flags"))
{
depth = 1;
if (sskip (&s, &depth) || depth)
return gpg_error (GPG_ERR_INV_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
}
if (smatch (&s, n, "rsa"))
{
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "a"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
}
else if (smatch (&s, n, "ecdh"))
{
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "s"))
{
n = snext (&s);
s += n;
if (*s++ != ')')
return gpg_error (GPG_ERR_INV_SEXP);
if (*s++ != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
}
if (!smatch (&s, n, "e"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
n = snext (&s);
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
if (!n)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
ciphertext = s;
ciphertextlen = n;
rc = agent_card_pkdecrypt (ctrl, hexgrip, getpin_cb, ctrl, NULL,
ciphertext, ciphertextlen,
&plaintext, &plaintextlen, r_padding);
if (!rc)
{
*r_buf = plaintext;
*r_len = plaintextlen;
}
return rc;
}
gpg_error_t
agent_card_ecc_kem (ctrl_t ctrl, const unsigned char *ecc_ct,
size_t ecc_point_len, unsigned char *ecc_ecdh)
{
gpg_error_t err = 0;
char *ecdh = NULL;
size_t len;
int rc;
rc = agent_card_pkdecrypt (ctrl, ctrl->keygrip, getpin_cb, ctrl, NULL,
ecc_ct, ecc_point_len, &ecdh, &len, NULL);
if (rc)
return rc;
if (len != ecc_point_len)
{
if (opt.verbose)
log_info ("%s: ECC result length invalid (%zu != %zu)\n",
__func__, len, ecc_point_len);
return gpg_error (GPG_ERR_INV_DATA);
}
else
memcpy (ecc_ecdh, ecdh, len);
xfree (ecdh);
return err;
}
gpg_error_t
divert_writekey (ctrl_t ctrl, int force, const char *serialno,
const char *keyref, const char *keydata, size_t keydatalen)
{
return agent_card_writekey (ctrl, force, serialno, keyref,
keydata, keydatalen, getpin_cb, ctrl);
}
int
divert_generic_cmd (ctrl_t ctrl, const char *cmdline, void *assuan_context)
{
return agent_card_scd (ctrl, cmdline, getpin_cb, ctrl, assuan_context);
}
diff --git a/agent/findkey.c b/agent/findkey.c
index 1f2938ea3..c0cbce7a2 100644
--- a/agent/findkey.c
+++ b/agent/findkey.c
@@ -1,2099 +1,2099 @@
/* findkey.c - Locate the secret key
* Copyright (C) 2001, 2002, 2003, 2004, 2005, 2007,
* 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2019 Werner Koch
* Copyright (C) 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <npth.h> /* (we use pth_sleep) */
#include "agent.h"
#include "../common/i18n.h"
#include "../common/ssh-utils.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
static gpg_error_t read_key_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta,
char **r_orig_key_value);
static gpg_error_t is_shadowed_key (gcry_sexp_t s_skey);
/* Helper to pass data to the check callback of the unprotect function. */
struct try_unprotect_arg_s
{
ctrl_t ctrl;
const unsigned char *protected_key;
unsigned char *unprotected_key;
int change_required; /* Set by the callback to indicate that the
user should change the passphrase. */
};
/* Return the file name for the 20 byte keygrip GRIP. With FOR_NEW
* create a file name for later renaming to the actual name. Return
* NULL on error. */
static char *
fname_from_keygrip (const unsigned char *grip, int for_new)
{
char hexgrip[40+4+4+1];
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, for_new? ".key.tmp" : ".key");
return make_filename_try (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
}
/* Helper until we have a "wipe" mode flag in es_fopen. */
static void
wipe_and_fclose (estream_t fp)
{
void *blob;
size_t blob_size;
if (!fp)
;
else if (es_fclose_snatch (fp, &blob, &blob_size))
{
log_error ("error wiping buffer during fclose\n");
es_fclose (fp);
}
else if (blob)
{
wipememory (blob, blob_size);
gpgrt_free (blob);
}
}
/* Replace all linefeeds in STRING by "%0A" and return a new malloced
* string. May return NULL on memory error. */
static char *
linefeed_to_percent0A (const char *string)
{
const char *s;
size_t n;
char *buf, *p;
for (n=0, s=string; *s; s++)
if (*s == '\n')
n += 3;
else
n++;
p = buf = xtrymalloc (n+1);
if (!buf)
return NULL;
for (s=string; *s; s++)
if (*s == '\n')
{
memcpy (p, "%0A", 3);
p += 3;
}
else
*p++ = *s;
*p = 0;
return buf;
}
/* Write the S-expression formatted key (BUFFER,LENGTH) to our key
* storage. With FORCE passed as true an existing key with the given
* GRIP will get overwritten. If SERIALNO and KEYREF are given a
* Token line is added to the key if the extended format is used. If
* TIMESTAMP is not zero and the key does not yet exists it will be
* recorded as creation date. */
gpg_error_t
agent_write_private_key (ctrl_t ctrl,
const unsigned char *grip,
const void *buffer, size_t length, int force,
const char *serialno, const char *keyref,
const char *dispserialno,
time_t timestamp)
{
gpg_error_t err;
char *fname = NULL;
char *tmpfname = NULL;
estream_t fp = NULL;
int newkey = 0;
nvc_t pk = NULL;
gcry_sexp_t key = NULL;
int removetmp = 0;
char *token0 = NULL;
char *token = NULL;
char *dispserialno_buffer = NULL;
char **tokenfields = NULL;
int is_regular;
int blocksigs = 0;
char *orig_key_value = NULL;
const char *s;
int force_modify = 0;
fname = (ctrl->ephemeral_mode
? xtrystrdup ("[ephemeral key store]")
: fname_from_keygrip (grip, 0));
if (!fname)
return gpg_error_from_syserror ();
err = read_key_file (ctrl, grip, &key, &pk, &orig_key_value);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
newkey = 1;
else
{
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
}
nvc_modified (pk, 1); /* Clear that flag after a read. */
if (!pk)
{
/* Key is still in the old format or does not exist - create a
* new container. */
pk = nvc_new_private_key ();
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
force_modify = 1;
}
/* Check whether we already have a regular key. */
is_regular = (key && gpg_err_code (is_shadowed_key (key)) != GPG_ERR_TRUE);
/* Turn (BUFFER,LENGTH) into a gcrypt s-expression and set it into
* our name value container. */
gcry_sexp_release (key);
err = gcry_sexp_sscan (&key, NULL, buffer, length);
if (err)
goto leave;
err = nvc_set_private_key (pk, key);
if (err)
goto leave;
/* Detect whether the key value actually changed and if not clear
* the modified flag. This extra check is required because
* read_key_file removes the Key entry from the container and we
* then create a new Key entry which might be the same, though. */
if (!force_modify
&& orig_key_value && (s = nvc_get_string (pk, "Key:"))
&& !strcmp (orig_key_value, s))
{
nvc_modified (pk, 1); /* Clear that flag. */
}
xfree (orig_key_value);
orig_key_value = NULL;
/* Check that we do not update a regular key with a shadow key. */
if (is_regular && gpg_err_code (is_shadowed_key (key)) == GPG_ERR_TRUE)
{
log_info ("updating regular key file '%s'"
" by a shadow key inhibited\n", fname);
err = 0; /* Simply ignore the error. */
goto leave;
}
/* Check that we update a regular key only in force mode. */
if (is_regular && !force)
{
log_error ("secret key file '%s' already exists\n", fname);
err = gpg_error (GPG_ERR_EEXIST);
goto leave;
}
/* If requested write a Token line. */
if (serialno && keyref)
{
nve_t item;
size_t token0len;
if (dispserialno)
{
/* Escape the DISPSERIALNO. */
dispserialno_buffer = percent_plus_escape (dispserialno);
if (!dispserialno_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
dispserialno = dispserialno_buffer;
}
token0 = strconcat (serialno, " ", keyref, NULL);
if (token0)
token = strconcat (token0, " - ", dispserialno? dispserialno:"-", NULL);
if (!token0 || !token)
{
err = gpg_error_from_syserror ();
goto leave;
}
token0len = strlen (token0);
for (item = nvc_lookup (pk, "Token:");
item;
item = nve_next_value (item, "Token:"))
if ((s = nve_value (item)) && !strncmp (s, token0, token0len))
break;
if (!item)
{
/* No token or no token with that value exists. Add a new
* one so that keys which have been stored on several cards
* are well supported. */
err = nvc_add (pk, "Token:", token);
if (err)
goto leave;
}
else
{
/* Token exists: Update the display s/n. It may have
* changed due to changes in a newer software version. */
if (s && (tokenfields = strtokenize (s, " \t\n"))
&& tokenfields[0] && tokenfields[1] && tokenfields[2]
&& tokenfields[3]
&& !strcmp (tokenfields[3], dispserialno))
; /* No need to update Token entry. */
else
{
err = nve_set (pk, item, token);
if (err)
goto leave;
}
}
}
/* If a timestamp has been supplied and the key is new, write a
* creation timestamp. (We douple check that there is no Created
* item yet.)*/
if (timestamp && newkey && !nvc_lookup (pk, "Created:"))
{
gnupg_isotime_t timebuf;
epoch2isotime (timebuf, timestamp);
err = nvc_add (pk, "Created:", timebuf);
if (err)
goto leave;
}
/* Check whether we need to write the file at all. */
if (!nvc_modified (pk, 0))
{
err = 0;
goto leave;
}
if (ctrl->ephemeral_mode)
{
ephemeral_private_key_t ek;
void *blob;
size_t blobsize;
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN))
break;
if (!ek)
{
ek = xtrycalloc (1, sizeof *ek);
if (!ek)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (ek->grip, grip, KEYGRIP_LEN);
ek->next = ctrl->ephemeral_keys;
ctrl->ephemeral_keys = ek;
}
fp = es_fopenmem (0, "wb,wipe");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open memory stream: %s\n", gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing to memory stream: %s\n",gpg_strerror (err));
goto leave;
}
if (es_fclose_snatch (fp, &blob, &blobsize) || !blob)
{
err = gpg_error_from_syserror ();
log_error ("error getting memory stream buffer: %s\n",
gpg_strerror (err));
/* Closing right away so that we don't try another snatch in
* the cleanup. */
es_fclose (fp);
fp = NULL;
goto leave;
}
fp = NULL;
xfree (ek->keybuf);
ek->keybuf = blob;
ek->keybuflen = blobsize;
}
else
{
/* Create a temporary file for writing. */
tmpfname = fname_from_keygrip (grip, 1);
fp = tmpfname ? es_fopen (tmpfname, "wbx,mode=-rw") : NULL;
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't create '%s': %s\n", tmpfname, gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (!err && es_fflush (fp))
err = gpg_error_from_syserror ();
if (err)
{
log_error ("error writing '%s': %s\n", tmpfname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error ("error closing '%s': %s\n", tmpfname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
else
fp = NULL;
err = gnupg_rename_file (tmpfname, fname, &blocksigs);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("error renaming '%s': %s\n", tmpfname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
}
bump_key_eventcounter ();
leave:
if (blocksigs)
gnupg_unblock_all_signals ();
if (ctrl->ephemeral_mode)
wipe_and_fclose (fp);
else
es_fclose (fp);
if (removetmp && tmpfname)
gnupg_remove (tmpfname);
xfree (orig_key_value);
xfree (fname);
xfree (tmpfname);
xfree (token);
xfree (token0);
xfree (dispserialno_buffer);
xfree (tokenfields);
gcry_sexp_release (key);
nvc_release (pk);
return err;
}
gpg_error_t
agent_update_private_key (ctrl_t ctrl, const unsigned char *grip, nvc_t pk)
{
gpg_error_t err;
char *fname0 = NULL; /* The existing file name. */
char *fname = NULL; /* The temporary new file name. */
estream_t fp = NULL;
int removetmp = 0;
int blocksigs = 0;
if (ctrl->ephemeral_mode)
{
ephemeral_private_key_t ek;
void *blob;
size_t blobsize;
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN))
break;
if (!ek)
{
err = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
fp = es_fopenmem (0, "wbx,wipe");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open memory stream: %s\n", gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing to memory stream: %s\n",gpg_strerror (err));
goto leave;
}
if (es_fclose_snatch (fp, &blob, &blobsize) || !blob)
{
err = gpg_error_from_syserror ();
log_error ("error getting memory stream buffer: %s\n",
gpg_strerror (err));
/* Closing right away so that we don't try another snatch in
* the cleanup. */
es_fclose (fp);
fp = NULL;
goto leave;
}
fp = NULL;
/* No need to revisit the linked list because the found EK is
* not expected to change due to the other syscalls above. */
xfree (ek->keybuf);
ek->keybuf = blob;
ek->keybuflen = blobsize;
goto leave;
}
fname0 = fname_from_keygrip (grip, 0);
if (!fname0)
{
err = gpg_error_from_syserror ();
goto leave;
}
fname = fname_from_keygrip (grip, 1);
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = es_fopen (fname, "wbx,mode=-rw");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
err = nvc_write (pk, fp);
if (err)
{
log_error ("error writing '%s': %s\n", fname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
es_fclose (fp);
fp = NULL;
err = gnupg_rename_file (fname, fname0, &blocksigs);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("error renaming '%s': %s\n", fname, gpg_strerror (err));
removetmp = 1;
goto leave;
}
leave:
if (blocksigs)
gnupg_unblock_all_signals ();
if (ctrl->ephemeral_mode)
wipe_and_fclose (fp);
else
es_fclose (fp);
if (removetmp && fname)
gnupg_remove (fname);
xfree (fname);
xfree (fname0);
return err;
}
/* Callback function to try the unprotection from the passphrase query
code. */
static gpg_error_t
try_unprotect_cb (struct pin_entry_info_s *pi)
{
struct try_unprotect_arg_s *arg = pi->check_cb_arg;
ctrl_t ctrl = arg->ctrl;
size_t dummy;
gpg_error_t err;
gnupg_isotime_t now, protected_at, tmptime;
char *desc = NULL;
log_assert (!arg->unprotected_key);
arg->change_required = 0;
err = agent_unprotect (ctrl, arg->protected_key, pi->pin, protected_at,
&arg->unprotected_key, &dummy);
if (err)
return err;
if (!opt.max_passphrase_days || ctrl->in_passwd)
return 0; /* No regular passphrase change required. */
if (!*protected_at)
{
/* No protection date known - must force passphrase change. */
desc = xtrystrdup (L_("Note: This passphrase has never been changed.%0A"
"Please change it now."));
if (!desc)
return gpg_error_from_syserror ();
}
else
{
gnupg_get_isotime (now);
gnupg_copy_time (tmptime, protected_at);
err = add_days_to_isotime (tmptime, opt.max_passphrase_days);
if (err)
return err;
if (strcmp (now, tmptime) > 0 )
{
/* Passphrase "expired". */
desc = xtryasprintf
(L_("This passphrase has not been changed%%0A"
"since %.4s-%.2s-%.2s. Please change it now."),
protected_at, protected_at+4, protected_at+6);
if (!desc)
return gpg_error_from_syserror ();
}
}
if (desc)
{
/* Change required. */
if (opt.enforce_passphrase_constraints)
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"), NULL, 0);
if (!err)
arg->change_required = 1;
}
else
{
err = agent_get_confirmation (ctrl, desc,
L_("Change passphrase"),
L_("I'll change it later"), 0);
if (!err)
arg->change_required = 1;
else if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
err = 0;
}
xfree (desc);
}
return err;
}
/* Return true if the STRING has an %C or %c expando. */
static int
has_comment_expando (const char *string)
{
const char *s;
int percent = 0;
if (!string)
return 0;
for (s = string; *s; s++)
{
if (percent)
{
if (*s == 'c' || *s == 'C')
return 1;
percent = 0;
}
else if (*s == '%')
percent = 1;
}
return 0;
}
/* Modify a Key description, replacing certain special format
characters. List of currently supported replacements:
%% - Replaced by a single %
%c - Replaced by the content of COMMENT.
%C - Same as %c but put into parentheses.
%F - Replaced by an ssh style fingerprint computed from KEY.
The functions returns 0 on success or an error code. On success a
newly allocated string is stored at the address of RESULT.
*/
gpg_error_t
agent_modify_description (const char *in, const char *comment,
const gcry_sexp_t key, char **result)
{
size_t comment_length;
size_t in_len;
size_t out_len;
char *out;
size_t i;
int special, pass;
char *ssh_fpr = NULL;
char *p;
*result = NULL;
if (!comment)
comment = "";
comment_length = strlen (comment);
in_len = strlen (in);
/* First pass calculates the length, second pass does the actual
copying. */
/* FIXME: This can be simplified by using es_fopenmem. */
out = NULL;
out_len = 0;
for (pass=0; pass < 2; pass++)
{
special = 0;
for (i = 0; i < in_len; i++)
{
if (special)
{
special = 0;
switch (in[i])
{
case '%':
if (out)
*out++ = '%';
else
out_len++;
break;
case 'c': /* Comment. */
if (out)
{
memcpy (out, comment, comment_length);
out += comment_length;
}
else
out_len += comment_length;
break;
case 'C': /* Comment. */
if (!comment_length)
;
else if (out)
{
*out++ = '(';
memcpy (out, comment, comment_length);
out += comment_length;
*out++ = ')';
}
else
out_len += comment_length + 2;
break;
case 'F': /* SSH style fingerprint. */
if (!ssh_fpr && key)
ssh_get_fingerprint_string (key, opt.ssh_fingerprint_digest,
&ssh_fpr);
if (ssh_fpr)
{
if (out)
out = stpcpy (out, ssh_fpr);
else
out_len += strlen (ssh_fpr);
}
break;
default: /* Invalid special sequences are kept as they are. */
if (out)
{
*out++ = '%';
*out++ = in[i];
}
else
out_len+=2;
break;
}
}
else if (in[i] == '%')
special = 1;
else
{
if (out)
*out++ = in[i];
else
out_len++;
}
}
if (!pass)
{
*result = out = xtrymalloc (out_len + 1);
if (!out)
{
xfree (ssh_fpr);
return gpg_error_from_syserror ();
}
}
}
*out = 0;
log_assert (*result + out_len == out);
xfree (ssh_fpr);
/* The ssh prompt may sometimes end in
* "...%0A ()"
* The empty parentheses doesn't look very good. We use this hack
* here to remove them as well as the indentation spaces. */
p = *result;
i = strlen (p);
if (i > 2 && !strcmp (p + i - 2, "()"))
{
p += i - 2;
*p-- = 0;
while (p > *result && spacep (p))
*p-- = 0;
}
return 0;
}
/* Unprotect the canconical encoded S-expression key in KEYBUF. GRIP
should be the hex encoded keygrip of that key to be used with the
caching mechanism. DESC_TEXT may be set to override the default
description used for the pinentry. If LOOKUP_TTL is given this
function is used to lookup the default ttl. If R_PASSPHRASE is not
NULL, the function succeeded and the key was protected the used
passphrase (entered or from the cache) is stored there; if not NULL
will be stored. The caller needs to free the returned
passphrase. */
static gpg_error_t
unprotect (ctrl_t ctrl, const char *cache_nonce, const char *desc_text,
unsigned char **keybuf, const unsigned char *grip,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
char **r_passphrase)
{
struct pin_entry_info_s *pi;
struct try_unprotect_arg_s arg;
int rc;
unsigned char *result;
size_t resultlen;
char hexgrip[40+1];
if (r_passphrase)
*r_passphrase = NULL;
bin2hex (grip, 20, hexgrip);
/* Initially try to get it using a cache nonce. */
if (cache_nonce)
{
char *pw;
pw = agent_get_cache (ctrl, cache_nonce, CACHE_MODE_NONCE);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
}
/* First try to get it from the cache - if there is none or we can't
unprotect it, we fall back to ask the user */
if (cache_mode != CACHE_MODE_IGNORE)
{
char *pw;
retry:
pw = agent_get_cache (ctrl, hexgrip, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL, &result, &resultlen);
if (!rc)
{
if (cache_mode == CACHE_MODE_NORMAL)
agent_store_cache_hit (hexgrip);
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
else if (cache_mode == CACHE_MODE_NORMAL)
{
/* The standard use of GPG keys is to have a signing and an
encryption subkey. Commonly both use the same
passphrase. We try to help the user to enter the
passphrase only once by silently trying the last
correctly entered passphrase. Checking one additional
passphrase should be acceptable; despite the S2K
introduced delays. The assumed workflow is:
1. Read encrypted message in a MUA and thus enter a
passphrase for the encryption subkey.
2. Reply to that mail with an encrypted and signed
mail, thus entering the passphrase for the signing
subkey.
We can often avoid the passphrase entry in the second
step. We do this only in normal mode, so not to
interfere with unrelated cache entries. */
pw = agent_get_cache (ctrl, NULL, cache_mode);
if (pw)
{
rc = agent_unprotect (ctrl, *keybuf, pw, NULL,
&result, &resultlen);
if (!rc)
{
if (r_passphrase)
*r_passphrase = pw;
else
xfree (pw);
xfree (*keybuf);
*keybuf = result;
return 0;
}
xfree (pw);
}
}
/* If the pinentry is currently in use, we wait up to 60 seconds
for it to close and check the cache again. This solves a common
situation where several requests for unprotecting a key have
been made but the user is still entering the passphrase for
the first request. Because all requests to agent_askpin are
serialized they would then pop up one after the other to
request the passphrase - despite that the user has already
entered it and is then available in the cache. This
implementation is not race free but in the worst case the
user has to enter the passphrase only once more. */
if (pinentry_active_p (ctrl, 0))
{
/* Active - wait */
if (!pinentry_active_p (ctrl, 60))
{
/* We need to give the other thread a chance to actually put
it into the cache. */
gnupg_sleep (1);
goto retry;
}
/* Timeout - better call pinentry now the plain way. */
}
}
pi = gcry_calloc_secure (1, sizeof (*pi) + MAX_PASSPHRASE_LEN + 1);
if (!pi)
return gpg_error_from_syserror ();
pi->max_length = MAX_PASSPHRASE_LEN + 1;
pi->min_digits = 0; /* we want a real passphrase */
pi->max_digits = 16;
pi->max_tries = 3;
pi->check_cb = try_unprotect_cb;
arg.ctrl = ctrl;
arg.protected_key = *keybuf;
arg.unprotected_key = NULL;
arg.change_required = 0;
pi->check_cb_arg = &arg;
rc = agent_askpin (ctrl, desc_text, NULL, NULL, pi, hexgrip, cache_mode);
if (rc)
{
if ((pi->status & PINENTRY_STATUS_PASSWORD_FROM_CACHE))
{
log_error ("Clearing pinentry cache which caused error %s\n",
gpg_strerror (rc));
agent_clear_passphrase (ctrl, hexgrip, cache_mode);
}
}
else
{
log_assert (arg.unprotected_key);
if (arg.change_required)
{
/* The callback told as that the user should change their
passphrase. Present the dialog to do. */
size_t canlen, erroff;
gcry_sexp_t s_skey;
log_assert (arg.unprotected_key);
canlen = gcry_sexp_canon_len (arg.unprotected_key, 0, NULL, NULL);
rc = gcry_sexp_sscan (&s_skey, &erroff,
(char*)arg.unprotected_key, canlen);
if (rc)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
rc = agent_protect_and_store (ctrl, s_skey, NULL);
gcry_sexp_release (s_skey);
if (rc)
{
log_error ("changing the passphrase failed: %s\n",
gpg_strerror (rc));
wipememory (arg.unprotected_key, canlen);
xfree (arg.unprotected_key);
xfree (pi);
return rc;
}
}
else
{
/* Passphrase is fine. */
agent_put_cache (ctrl, hexgrip, cache_mode, pi->pin,
lookup_ttl? lookup_ttl (hexgrip) : 0);
agent_store_cache_hit (hexgrip);
if (r_passphrase && *pi->pin)
*r_passphrase = xtrystrdup (pi->pin);
}
xfree (*keybuf);
*keybuf = arg.unprotected_key;
}
xfree (pi);
return rc;
}
/* Read the key identified by GRIP from the private key directory and
* return it as an gcrypt S-expression object in RESULT. If R_KEYMETA
* is not NULL and the extended key format is used, the meta data
* items are stored there. However the "Key:" item is removed from
* it. If R_ORIG_KEY_VALUE is non-NULL and the Key item was removed,
* its original value is stored at that R_ORIG_KEY_VALUE and the
* caller must free it. On failure returns an error code and stores
* NULL at RESULT and R_KEYMETA. */
static gpg_error_t
read_key_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta, char **r_orig_key_value)
{
gpg_error_t err;
char *fname;
estream_t fp = NULL;
unsigned char *buf = NULL;
size_t buflen, erroff;
nvc_t pk = NULL;
char first;
size_t keybuflen;
*result = NULL;
if (r_keymeta)
*r_keymeta = NULL;
if (r_orig_key_value)
*r_orig_key_value = NULL;
fname = (ctrl->ephemeral_mode
? xtrystrdup ("[ephemeral key store]")
: fname_from_keygrip (grip, 0));
if (!fname)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (ctrl->ephemeral_mode)
{
ephemeral_private_key_t ek;
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN)
&& ek->keybuf && ek->keybuflen)
break;
if (!ek || !ek->keybuf || !ek->keybuflen)
{
err = gpg_error (GPG_ERR_ENOENT);
goto leave;
}
keybuflen = ek->keybuflen;
fp = es_fopenmem_init (0, "rb", ek->keybuf, ek->keybuflen);
}
else
{
keybuflen = 0; /* Indicates that this is not ephemeral mode. */
fp = es_fopen (fname, "rb");
}
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
if (es_fread (&first, 1, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading first byte from '%s': %s\n",
fname, gpg_strerror (err));
goto leave;
}
if (es_fseek (fp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking in '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
if (first != '(')
{
/* Key is in extended format. */
int line;
err = nvc_parse_private_key (&pk, &line, fp);
if (err)
log_error ("error parsing '%s' line %d: %s\n",
fname, line, gpg_strerror (err));
else
{
err = nvc_get_private_key (pk, result);
if (err)
log_error ("error getting private key from '%s': %s\n",
fname, gpg_strerror (err));
else
{
if (r_orig_key_value)
{
const char *s = nvc_get_string (pk, "Key:");
if (s)
{
*r_orig_key_value = xtrystrdup (s);
if (!*r_orig_key_value)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
}
nvc_delete_named (pk, "Key:");
}
}
goto leave; /* Ready. */
}
if (keybuflen)
buflen = keybuflen;
else
{
struct stat st;
if (fstat (es_fileno (fp), &st))
{
err = gpg_error_from_syserror ();
log_error ("can't stat '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
buflen = st.st_size;
}
buf = xtrymalloc (buflen+1);
if (!buf)
{
err = gpg_error_from_syserror ();
log_error ("error allocating %zu bytes for '%s': %s\n",
buflen, fname, gpg_strerror (err));
goto leave;
}
if (es_fread (buf, buflen, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error ("error reading %zu bytes from '%s': %s\n",
buflen, fname, gpg_strerror (err));
goto leave;
}
/* Convert the file into a gcrypt S-expression object. */
{
gcry_sexp_t s_skey;
err = gcry_sexp_sscan (&s_skey, &erroff, (char*)buf, buflen);
if (err)
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
else
*result = s_skey;
}
leave:
if (!err && r_keymeta)
*r_keymeta = pk;
else
nvc_release (pk);
if (ctrl->ephemeral_mode)
wipe_and_fclose (fp);
else
es_fclose (fp);
xfree (fname);
return err;
}
/* Remove the key identified by GRIP from the private key directory. */
static gpg_error_t
remove_key_file (const unsigned char *grip)
{
gpg_error_t err = 0;
char *fname;
fname = fname_from_keygrip (grip, 0);
if (!fname)
{
return gpg_error_from_syserror ();
}
if (gnupg_remove (fname))
err = gpg_error_from_syserror ();
xfree (fname);
return err;
}
/*
* Prompt a user the card insertion, when it's not available yet.
*/
static gpg_error_t
prompt_for_card (ctrl_t ctrl, const unsigned char *grip,
nvc_t keymeta, const unsigned char *shadow_info)
{
char *serialno;
char *desc;
char *want_sn = NULL;
int len;
gpg_error_t err;
char hexgrip[41];
char *comment_buffer = NULL;
const char *comment = NULL;
int refuse_prompt = 0;
bin2hex (grip, 20, hexgrip);
if (keymeta)
{
const char *p;
if ((p = nvc_get_string (keymeta, "Prompt:")) && !strcmp (p, "no"))
refuse_prompt = 1;
if ((p = nvc_get_string (keymeta, "Label:")))
{
if (strchr (p, '\n')
&& (comment_buffer = linefeed_to_percent0A (p)))
comment = comment_buffer;
else
comment = p;
}
}
err = parse_shadow_info (shadow_info, &want_sn, NULL, NULL);
if (err)
return err;
len = want_sn? strlen (want_sn) : 0;
if (len == 32 && !strncmp (want_sn, "D27600012401", 12))
{
/* This is an OpenPGP card - reformat */
if (!strncmp (want_sn+16, "0006", 4))
{
/* This is a Yubikey. Print the s/n as it would be printed
* on Yubikey 5. Example: D2760001240100000006120808620000
* mmmm^^^^^^^^ */
unsigned long sn;
sn = atoi_4 (want_sn+20) * 10000;
sn += atoi_4 (want_sn+24);
snprintf (want_sn, 32, "%lu %03lu %03lu",
(sn/1000000ul), (sn/1000ul % 1000ul), (sn % 1000ul));
}
else /* Default is the Zeitcontrol card print format. */
{
memmove (want_sn, want_sn+16, 4);
want_sn[4] = ' ';
memmove (want_sn+5, want_sn+20, 8);
want_sn[13] = 0;
}
}
else if (len == 20 && want_sn[19] == '0')
{
/* We assume that a 20 byte serial number is a standard one
* which has the property to have a zero in the last nibble (Due
* to BCD representation). We don't display this '0' because it
* may confuse the user. */
want_sn[19] = 0;
}
for (;;)
{
/* Scan device(s), and check if key for GRIP is available. */
err = agent_card_serialno (ctrl, &serialno, NULL);
if (!err)
{
struct card_key_info_s *keyinfo;
xfree (serialno);
err = agent_card_keyinfo (ctrl, hexgrip, 0, &keyinfo);
if (!err)
{
/* Key for GRIP found, use it. */
agent_card_free_keyinfo (keyinfo);
break;
}
}
/* Card is not available. Prompt the insertion. */
if (refuse_prompt)
{
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
}
if (asprintf (&desc,
"%s:%%0A%%0A"
" %s%%0A"
" %s",
L_("Please insert the card with serial number"),
want_sn ? want_sn : "",
comment? comment:"") < 0)
err = out_of_core ();
else
{
err = agent_get_confirmation (ctrl, desc, NULL, NULL, 0);
if (ctrl->pinentry_mode == PINENTRY_MODE_LOOPBACK &&
gpg_err_code (err) == GPG_ERR_NO_PIN_ENTRY)
err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
xfree (desc);
}
if (err)
break;
}
xfree (want_sn);
gcry_free (comment_buffer);
return err;
}
/* Return the secret key as an S-Exp in RESULT after locating it using
the GRIP. Caller should set GRIP=NULL, when a key in a file is
intended to be used for cryptographic operation. In this case,
CTRL->keygrip is used to locate the file, and it may ask a user for
confirmation. If the operation shall be diverted to a token, an
allocated S-expression with the shadow_info part from the file is
stored at SHADOW_INFO; if not NULL will be stored at SHADOW_INFO.
CACHE_MODE defines now the cache shall be used. DESC_TEXT may be
set to present a custom description for the pinentry. LOOKUP_TTL
is an optional function to convey a TTL to the cache manager; we do
not simply pass the TTL value because the value is only needed if
an unprotect action was needed and looking up the TTL may have some
overhead (e.g. scanning the sshcontrol file). If a CACHE_NONCE is
given that cache item is first tried to get a passphrase. If
R_PASSPHRASE is not NULL, the function succeeded and the key was
protected the used passphrase (entered or from the cache) is stored
there; if not NULL will be stored. The caller needs to free the
returned passphrase. */
gpg_error_t
agent_key_from_file (ctrl_t ctrl, const char *cache_nonce,
const char *desc_text,
const unsigned char *grip, unsigned char **shadow_info,
cache_mode_t cache_mode, lookup_ttl_t lookup_ttl,
gcry_sexp_t *result, char **r_passphrase,
time_t *r_timestamp)
{
gpg_error_t err;
unsigned char *buf;
size_t len, erroff;
gcry_sexp_t s_skey;
nvc_t keymeta = NULL;
char *desc_text_buffer = NULL; /* Used in case we extend DESC_TEXT. */
*result = NULL;
if (shadow_info)
*shadow_info = NULL;
if (r_passphrase)
*r_passphrase = NULL;
if (r_timestamp)
*r_timestamp = (time_t)(-1);
if (!grip && !ctrl->have_keygrip)
return gpg_error (GPG_ERR_NO_SECKEY);
err = read_key_file (ctrl, grip? grip : ctrl->keygrip,
&s_skey, &keymeta, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
else
log_error ("findkey: error reading key file: %s\n",
gpg_strerror (err));
return err;
}
/* For use with the protection functions we also need the key as an
canonical encoded S-expression in a buffer. Create this buffer
now. */
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
{
nvc_release (keymeta);
xfree (desc_text_buffer);
return err;
}
if (r_timestamp && keymeta)
{
const char *created = nvc_get_string (keymeta, "Created:");
if (created)
*r_timestamp = isotime2epoch (created);
}
if (!grip && keymeta)
{
const char *ask_confirmation = nvc_get_string (keymeta, "Confirm:");
if (ask_confirmation
&& ((!strcmp (ask_confirmation, "restricted") && ctrl->restricted)
|| !strcmp (ask_confirmation, "yes")))
{
char hexgrip[40+4+1];
char *prompt;
char *comment_buffer = NULL;
const char *comment = NULL;
bin2hex (ctrl->keygrip, 20, hexgrip);
if ((comment = nvc_get_string (keymeta, "Label:")))
{
if (strchr (comment, '\n')
&& (comment_buffer = linefeed_to_percent0A (comment)))
comment = comment_buffer;
}
prompt = xtryasprintf (L_("Requested the use of key%%0A"
" %s%%0A"
" %s%%0A"
"Do you want to allow this?"),
hexgrip, comment? comment:"");
gcry_free (comment_buffer);
err = agent_get_confirmation (ctrl, prompt,
L_("Allow"), L_("Deny"), 0);
xfree (prompt);
if (err)
return err;
}
}
switch (agent_private_key_type (buf))
{
case PRIVATE_KEY_CLEAR:
break; /* no unprotection needed */
case PRIVATE_KEY_OPENPGP_NONE:
{
unsigned char *buf_new;
size_t buf_newlen;
err = agent_unprotect (ctrl, buf, "", NULL, &buf_new, &buf_newlen);
if (err)
log_error ("failed to convert unprotected openpgp key: %s\n",
gpg_strerror (err));
else
{
xfree (buf);
buf = buf_new;
}
}
break;
case PRIVATE_KEY_PROTECTED:
{
char *desc_text_final;
char *comment_buffer = NULL;
const char *comment = NULL;
/* Note, that we will take the comment as a C string for
* display purposes; i.e. all stuff beyond a Nul character is
* ignored. If a "Label" entry is available in the meta data
* this is used instead of the s-expression comment. */
if (keymeta && (comment = nvc_get_string (keymeta, "Label:")))
{
if (strchr (comment, '\n')
&& (comment_buffer = linefeed_to_percent0A (comment)))
comment = comment_buffer;
/* In case DESC_TEXT has no escape pattern for a comment
* we append one. */
if (desc_text && !has_comment_expando (desc_text))
{
desc_text_buffer = strconcat (desc_text, "%0A%C", NULL);
if (desc_text_buffer)
desc_text = desc_text_buffer;
}
}
else
{
gcry_sexp_t comment_sexp;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment_buffer = gcry_sexp_nth_string (comment_sexp, 1);
gcry_sexp_release (comment_sexp);
comment = comment_buffer;
}
desc_text_final = NULL;
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
gcry_free (comment_buffer);
if (!err)
{
err = unprotect (ctrl, cache_nonce, desc_text_final, &buf,
grip? grip : ctrl->keygrip,
cache_mode, lookup_ttl, r_passphrase);
if (err)
log_error ("failed to unprotect the secret key: %s\n",
gpg_strerror (err));
}
xfree (desc_text_final);
}
break;
case PRIVATE_KEY_SHADOWED:
if (shadow_info)
{
const unsigned char *s;
unsigned char *shadow_type;
size_t n;
err = agent_get_shadow_info_type (buf, &s, &shadow_type);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL,NULL);
log_assert (n);
*shadow_info = xtrymalloc (n);
if (!*shadow_info)
{
err = out_of_core ();
goto shadow_error;
}
else
{
memcpy (*shadow_info, s, n);
/*
- * When it's a key on card (not on tpm2), maks sure
+ * When it's a key on card (not on tpm2), make sure
* it's available.
*/
if (strcmp (shadow_type, "t1-v1") == 0 && !grip)
err = prompt_for_card (ctrl, ctrl->keygrip,
keymeta, *shadow_info);
}
}
else
shadow_error:
log_error ("get_shadow_info failed: %s\n", gpg_strerror (err));
xfree (shadow_type);
}
else
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
gcry_sexp_release (s_skey);
s_skey = NULL;
if (err)
{
xfree (buf);
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
nvc_release (keymeta);
xfree (desc_text_buffer);
return err;
}
err = sexp_sscan_private_key (result, &erroff, buf);
xfree (buf);
nvc_release (keymeta);
xfree (desc_text_buffer);
if (err)
{
log_error ("failed to build S-Exp (off=%u): %s\n",
(unsigned int)erroff, gpg_strerror (err));
if (r_passphrase)
{
xfree (*r_passphrase);
*r_passphrase = NULL;
}
}
return err;
}
/* This function returns GPG_ERR_TRUE if S_SKEY represents a shadowed
* key. 0 is return for other key types. Any other error may occur
* if S_SKEY is not valid. */
static gpg_error_t
is_shadowed_key (gcry_sexp_t s_skey)
{
gpg_error_t err;
unsigned char *buf;
size_t buflen;
err = make_canon_sexp (s_skey, &buf, &buflen);
if (err)
return err;
if (agent_private_key_type (buf) == PRIVATE_KEY_SHADOWED)
err = gpg_error (GPG_ERR_TRUE);
wipememory (buf, buflen);
xfree (buf);
return err;
}
/* Return the key for the keygrip GRIP. The result is stored at
RESULT. This function extracts the key from the private key
database and returns it as an S-expression object as it is. On
failure an error code is returned and NULL stored at RESULT. */
gpg_error_t
agent_raw_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, nvc_t *r_keymeta)
{
gpg_error_t err;
gcry_sexp_t s_skey;
(void)ctrl;
*result = NULL;
err = read_key_file (ctrl, grip, &s_skey, r_keymeta, NULL);
if (!err)
*result = s_skey;
return err;
}
/* Return the public key for the keygrip GRIP. The result is stored
at RESULT. This function extracts the public key from the private
key database. On failure an error code is returned and NULL stored
at RESULT. If R_SSHORDER is not NULL the ordinal from the
Use-for-ssh attribute is stored at that address. */
static gpg_error_t
public_key_from_file (ctrl_t ctrl, const unsigned char *grip,
gcry_sexp_t *result, int for_ssh, int *r_sshorder)
{
gpg_error_t err;
int i, idx;
gcry_sexp_t s_skey;
nvc_t keymeta = NULL;
const char *algoname, *elems;
int npkey;
gcry_mpi_t array[10];
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
gcry_sexp_t uri_sexp, comment_sexp;
const char *uri, *comment;
size_t uri_length, comment_length;
int uri_intlen, comment_intlen;
membuf_t format_mb;
char *format;
void *args[2+7+2+2+1]; /* Size is 2 + max. # of elements + 2 for uri + 2
for comment + end-of-list. */
int argidx;
gcry_sexp_t list = NULL;
const char *s;
(void)ctrl;
*result = NULL;
if (r_sshorder)
*r_sshorder = 0;
err = read_key_file (ctrl, grip, &s_skey, for_ssh? &keymeta : NULL, NULL);
if (err)
return err;
if (for_ssh)
{
/* Use-for-ssh: yes */
int is_ssh = 0;
if (keymeta == NULL)
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
is_ssh = nvc_get_boolean (keymeta, "Use-for-ssh:");
nvc_release (keymeta);
keymeta = NULL;
if (!is_ssh)
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
if (r_sshorder)
*r_sshorder = is_ssh;
}
for (i=0; i < DIM (array); i++)
array[i] = NULL;
err = extract_private_key (s_skey, 0, &algoname, &npkey, NULL, &elems,
array, DIM (array), &curve, &flags);
if (err)
{
gcry_sexp_release (s_skey);
return err;
}
uri = NULL;
uri_length = 0;
uri_sexp = gcry_sexp_find_token (s_skey, "uri", 0);
if (uri_sexp)
uri = gcry_sexp_nth_data (uri_sexp, 1, &uri_length);
comment = NULL;
comment_length = 0;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_data (comment_sexp, 1, &comment_length);
gcry_sexp_release (s_skey);
s_skey = NULL;
log_assert (sizeof (size_t) <= sizeof (void*));
init_membuf (&format_mb, 256);
argidx = 0;
put_membuf_printf (&format_mb, "(public-key(%s%%S%%S", algoname);
args[argidx++] = &curve;
args[argidx++] = &flags;
for (idx=0, s=elems; idx < npkey; idx++)
{
put_membuf_printf (&format_mb, "(%c %%m)", *s++);
log_assert (argidx < DIM (args));
args[argidx++] = &array[idx];
}
put_membuf_str (&format_mb, ")");
if (uri)
{
put_membuf_str (&format_mb, "(uri %b)");
log_assert (argidx+1 < DIM (args));
uri_intlen = (int)uri_length;
args[argidx++] = (void *)&uri_intlen;
args[argidx++] = (void *)&uri;
}
if (comment)
{
put_membuf_str (&format_mb, "(comment %b)");
log_assert (argidx+1 < DIM (args));
comment_intlen = (int)comment_length;
args[argidx++] = (void *)&comment_intlen;
args[argidx++] = (void *)&comment;
}
put_membuf (&format_mb, ")", 2);
log_assert (argidx < DIM (args));
args[argidx] = NULL;
format = get_membuf (&format_mb, NULL);
if (!format)
{
err = gpg_error_from_syserror ();
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
return err;
}
err = gcry_sexp_build_array (&list, NULL, format, args);
xfree (format);
for (i=0; array[i]; i++)
gcry_mpi_release (array[i]);
gcry_sexp_release (curve);
gcry_sexp_release (flags);
gcry_sexp_release (uri_sexp);
gcry_sexp_release (comment_sexp);
if (!err)
*result = list;
return err;
}
gpg_error_t
agent_public_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result)
{
return public_key_from_file (ctrl, grip, result, 0, NULL);
}
gpg_error_t
agent_ssh_key_from_file (ctrl_t ctrl,
const unsigned char *grip,
gcry_sexp_t *result, int *r_order)
{
return public_key_from_file (ctrl, grip, result, 1, r_order);
}
/* Check whether the secret key identified by GRIP is available.
Returns 0 is the key is available. */
int
agent_key_available (ctrl_t ctrl, const unsigned char *grip)
{
int result;
char *fname;
char hexgrip[40+4+1];
ephemeral_private_key_t ek;
if (ctrl && ctrl->ephemeral_mode)
{
for (ek = ctrl->ephemeral_keys; ek; ek = ek->next)
if (!memcmp (ek->grip, grip, KEYGRIP_LEN)
&& ek->keybuf && ek->keybuflen)
return 0;
return -1;
}
bin2hex (grip, 20, hexgrip);
strcpy (hexgrip+40, ".key");
fname = make_filename (gnupg_homedir (), GNUPG_PRIVATE_KEYS_DIR,
hexgrip, NULL);
result = !gnupg_access (fname, R_OK)? 0 : -1;
xfree (fname);
return result;
}
/* Return the information about the secret key specified by the binary
keygrip GRIP. If the key is a shadowed one the shadow information
will be stored at the address R_SHADOW_INFO as an allocated
S-expression. */
gpg_error_t
agent_key_info_from_file (ctrl_t ctrl, const unsigned char *grip,
int *r_keytype, unsigned char **r_shadow_info,
unsigned char **r_shadow_info_type)
{
gpg_error_t err;
unsigned char *buf;
size_t len;
int keytype;
(void)ctrl;
if (r_keytype)
*r_keytype = PRIVATE_KEY_UNKNOWN;
if (r_shadow_info)
*r_shadow_info = NULL;
{
gcry_sexp_t sexp;
err = read_key_file (ctrl, grip, &sexp, NULL, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT)
return gpg_error (GPG_ERR_NOT_FOUND);
else
return err;
}
err = make_canon_sexp (sexp, &buf, &len);
gcry_sexp_release (sexp);
if (err)
return err;
}
keytype = agent_private_key_type (buf);
switch (keytype)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
break;
case PRIVATE_KEY_PROTECTED:
/* If we ever require it we could retrieve the comment fields
from such a key. */
break;
case PRIVATE_KEY_SHADOWED:
if (r_shadow_info)
{
const unsigned char *s;
size_t n;
err = agent_get_shadow_info_type (buf, &s, r_shadow_info_type);
if (!err)
{
n = gcry_sexp_canon_len (s, 0, NULL, NULL);
log_assert (n);
*r_shadow_info = xtrymalloc (n);
if (!*r_shadow_info)
err = gpg_error_from_syserror ();
else
memcpy (*r_shadow_info, s, n);
}
}
break;
default:
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
if (!err && r_keytype)
*r_keytype = keytype;
xfree (buf);
return err;
}
/* Delete the key with GRIP from the disk after having asked for
* confirmation using DESC_TEXT. If FORCE is set the function won't
* require a confirmation via Pinentry or warns if the key is also
* used by ssh. If ONLY_STUBS is set only stub keys (references to
* smartcards) will be affected.
*
* Common error codes are:
* GPG_ERR_NO_SECKEY
* GPG_ERR_KEY_ON_CARD
* GPG_ERR_NOT_CONFIRMED
* GPG_ERR_FORBIDDEN - Not a stub key and ONLY_STUBS requested.
*/
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *desc_text,
const unsigned char *grip, int force, int only_stubs)
{
gpg_error_t err;
gcry_sexp_t s_skey = NULL;
unsigned char *buf = NULL;
size_t len;
char *desc_text_final = NULL;
char *comment = NULL;
ssh_control_file_t cf = NULL;
char hexgrip[40+4+1];
char *default_desc = NULL;
int key_type;
if (ctrl->ephemeral_mode)
{
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
err = read_key_file (ctrl, grip, &s_skey, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_SECKEY);
if (err)
goto leave;
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
goto leave;
key_type = agent_private_key_type (buf);
if (only_stubs && key_type != PRIVATE_KEY_SHADOWED)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
switch (key_type)
{
case PRIVATE_KEY_CLEAR:
case PRIVATE_KEY_OPENPGP_NONE:
case PRIVATE_KEY_PROTECTED:
bin2hex (grip, 20, hexgrip);
if (!force)
{
if (!desc_text)
{
default_desc = xtryasprintf
(L_("Do you really want to delete the key identified by keygrip%%0A"
" %s%%0A %%C%%0A?"), hexgrip);
desc_text = default_desc;
}
/* Note, that we will take the comment as a C string for
display purposes; i.e. all stuff beyond a Nul character is
ignored. */
{
gcry_sexp_t comment_sexp;
comment_sexp = gcry_sexp_find_token (s_skey, "comment", 0);
if (comment_sexp)
comment = gcry_sexp_nth_string (comment_sexp, 1);
gcry_sexp_release (comment_sexp);
}
if (desc_text)
err = agent_modify_description (desc_text, comment, s_skey,
&desc_text_final);
if (err)
goto leave;
err = agent_get_confirmation (ctrl, desc_text_final,
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
cf = ssh_open_control_file ();
if (cf)
{
if (!ssh_search_control_file (cf, hexgrip, NULL, NULL, NULL))
{
err = agent_get_confirmation
(ctrl,
L_("Warning: This key is also listed for use with SSH!\n"
"Deleting the key might remove your ability to "
"access remote machines."),
L_("Delete key"), L_("No"), 0);
if (err)
goto leave;
}
}
}
err = remove_key_file (grip);
break;
case PRIVATE_KEY_SHADOWED:
err = remove_key_file (grip);
break;
default:
log_error ("invalid private key format\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
break;
}
leave:
ssh_close_control_file (cf);
gcry_free (comment);
xfree (desc_text_final);
xfree (default_desc);
xfree (buf);
gcry_sexp_release (s_skey);
return err;
}
/* Write an S-expression formatted shadow key to our key storage.
Shadow key is created by an S-expression public key in PKBUF and
card's SERIALNO and the IDSTRING. With FORCE passed as true an
existing key with the given GRIP will get overwritten. */
gpg_error_t
agent_write_shadow_key (ctrl_t ctrl, const unsigned char *grip,
const char *serialno, const char *keyid,
const unsigned char *pkbuf, int force,
const char *dispserialno)
{
gpg_error_t err;
unsigned char *shadow_info;
unsigned char *shdkey;
size_t len;
/* Just in case some caller did not parse the stuff correctly, skip
* leading spaces. */
while (spacep (serialno))
serialno++;
while (spacep (keyid))
keyid++;
shadow_info = make_shadow_info (serialno, keyid);
if (!shadow_info)
return gpg_error_from_syserror ();
err = agent_shadow_key (pkbuf, shadow_info, &shdkey);
xfree (shadow_info);
if (err)
{
log_error ("shadowing the key failed: %s\n", gpg_strerror (err));
return err;
}
len = gcry_sexp_canon_len (shdkey, 0, NULL, NULL);
err = agent_write_private_key (ctrl, grip, shdkey, len, force,
serialno, keyid, dispserialno, 0);
xfree (shdkey);
if (err)
log_error ("error writing key: %s\n", gpg_strerror (err));
return err;
}
diff --git a/agent/gpg-agent.c b/agent/gpg-agent.c
index 3c71ba65d..3e1fb5d5b 100644
--- a/agent/gpg-agent.c
+++ b/agent/gpg-agent.c
@@ -1,3515 +1,3515 @@
/* gpg-agent.c - The GnuPG Agent
* Copyright (C) 2000-2020 Free Software Foundation, Inc.
* Copyright (C) 2000-2019 Werner Koch
* Copyright (C) 2015-2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <aclapi.h>
# include <sddl.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/socket.h>
# include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "agent.h"
#include <assuan.h> /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "../common/asshelp.h"
#include "../common/comopt.h"
#include "../common/init.h"
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
aUseStandardSocketP,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugQuickRandom,
oDebugPinentry,
oNoOptions,
oHomedir,
oNoDetach,
oGrab,
oNoGrab,
oLogFile,
oServer,
oDaemon,
oSupervised,
oBatch,
oPinentryProgram,
oPinentryTouchFile,
oPinentryInvisibleChar,
oPinentryTimeout,
oPinentryFormattedPassphrase,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oScdaemonProgram,
oTpm2daemonProgram,
oDefCacheTTL,
oDefCacheTTLSSH,
oMaxCacheTTL,
oMaxCacheTTLSSH,
oEnforcePassphraseConstraints,
oMinPassphraseLen,
oMinPassphraseNonalpha,
oCheckPassphrasePattern,
oCheckSymPassphrasePattern,
oMaxPassphraseDays,
oEnablePassphraseHistory,
oStealSocket,
oUseStandardSocket,
oNoUseStandardSocket,
oExtraSocket,
oBrowserSocket,
oFakedSystemTime,
oIgnoreCacheForSigning,
oAllowMarkTrusted,
oNoAllowMarkTrusted,
oNoUserTrustlist,
oSysTrustlistName,
oAllowPresetPassphrase,
oAllowLoopbackPinentry,
oNoAllowLoopbackPinentry,
oNoAllowExternalCache,
oAllowEmacsPinentry,
oKeepTTY,
oKeepDISPLAY,
oSSHSupport,
oSSHFingerprintDigest,
oPuttySupport,
oWin32OpenSSHSupport,
oDisableScdaemon,
oDisableCheckOwnSocket,
oS2KCount,
oS2KCalibration,
oAutoExpandSecmem,
oListenBacklog,
oInactivityTimeout,
oWriteEnvFile,
oNoop
};
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aUseStandardSocketP, "use-standard-socket-p", "@"),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_s_n (oSupervised, "supervised", "@"),
#endif
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_n (oStealSocket, "steal-socket", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_s_i (oInactivityTimeout, "inactivity-timeout", "@"),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oDebugPinentry, "debug-pinentry", "@"),
ARGPARSE_s_s (oLogFile, "log-file",
/* */ N_("|FILE|write server mode logs to FILE")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_n (oDisableScdaemon, "disable-scdaemon",
/* */ N_("do not use the SCdaemon") ),
ARGPARSE_s_s (oScdaemonProgram, "scdaemon-program",
/* */ N_("|PGM|use PGM as the SCdaemon program") ),
ARGPARSE_s_s (oTpm2daemonProgram, "tpm2daemon-program",
/* */ N_("|PGM|use PGM as the tpm2daemon program") ),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oExtraSocket, "extra-socket",
/* */ N_("|NAME|accept some commands via NAME")),
ARGPARSE_s_s (oBrowserSocket, "browser-socket", "@"),
ARGPARSE_s_n (oKeepTTY, "keep-tty",
/* */ N_("ignore requests to change the TTY")),
ARGPARSE_s_n (oKeepDISPLAY, "keep-display",
/* */ N_("ignore requests to change the X display")),
ARGPARSE_s_n (oSSHSupport, "enable-ssh-support", N_("enable ssh support")),
ARGPARSE_s_s (oSSHFingerprintDigest, "ssh-fingerprint-digest",
N_("|ALGO|use ALGO to show ssh fingerprints")),
ARGPARSE_s_n (oPuttySupport, "enable-putty-support",
#ifdef HAVE_W32_SYSTEM
/* */ N_("enable putty support")
#else
/* */ "@"
#endif
),
ARGPARSE_o_s (oWin32OpenSSHSupport, "enable-win32-openssh-support",
#ifdef HAVE_W32_SYSTEM
/* */ N_("enable Win32-OpenSSH support")
#else
/* */ "@"
#endif
),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_op_u (oAutoExpandSecmem, "auto-expand-secmem", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_header ("Security", N_("Options controlling the security")),
ARGPARSE_s_u (oDefCacheTTL, "default-cache-ttl",
N_("|N|expire cached PINs after N seconds")),
ARGPARSE_s_u (oDefCacheTTLSSH, "default-cache-ttl-ssh",
/* */ N_("|N|expire SSH keys after N seconds")),
ARGPARSE_s_u (oMaxCacheTTL, "max-cache-ttl",
/* */ N_("|N|set maximum PIN cache lifetime to N seconds")),
ARGPARSE_s_u (oMaxCacheTTLSSH, "max-cache-ttl-ssh",
/* */ N_("|N|set maximum SSH key lifetime to N seconds")),
ARGPARSE_s_n (oIgnoreCacheForSigning, "ignore-cache-for-signing",
/* */ N_("do not use the PIN cache when signing")),
ARGPARSE_s_n (oNoAllowExternalCache, "no-allow-external-cache",
/* */ N_("disallow the use of an external password cache")),
ARGPARSE_s_n (oNoAllowMarkTrusted, "no-allow-mark-trusted",
/* */ N_("disallow clients to mark keys as \"trusted\"")),
ARGPARSE_s_n (oAllowMarkTrusted, "allow-mark-trusted", "@"),
ARGPARSE_s_n (oNoUserTrustlist, "no-user-trustlist", "@"),
ARGPARSE_s_s (oSysTrustlistName, "sys-trustlist-name", "@"),
ARGPARSE_s_n (oAllowPresetPassphrase, "allow-preset-passphrase",
/* */ N_("allow presetting passphrase")),
ARGPARSE_s_u (oS2KCount, "s2k-count", "@"),
ARGPARSE_s_u (oS2KCalibration, "s2k-calibration", "@"),
ARGPARSE_header ("Passphrase policy",
N_("Options enforcing a passphrase policy")),
ARGPARSE_s_n (oEnforcePassphraseConstraints, "enforce-passphrase-constraints",
N_("do not allow bypassing the passphrase policy")),
ARGPARSE_s_u (oMinPassphraseLen, "min-passphrase-len",
N_("|N|set minimal required length for new passphrases to N")),
ARGPARSE_s_u (oMinPassphraseNonalpha, "min-passphrase-nonalpha",
N_("|N|require at least N non-alpha"
" characters for a new passphrase")),
ARGPARSE_s_s (oCheckPassphrasePattern, "check-passphrase-pattern",
N_("|FILE|check new passphrases against pattern in FILE")),
ARGPARSE_s_s (oCheckSymPassphrasePattern, "check-sym-passphrase-pattern",
"@"),
ARGPARSE_s_u (oMaxPassphraseDays, "max-passphrase-days",
N_("|N|expire the passphrase after N days")),
ARGPARSE_s_n (oEnablePassphraseHistory, "enable-passphrase-history",
N_("do not allow the reuse of old passphrases")),
ARGPARSE_header ("Pinentry", N_("Options controlling the PIN-Entry")),
ARGPARSE_s_n (oBatch, "batch", N_("never use the PIN-entry")),
ARGPARSE_s_n (oNoAllowLoopbackPinentry, "no-allow-loopback-pinentry",
N_("disallow caller to override the pinentry")),
ARGPARSE_s_n (oAllowLoopbackPinentry, "allow-loopback-pinentry", "@"),
ARGPARSE_s_n (oGrab, "grab", N_("let PIN-Entry grab keyboard and mouse")),
ARGPARSE_s_n (oNoGrab, "no-grab", "@"),
ARGPARSE_s_s (oPinentryProgram, "pinentry-program",
N_("|PGM|use PGM as the PIN-Entry program")),
ARGPARSE_s_s (oPinentryTouchFile, "pinentry-touch-file", "@"),
ARGPARSE_s_s (oPinentryInvisibleChar, "pinentry-invisible-char", "@"),
ARGPARSE_s_u (oPinentryTimeout, "pinentry-timeout",
N_("|N|set the Pinentry timeout to N seconds")),
ARGPARSE_s_n (oPinentryFormattedPassphrase, "pinentry-formatted-passphrase",
"@"),
ARGPARSE_s_n (oAllowEmacsPinentry, "allow-emacs-pinentry",
N_("allow passphrase to be prompted through Emacs")),
/* Dummy options for backward compatibility. */
ARGPARSE_o_s (oWriteEnvFile, "write-env-file", "@"),
ARGPARSE_s_n (oUseStandardSocket, "use-standard-socket", "@"),
ARGPARSE_s_n (oNoUseStandardSocket, "no-use-standard-socket", "@"),
/* Dummy options. */
ARGPARSE_s_n (oNoop, "disable-extended-key-format", "@"),
ARGPARSE_s_n (oNoop, "enable-extended-key-format", "@"),
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
#define DEFAULT_CACHE_TTL (10*60) /* 10 minutes */
#define DEFAULT_CACHE_TTL_SSH (30*60) /* 30 minutes */
#define MAX_CACHE_TTL (120*60) /* 2 hours */
#define MAX_CACHE_TTL_SSH (120*60) /* 2 hours */
#define MIN_PASSPHRASE_LEN (8)
#define MIN_PASSPHRASE_NONALPHA (1)
#define MAX_PASSPHRASE_DAYS (0)
/* CHECK_OWN_SOCKET_INTERVAL defines how often we check our own socket
* in standard socket mode. If that value is 0 we don't check at all.
* Values is in seconds. */
#define CHECK_OWN_SOCKET_INTERVAL (60)
/* CHECK_PROBLEMS_INTERVAL defines how often we check the existence of
* parent process and homedir. Value is in seconds. */
#define CHECK_PROBLEMS_INTERVAL (4)
/* Flag indicating that the ssh-agent subsystem has been enabled. */
static int ssh_support;
#ifdef HAVE_W32_SYSTEM
/* Flag indicating that support for Putty has been enabled. */
static int putty_support;
/* A magic value used with WM_COPYDATA. */
#define PUTTY_IPC_MAGIC 0x804e50ba
/* To avoid surprises we limit the size of the mapped IPC file to this
value. Putty currently (0.62) uses 8k, thus 16k should be enough
for the foreseeable future. */
#define PUTTY_IPC_MAXLEN 16384
/* Path to the pipe, which handles requests from Win32-OpenSSH. */
static const char *win32_openssh_support;
#define W32_DEFAULT_AGENT_PIPE_NAME "\\\\.\\pipe\\openssh-ssh-agent"
#endif /*HAVE_W32_SYSTEM*/
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Flags to indicate that check_own_socket shall not be called. */
static int disable_check_own_socket;
/* Flag indicating that we are in supervised mode. */
static int is_supervised;
/* Flag indicating to start the daemon even if one already runs. */
static int steal_socket;
/* Flag to monitor problems. */
static int problem_detected;
#define AGENT_PROBLEM_SOCKET_TAKEOVER (1 << 0)
#define AGENT_PROBLEM_PARENT_HAS_GONE (1 << 1)
#define AGENT_PROBLEM_HOMEDIR_REMOVED (1 << 2)
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Name of the communication socket used for native gpg-agent
requests. The second variable is either NULL or a malloced string
with the real socket name in case it has been redirected. */
static char *socket_name;
static char *redir_socket_name;
/* Name of the optional extra socket used for native gpg-agent requests. */
static char *socket_name_extra;
static char *redir_socket_name_extra;
/* Name of the optional browser socket used for native gpg-agent requests. */
static char *socket_name_browser;
static char *redir_socket_name_browser;
/* Name of the communication socket used for ssh-agent protocol. */
static char *socket_name_ssh;
static char *redir_socket_name_ssh;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
static assuan_sock_nonce_t socket_nonce_extra;
static assuan_sock_nonce_t socket_nonce_browser;
static assuan_sock_nonce_t socket_nonce_ssh;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
/* The event to break the select call. */
static HANDLE the_event2;
#elif defined(HAVE_PSELECT_NO_EINTR)
/* An FD to break the select call. */
static int event_pipe_fd;
#else
/* PID of the main thread. */
static pid_t main_thread_pid;
#endif
/* Default values for options passed to the pinentry. */
static char *default_display;
static char *default_ttyname;
static char *default_ttytype;
static char *default_lc_ctype;
static char *default_lc_messages;
static char *default_xauthority;
/* Name of a config file which was last read on startup or if missing
* the name of the standard config file. Any value here enabled the
* rereading of the standard config files on SIGHUP. */
static char *config_filename;
/* Helper to implement --debug-level */
static const char *debug_level;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
#ifdef HAVE_W32_SYSTEM
#define HAVE_PARENT_PID_SUPPORT 0
#else
#define HAVE_PARENT_PID_SUPPORT 1
#endif
/* The check_others_thread() function may test whether a parent is
* still running. We record the PID of the parent here or -1 if it
* should be watched. */
static pid_t parent_pid = (pid_t)(-1);
/* This flag is true if the inotify mechanism for detecting the
* removal of the homedir is active. This flag is used to disable the
* alternative but portable stat based check. */
static int have_homedir_inotify;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void agent_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void agent_init_default_ctrl (ctrl_t ctrl);
static void agent_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh,
int reliable_homedir_inotify);
static int check_for_running_agent (int silent);
#if CHECK_OWN_SOCKET_INTERVAL > 0
static void *check_own_socket_thread (void *arg);
#endif
static void *check_others_thread (void *arg);
/*
Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
This function is expected to be called only once. GETFNC is
expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
/* Return strings describing this program. The case values are
described in common/argparse.c:strusage. The values here override
the default values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@GPG_AGENT@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @GPG_AGENT@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @GPG_AGENT@ [options] [command [args]]\n"
"Secret key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. With the global variable DEBUG_LEVEL set to NULL
only the active debug flags are propagated to the subsystems. With
DEBUG_LEVEL set, a specific set of debug flags is set; thus overriding
all flags already set. Note that we don't fail here, because it is
important to keep gpg-agent running even after re-reading the
options due to a SIGHUP. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE | DBG_CACHE_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. REDIR_NAME is
the corresponding real name if the socket has been redirected. */
static void
remove_socket (char *name, char *redir_name)
{
if (name && *name)
{
if (redir_name)
name = redir_name;
gnupg_remove (name);
*name = 0;
}
}
/* Discover which inherited file descriptors correspond to which
* services/sockets offered by gpg-agent, using the LISTEN_FDS and
* LISTEN_FDNAMES convention. The understood labels are "ssh",
* "extra", and "browser". "std" or other labels will be interpreted
* as the standard socket.
*
* This function is designed to log errors when the expected file
* descriptors don't make sense, but to do its best to continue to
* work even in the face of minor misconfigurations.
*
* For more information on the LISTEN_FDS convention, see
* sd_listen_fds(3) on certain Linux distributions.
*/
#ifndef HAVE_W32_SYSTEM
static void
map_supervised_sockets (gnupg_fd_t *r_fd,
gnupg_fd_t *r_fd_extra,
gnupg_fd_t *r_fd_browser,
gnupg_fd_t *r_fd_ssh)
{
struct {
const char *label;
int **fdaddr;
char **nameaddr;
} tbl[] = {
{ "ssh", &r_fd_ssh, &socket_name_ssh },
{ "browser", &r_fd_browser, &socket_name_browser },
{ "extra", &r_fd_extra, &socket_name_extra },
{ "std", &r_fd, &socket_name } /* (Must be the last item.) */
};
const char *envvar;
char **fdnames;
int nfdnames;
int fd_count;
*r_fd = *r_fd_extra = *r_fd_browser = *r_fd_ssh = -1;
/* Print a warning if LISTEN_PID does not match outr pid. */
envvar = getenv ("LISTEN_PID");
if (!envvar)
log_error ("no LISTEN_PID environment variable found in "
"--supervised mode (ignoring)\n");
else if (strtoul (envvar, NULL, 10) != (unsigned long)getpid ())
log_error ("environment variable LISTEN_PID (%lu) does not match"
" our pid (%lu) in --supervised mode (ignoring)\n",
(unsigned long)strtoul (envvar, NULL, 10),
(unsigned long)getpid ());
/* Parse LISTEN_FDNAMES into the array FDNAMES. */
envvar = getenv ("LISTEN_FDNAMES");
if (envvar)
{
fdnames = strtokenize (envvar, ":");
if (!fdnames)
{
log_error ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
agent_exit (1);
}
for (nfdnames=0; fdnames[nfdnames]; nfdnames++)
;
}
else
{
fdnames = NULL;
nfdnames = 0;
}
/* Parse LISTEN_FDS into fd_count or provide a replacement. */
envvar = getenv ("LISTEN_FDS");
if (envvar)
fd_count = atoi (envvar);
else if (fdnames)
{
log_error ("no LISTEN_FDS environment variable found in --supervised"
" mode (relying on LISTEN_FDNAMES instead)\n");
fd_count = nfdnames;
}
else
{
log_error ("no LISTEN_FDS or LISTEN_FDNAMES environment variables "
"found in --supervised mode"
" (assuming 1 active descriptor)\n");
fd_count = 1;
}
if (fd_count < 1)
{
log_error ("--supervised mode expects at least one file descriptor"
" (was told %d, carrying on as though it were 1)\n",
fd_count);
fd_count = 1;
}
/* Assign the descriptors to the return values. */
if (!fdnames)
{
struct stat statbuf;
if (fd_count != 1)
log_error ("no LISTEN_FDNAMES and LISTEN_FDS (%d) != 1"
" in --supervised mode."
" (ignoring all sockets but the first one)\n",
fd_count);
if (fstat (3, &statbuf) == -1 && errno ==EBADF)
log_fatal ("file descriptor 3 must be valid in --supervised mode"
" if LISTEN_FDNAMES is not set\n");
*r_fd = 3;
socket_name = gnupg_get_socket_name (3);
}
else if (fd_count != nfdnames)
{
log_fatal ("number of items in LISTEN_FDNAMES (%d) does not match "
"LISTEN_FDS (%d) in --supervised mode\n",
nfdnames, fd_count);
}
else
{
int i, j, fd;
char *name;
for (i = 0; i < nfdnames; i++)
{
for (j = 0; j < DIM (tbl); j++)
{
if (!strcmp (fdnames[i], tbl[j].label) || j == DIM(tbl)-1)
{
fd = 3 + i;
if (**tbl[j].fdaddr == -1)
{
name = gnupg_get_socket_name (fd);
if (name)
{
**tbl[j].fdaddr = fd;
*tbl[j].nameaddr = name;
log_info ("using fd %d for %s socket (%s)\n",
fd, tbl[j].label, name);
}
else
{
log_error ("cannot listen on fd %d for %s socket\n",
fd, tbl[j].label);
close (fd);
}
}
else
{
log_error ("cannot listen on more than one %s socket\n",
tbl[j].label);
close (fd);
}
break;
}
}
}
}
xfree (fdnames);
}
#endif /*!HAVE_W32_SYSTEM*/
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
deinitialize_module_cache ();
if (!is_supervised && !inhibit_socket_removal)
{
remove_socket (socket_name, redir_socket_name);
if (opt.extra_socket > 1)
remove_socket (socket_name_extra, redir_socket_name_extra);
if (opt.browser_socket > 1)
remove_socket (socket_name_browser, redir_socket_name_browser);
remove_socket (socket_name_ssh, redir_socket_name_ssh);
}
}
/* Handle options which are allowed to be reset after program start.
Return true when the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (gpgrt_argparse_t *pargs, int reread)
{
int i;
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.no_grab = 1;
opt.debug_pinentry = 0;
xfree (opt.pinentry_program);
opt.pinentry_program = NULL;
opt.pinentry_touch_file = NULL;
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = NULL;
opt.pinentry_timeout = 0;
opt.pinentry_formatted_passphrase = 0;
memset (opt.daemon_program, 0, sizeof opt.daemon_program);
opt.def_cache_ttl = DEFAULT_CACHE_TTL;
opt.def_cache_ttl_ssh = DEFAULT_CACHE_TTL_SSH;
opt.max_cache_ttl = MAX_CACHE_TTL;
opt.max_cache_ttl_ssh = MAX_CACHE_TTL_SSH;
opt.enforce_passphrase_constraints = 0;
opt.min_passphrase_len = MIN_PASSPHRASE_LEN;
opt.min_passphrase_nonalpha = MIN_PASSPHRASE_NONALPHA;
opt.check_passphrase_pattern = NULL;
opt.check_sym_passphrase_pattern = NULL;
opt.max_passphrase_days = MAX_PASSPHRASE_DAYS;
opt.enable_passphrase_history = 0;
opt.ignore_cache_for_signing = 0;
opt.allow_mark_trusted = 1;
opt.sys_trustlist_name = NULL;
opt.allow_external_cache = 1;
opt.allow_loopback_pinentry = 1;
opt.allow_emacs_pinentry = 0;
memset (opt.disable_daemon, 0, sizeof opt.disable_daemon);
disable_check_own_socket = 0;
/* Note: When changing the next line, change also gpgconf_list. */
opt.ssh_fingerprint_digest = GCRY_MD_SHA256;
opt.s2k_count = 0;
set_s2k_calibration_time (0); /* Set to default. */
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oDebugPinentry: opt.debug_pinentry = 1; break;
case oLogFile:
if (!reread)
return 0; /* not handled */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oNoGrab: opt.no_grab |= 1; break;
case oGrab: opt.no_grab |= 2; break;
case oPinentryProgram:
xfree (opt.pinentry_program);
opt.pinentry_program = make_filename_try (pargs->r.ret_str, NULL);
break;
case oPinentryTouchFile: opt.pinentry_touch_file = pargs->r.ret_str; break;
case oPinentryInvisibleChar:
xfree (opt.pinentry_invisible_char);
opt.pinentry_invisible_char = xtrystrdup (pargs->r.ret_str); break;
break;
case oPinentryTimeout: opt.pinentry_timeout = pargs->r.ret_ulong; break;
case oPinentryFormattedPassphrase:
opt.pinentry_formatted_passphrase = 1;
break;
case oTpm2daemonProgram:
opt.daemon_program[DAEMON_TPM2D] = pargs->r.ret_str;
break;
case oScdaemonProgram:
opt.daemon_program[DAEMON_SCD] = pargs->r.ret_str;
break;
case oDisableScdaemon: opt.disable_daemon[DAEMON_SCD] = 1; break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oDefCacheTTL: opt.def_cache_ttl = pargs->r.ret_ulong; break;
case oDefCacheTTLSSH: opt.def_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oMaxCacheTTL: opt.max_cache_ttl = pargs->r.ret_ulong; break;
case oMaxCacheTTLSSH: opt.max_cache_ttl_ssh = pargs->r.ret_ulong; break;
case oEnforcePassphraseConstraints:
opt.enforce_passphrase_constraints=1;
break;
case oMinPassphraseLen: opt.min_passphrase_len = pargs->r.ret_ulong; break;
case oMinPassphraseNonalpha:
opt.min_passphrase_nonalpha = pargs->r.ret_ulong;
break;
case oCheckPassphrasePattern:
opt.check_passphrase_pattern = pargs->r.ret_str;
break;
case oCheckSymPassphrasePattern:
opt.check_sym_passphrase_pattern = pargs->r.ret_str;
break;
case oMaxPassphraseDays:
opt.max_passphrase_days = pargs->r.ret_ulong;
break;
case oEnablePassphraseHistory:
opt.enable_passphrase_history = 1;
break;
case oIgnoreCacheForSigning: opt.ignore_cache_for_signing = 1; break;
case oAllowMarkTrusted: opt.allow_mark_trusted = 1; break;
case oNoAllowMarkTrusted: opt.allow_mark_trusted = 0; break;
case oNoUserTrustlist: opt.no_user_trustlist = 1; break;
case oSysTrustlistName: opt.sys_trustlist_name = pargs->r.ret_str; break;
case oAllowPresetPassphrase: opt.allow_preset_passphrase = 1; break;
case oAllowLoopbackPinentry: opt.allow_loopback_pinentry = 1; break;
case oNoAllowLoopbackPinentry: opt.allow_loopback_pinentry = 0; break;
case oNoAllowExternalCache: opt.allow_external_cache = 0;
break;
case oAllowEmacsPinentry: opt.allow_emacs_pinentry = 1;
break;
case oSSHFingerprintDigest:
i = gcry_md_map_name (pargs->r.ret_str);
if (!i)
log_error (_("selected digest algorithm is invalid\n"));
else
opt.ssh_fingerprint_digest = i;
break;
case oS2KCount:
opt.s2k_count = pargs->r.ret_ulong;
break;
case oS2KCalibration:
set_s2k_calibration_time (pargs->r.ret_ulong);
break;
case oNoop: break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
/* Hack to allow --grab to override --no-grab. */
if ((opt.no_grab & 2))
opt.no_grab = 0;
/* With --no-user-trustlist it does not make sense to allow the mark
* trusted feature. */
if (opt.no_user_trustlist)
opt.allow_mark_trusted = 0;
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
}
static void
initialize_modules (void)
{
thread_init_once ();
initialize_module_cache ();
initialize_module_call_pinentry ();
initialize_module_daemon ();
initialize_module_trustlist ();
}
/* The main entry point. */
int
main (int argc, char **argv)
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
int debug_argparser = 0;
const char *shell;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
gpg_error_t err;
struct assuan_malloc_hooks malloc_hooks;
int reliable_homedir_inotify = 1;
early_system_init ();
/* Before we do anything else we save the list of currently open
file descriptors and the signal mask. This info is required to
do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix (GPG_AGENT_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
gcry_set_progress_handler (agent_libgcrypt_progress_cb, NULL);
disable_core_dumps ();
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Record some of the original environment strings. */
{
const char *s;
int idx;
static const char *names[] =
{ "DISPLAY", "TERM", "XAUTHORITY", "PINENTRY_USER_DATA", NULL };
err = 0;
opt.startup_env = session_env_new ();
if (!opt.startup_env)
err = gpg_error_from_syserror ();
for (idx=0; !err && names[idx]; idx++)
{
s = getenv (names[idx]);
if (s)
err = session_env_setenv (opt.startup_env, names[idx], s);
}
if (!err)
{
s = gnupg_ttyname (0);
if (s)
err = session_env_setenv (opt.startup_env, "GPG_TTY", s);
}
if (err)
log_fatal ("error recording startup environment: %s\n",
gpg_strerror (err));
/* Fixme: Better use the locale function here. */
opt.startup_lc_ctype = getenv ("LC_CTYPE");
if (opt.startup_lc_ctype)
opt.startup_lc_ctype = xstrdup (opt.startup_lc_ctype);
opt.startup_lc_messages = getenv ("LC_MESSAGES");
if (opt.startup_lc_messages)
opt.startup_lc_messages = xstrdup (opt.startup_lc_messages);
}
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
case oDebugQuickRandom:
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0);
maybe_setuid = 0;
/*
* Now we are now working under our real uid
*/
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
/* We are re-using the struct, thus the reset flag. We OR the
- * flags so that the internal intialized flag won't be cleared. */
+ * flags so that the internal initialized flag won't be cleared. */
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, GPG_AGENT_NAME EXTSEP_S "conf"))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
continue;
}
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case aUseStandardSocketP: gpgconf_list = 3; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oStealSocket: steal_socket = 1; break;
case oSupervised: is_supervised = 1; break;
case oDisplay: default_display = xstrdup (pargs.r.ret_str); break;
case oTTYname: default_ttyname = xstrdup (pargs.r.ret_str); break;
case oTTYtype: default_ttytype = xstrdup (pargs.r.ret_str); break;
case oLCctype: default_lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: default_lc_messages = xstrdup (pargs.r.ret_str);
break;
case oXauthority: default_xauthority = xstrdup (pargs.r.ret_str);
break;
case oUseStandardSocket:
case oNoUseStandardSocket:
obsolete_option (configname, pargs.lineno, "use-standard-socket");
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oKeepTTY: opt.keep_tty = 1; break;
case oKeepDISPLAY: opt.keep_display = 1; break;
case oSSHSupport:
ssh_support = 1;
break;
case oPuttySupport:
# ifdef HAVE_W32_SYSTEM
putty_support = 1;
# endif
break;
case oWin32OpenSSHSupport:
# ifdef HAVE_W32_SYSTEM
if (pargs.r_type)
win32_openssh_support = pargs.r.ret_str;
else
win32_openssh_support = W32_DEFAULT_AGENT_PIPE_NAME;
# endif
break;
case oExtraSocket:
opt.extra_socket = 1; /* (1 = points into argv) */
socket_name_extra = pargs.r.ret_str;
break;
case oBrowserSocket:
opt.browser_socket = 1; /* (1 = points into argv) */
socket_name_browser = pargs.r.ret_str;
break;
case oAutoExpandSecmem:
/* Try to enable this option. It will officially only be
* supported by Libgcrypt 1.9 but 1.8.2 already supports it
* on the quiet and thus we use the numeric value value. */
gcry_control (78 /*GCRYCTL_AUTO_EXPAND_SECMEM*/,
(unsigned int)pargs.r.ret_ulong, 0);
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
case oDebugQuickRandom:
/* Only used by the first stage command line parser. */
break;
case oWriteEnvFile:
obsolete_option (configname, pargs.lineno, "write-env-file");
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
GPG_AGENT_NAME EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit(2);
finalize_rereadable_options ();
/* Get a default log file from common.conf. */
if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_AGENT, debug_argparser))
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
#ifdef ENABLE_NLS
/* gpg-agent usually does not output any messages because it runs in
the background. For log files it is acceptable to have messages
always encoded in utf-8. We switch here to utf-8, so that
commands like --help still give native messages. It is far
easier to switch only once instead of for every message and it
actually helps when more then one thread is active (avoids an
extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list && !is_supervised)
{
/* We have been called without any command and thus we merely
check whether an agent is already running. We do this right
here so that we don't clobber a logfile with this check but
print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_agent (0);
agent_exit (0);
}
if (is_supervised && !opt.quiet)
log_info(_("WARNING: \"%s\" is a deprecated option\n"), "--supervised");
if (is_supervised)
;
else if (!opt.extra_socket)
opt.extra_socket = 1;
else if (socket_name_extra
&& (!strcmp (socket_name_extra, "none")
|| !strcmp (socket_name_extra, "/dev/null")))
{
/* User requested not to create this socket. */
opt.extra_socket = 0;
socket_name_extra = NULL;
}
if (is_supervised)
;
else if (!opt.browser_socket)
opt.browser_socket = 1;
else if (socket_name_browser
&& (!strcmp (socket_name_browser, "none")
|| !strcmp (socket_name_browser, "/dev/null")))
{
/* User requested not to create this socket. */
opt.browser_socket = 0;
socket_name_browser = NULL;
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
if (!gpgconf_list)
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 3)
{
/* We now use the standard socket always - return true for
backward compatibility. */
agent_exit (0);
}
else if (gpgconf_list == 2)
agent_exit (0);
else if (gpgconf_list)
{
/* Note: If an option is runtime changeable, please set the
* respective flag in the gpgconf-comp.c table. */
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("default-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL );
es_printf ("default-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_CACHE_TTL_SSH );
es_printf ("max-cache-ttl:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL );
es_printf ("max-cache-ttl-ssh:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MAX_CACHE_TTL_SSH );
es_printf ("min-passphrase-len:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_LEN );
es_printf ("min-passphrase-nonalpha:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MIN_PASSPHRASE_NONALPHA);
es_printf ("check-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT);
es_printf ("check-sym-passphrase-pattern:%lu:\n",
GC_OPT_FLAG_DEFAULT);
es_printf ("max-passphrase-days:%lu:%d:\n",
GC_OPT_FLAG_DEFAULT, MAX_PASSPHRASE_DAYS);
es_printf ("ssh-fingerprint-digest:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT, "sha256");
agent_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
/* Make sure that we have a default ttyname. */
if (!default_ttyname && gnupg_ttyname (1))
default_ttyname = xstrdup (gnupg_ttyname (1));
if (!default_ttytype && getenv ("TERM"))
default_ttytype = xstrdup (getenv ("TERM"));
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
agent_exit (1);
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
xfree (ctrl);
agent_exit (1);
}
agent_init_default_ctrl (ctrl);
start_command_handler (ctrl, GNUPG_INVALID_FD, GNUPG_INVALID_FD);
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (is_supervised && comopt.no_autostart)
{
/* If we are running on a server and the user has set
* no-autostart for gpg or gpgsm. gpg-agent would anyway be
* started by the supervisor which has the bad effect that it
* will steal the socket from a remote server. Note that
* systemd has no knowledge about the lock files we take during
* the start operation. */
log_info ("%s %s not starting in supervised mode due to no-autostart.\n",
gpgrt_strusage(11), gpgrt_strusage(13) );
}
else if (is_supervised)
{
#ifndef HAVE_W32_SYSTEM
gnupg_fd_t fd, fd_extra, fd_browser, fd_ssh;
initialize_modules ();
/* when supervised and sending logs to stderr, the process
supervisor should handle log entry metadata (pid, name,
timestamp) */
if (!logfile)
log_set_prefix (NULL, 0);
log_info ("%s %s starting in supervised mode.\n",
gpgrt_strusage(11), gpgrt_strusage(13) );
/* See below in "regular server mode" on why we remove certain
* envvars. */
if (!opt.keep_display)
gnupg_unsetenv ("DISPLAY");
gnupg_unsetenv ("INSIDE_EMACS");
/* Virtually create the sockets. Note that we use -1 here
* because the whole thing works only on Unix. */
map_supervised_sockets (&fd, &fd_extra, &fd_browser, &fd_ssh);
if (fd == -1)
log_fatal ("no standard socket provided\n");
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
log_info ("listening on: std=%d extra=%d browser=%d ssh=%d\n",
fd, fd_extra, fd_browser, fd_ssh);
handle_connections (fd, fd_extra, fd_browser, fd_ssh, 1);
#endif /*!HAVE_W32_SYSTEM*/
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular server mode */
gnupg_fd_t fd;
gnupg_fd_t fd_extra = GNUPG_INVALID_FD;
gnupg_fd_t fd_browser = GNUPG_INVALID_FD;
gnupg_fd_t fd_ssh = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Remove the DISPLAY variable so that a pinentry does not
default to a specific display. There is still a default
display when gpg-agent was started using --display or a
client requested this using an OPTION command. Note, that we
don't do this when running in reverse daemon mode (i.e. when
exec the program given as arguments). */
#ifndef HAVE_W32_SYSTEM
if (!opt.keep_display && !argc)
gnupg_unsetenv ("DISPLAY");
#endif
/* Remove the INSIDE_EMACS variable so that a pinentry does not
always try to interact with Emacs. The variable is set when
a client requested this using an OPTION command. */
gnupg_unsetenv ("INSIDE_EMACS");
/* Create the sockets. */
socket_name = create_socket_name (GPG_AGENT_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 1, 0,
&redir_socket_name, &socket_nonce);
if (opt.extra_socket)
{
if (socket_name_extra)
socket_name_extra = create_socket_name (socket_name_extra, 0);
else
socket_name_extra = create_socket_name
/**/ (GPG_AGENT_EXTRA_SOCK_NAME, 1);
opt.extra_socket = 2; /* Indicate that it has been malloced. */
fd_extra = create_server_socket (socket_name_extra, 0, 0,
&redir_socket_name_extra,
&socket_nonce_extra);
}
if (opt.browser_socket)
{
if (socket_name_browser)
socket_name_browser = create_socket_name (socket_name_browser, 0);
else
socket_name_browser= create_socket_name
/**/ (GPG_AGENT_BROWSER_SOCK_NAME, 1);
opt.browser_socket = 2; /* Indicate that it has been malloced. */
fd_browser = create_server_socket (socket_name_browser, 0, 0,
&redir_socket_name_browser,
&socket_nonce_browser);
}
socket_name_ssh = create_socket_name (GPG_AGENT_SSH_SOCK_NAME, 1);
fd_ssh = create_server_socket (socket_name_ssh, 0, 1,
&redir_socket_name_ssh,
&socket_nonce_ssh);
/* If we are going to exec a program in the parent, we record
the PID, so that the child may check whether the program is
still alive. */
if (argc)
parent_pid = getpid ();
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
char *infostr_ssh_sock, *infostr_ssh_valid;
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
we restore it. That is not strictly necessary but some
programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
/* Create the SSH info string if enabled. */
if (ssh_support)
{
if (asprintf (&infostr_ssh_sock, "SSH_AUTH_SOCK=%s",
socket_name_ssh) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
if (asprintf (&infostr_ssh_valid, "gnupg_SSH_AUTH_SOCK_by=%lu",
(unsigned long)getpid()) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
}
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
if (opt.extra_socket)
*socket_name_extra = 0;
if (opt.browser_socket)
*socket_name_browser = 0;
*socket_name_ssh = 0;
if (argc)
{ /* Run the program given on the commandline. */
if (ssh_support && (putenv (infostr_ssh_sock)
|| putenv (infostr_ssh_valid)))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
/* Close all the file descriptors except the standard
ones and those open at startup. We explicitly don't
close 0,1,2 in case something went wrong collecting
them at startup. */
close_all_fds (3, startup_fd_list);
/* Run the command. */
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
if (ssh_support)
{
*strchr (infostr_ssh_sock, '=') = ' ';
es_printf ("setenv %s;\n", infostr_ssh_sock);
}
}
else
{
if (ssh_support)
{
es_printf ("%s; export SSH_AUTH_SOCK;\n",
infostr_ssh_sock);
}
}
if (ssh_support)
{
xfree (infostr_ssh_sock);
xfree (infostr_ssh_valid);
}
exit (0);
}
/*NOTREACHED*/
} /* End parent */
/*
This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
}
/* When we are running with a program given on the command
* line, the inotify things may not work well and thus
* we cannot avoid the regular stat calls. */
if (argc)
reliable_homedir_inotify = 0;
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13) );
handle_connections (fd, fd_extra, fd_browser, fd_ssh,
reliable_homedir_inotify);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
agent_exit (int rc)
{
/*FIXME: update_random_seed_file();*/
/* We run our cleanup handler because that may close cipher contexts
stored in secure memory and thus this needs to be done before we
explicitly terminate secure memory. */
cleanup ();
#if 1
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
set once at startup and dispatches progress messages to the
corresponding threads of the agent. */
static void
agent_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
agent_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
structure usually identified by an argument named CTRL. This
function is called immediately after allocating the control
structure. Its purpose is to setup the default values for that
structure. Note that some values may have already been set. */
static void
agent_init_default_ctrl (ctrl_t ctrl)
{
log_assert (ctrl->session_env);
/* Note we ignore malloc errors because we can't do much about it
and the request will fail anyway shortly after this
initialization. */
session_env_setenv (ctrl->session_env, "DISPLAY", default_display);
session_env_setenv (ctrl->session_env, "GPG_TTY", default_ttyname);
session_env_setenv (ctrl->session_env, "TERM", default_ttytype);
session_env_setenv (ctrl->session_env, "XAUTHORITY", default_xauthority);
session_env_setenv (ctrl->session_env, "PINENTRY_USER_DATA", NULL);
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
ctrl->lc_ctype = default_lc_ctype? xtrystrdup (default_lc_ctype) : NULL;
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = default_lc_messages? xtrystrdup (default_lc_messages)
/**/ : NULL;
ctrl->cache_ttl_opt_preset = CACHE_TTL_OPT_PRESET;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to agent_init_default_ctrl. */
static void
agent_deinit_default_ctrl (ctrl_t ctrl)
{
unregister_progress_cb ();
session_env_release (ctrl->session_env);
clear_ephemeral_keys (ctrl);
xfree (ctrl->digest.data);
ctrl->digest.data = NULL;
if (ctrl->lc_ctype)
xfree (ctrl->lc_ctype);
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
}
/* Because the ssh protocol does not send us information about the
current TTY setting, we use this function to use those from startup
or those explicitly set. This is also used for the restricted mode
where we ignore requests to change the environment. */
gpg_error_t
agent_copy_startup_env (ctrl_t ctrl)
{
gpg_error_t err = 0;
int iterator = 0;
const char *name, *value;
while (!err && (name = session_env_list_stdenvnames (&iterator, NULL)))
{
if ((value = session_env_getenv (opt.startup_env, name)))
err = session_env_setenv (ctrl->session_env, name, value);
}
if (!err && !ctrl->lc_ctype && opt.startup_lc_ctype)
if (!(ctrl->lc_ctype = xtrystrdup (opt.startup_lc_ctype)))
err = gpg_error_from_syserror ();
if (!err && !ctrl->lc_messages && opt.startup_lc_messages)
if (!(ctrl->lc_messages = xtrystrdup (opt.startup_lc_messages)))
err = gpg_error_from_syserror ();
if (err)
log_error ("error setting default session environment: %s\n",
gpg_strerror (err));
return err;
}
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the PTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process's original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
gpgrt_argparse_t pargs;
char *twopart;
int dummy;
int logfile_seen = 0;
if (!config_filename)
return; /* No config file. */
twopart = strconcat (GPG_AGENT_NAME EXTSEP_S "conf" PATHSEP_S,
config_filename, NULL);
if (!twopart)
return; /* Out of core. */
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = (ARGPARSE_FLAG_KEEP
|ARGPARSE_FLAG_SYS
|ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, twopart) )
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
}
else if (pargs.r_opt < -1)
pargs.err = ARGPARSE_PRINT_WARNING;
else /* Try to parse this option - ignore unchangeable ones. */
{
if (pargs.r_opt == oLogFile)
logfile_seen = 1;
parse_rereadable_options (&pargs, 1);
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
xfree (twopart);
finalize_rereadable_options ();
set_debug ();
/* Get a default log file from common.conf. */
if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_AGENT, !!opt.debug))
{
if (!current_logfile || !comopt.logfile
|| strcmp (current_logfile, comopt.logfile))
{
log_set_file (comopt.logfile);
xfree (current_logfile);
current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL;
}
}
}
/* Return the file name of the socket we are using for native
requests. */
const char *
get_agent_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the file name of the socket we are using for SSH
requests. */
const char *
get_agent_ssh_socket_name (void)
{
const char *s = socket_name_ssh;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_agent_active_connection_count (void)
{
return active_connections;
}
/* Under W32, this function returns the handle of the scdaemon
notification event. Calling it the first time creates that
event. */
#if defined(HAVE_W32_SYSTEM)
static void *
create_an_event (void)
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
/* We need to use a manual reset event object due to the way our
w32-pth wait function works: If we would use an automatic
reset event we are not able to figure out which handle has
been signaled because at the time we single out the signaled
handles using WFSO the event has already been reset due to
the WFMO. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create an event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for an event failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
return h2;
}
return INVALID_HANDLE_VALUE;
}
void *
get_agent_daemon_notify_event (void)
{
static HANDLE the_event = INVALID_HANDLE_VALUE;
if (the_event == INVALID_HANDLE_VALUE)
the_event = create_an_event ();
return the_event;
}
#endif /*HAVE_W32_SYSTEM*/
/* Create a name for the socket in the home directory as using
STANDARD_NAME. We also check for valid characters as well as
against a maximum allowed length for a unix domain socket is done.
The function terminates the process in case of an error. Returns:
Pointer to an allocated string with the absolute name of the socket
used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
agent_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. Note that this
function needs to be used for the regular socket first (indicated
by PRIMARY) and only then for the extra and the ssh sockets. If
the socket has been redirected the name of the real socket is
stored as a malloced string at R_REDIR_NAME. If CYGWIN is set a
Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int primary, int cygwin,
char **r_redir_name, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
agent_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
/* At least our error code mapping on Windows-CE used to return
* EEXIST thus we better test for this on Windows . */
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a gpg-agent is already running. We do this
test only if this is the primary socket. For secondary
sockets we assume that a test for gpg-agent has already been
done and reuse the requested socket. Testing the ssh-socket
is not possible because at this point, though we know the new
Assuan socket, the Assuan server and thus the ssh-agent
server is not yet operational; this would lead to a hang. */
if (primary && !check_for_running_agent (1))
{
if (steal_socket)
log_info (N_("trying to steal socket from running %s\n"),
"gpg-agent");
else
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a gpg-agent is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
agent_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n",
listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
agent_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the private keys exists and
create it if not. This function won't fail as it is only a
convenience function and not strictly necessary. */
static void
create_private_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PRIVATE_KEYS_DIR, NULL);
if (gnupg_stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
if (gnupg_chmod (fname, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
}
else
{
/* The file exists or another error. Make sure we have sensible
* permissions. We enforce rwx for user but keep existing group
* permissions. Permissions for other are always cleared. */
if (gnupg_chmod (fname, "-rwx...---"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
}
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
same as the default one. This way we avoid to create arbitrary
directories when a non-default home directory is used. To cope
with HOME, we compare only the suffix if we see that the default
homedir does start with a tilde. We don't stop here in case of
problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if (gnupg_stat (home, &statbuf))
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
create_private_keys_directory (home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_private_keys_directory (home);
}
xfree (home);
}
/* A global function which allows us to call the reload stuff from
other places too. This is only used when build for W32. */
void
agent_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
agent_flush_cache (0);
reread_configuration ();
agent_reload_trustlist ();
/* We flush the module name cache so that after installing a
"pinentry" binary that one can be used in case the
"pinentry-basic" fallback was in use. */
gnupg_module_name_flush_some ();
if (opt.disable_daemon[DAEMON_SCD])
agent_kill_daemon (DAEMON_SCD);
}
/* A helper function to handle SIGUSR2. */
static void
agent_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - updating card event counter\n");
/* Nothing to check right now. We only increment a counter. */
bump_card_eventcounter ();
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
#ifndef HAVE_W32_SYSTEM
case SIGHUP:
agent_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
agent_query_dump_state ();
agent_daemon_dump_state ();
break;
case SIGUSR2:
agent_sigusr2_action ();
break;
case SIGCONT:
/* Do nothing, but break the syscall. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
agent_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
agent_exit (0);
break;
#endif
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
#ifdef HAVE_W32_SYSTEM
/* The window message processing function for Putty. Warning: This
code runs as a native Windows thread. Use of our own functions
needs to be bracket with pth_leave/pth_enter. */
static LRESULT CALLBACK
putty_message_proc (HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
int ret = 0;
int w32rc;
COPYDATASTRUCT *cds;
const char *mapfile;
HANDLE maphd;
PSID mysid = NULL;
PSID mapsid = NULL;
void *data = NULL;
PSECURITY_DESCRIPTOR psd = NULL;
ctrl_t ctrl = NULL;
if (msg != WM_COPYDATA)
{
return DefWindowProc (hwnd, msg, wparam, lparam);
}
cds = (COPYDATASTRUCT*)lparam;
if (cds->dwData != PUTTY_IPC_MAGIC)
return 0; /* Ignore data with the wrong magic. */
mapfile = cds->lpData;
if (!cds->cbData || mapfile[cds->cbData - 1])
return 0; /* Ignore empty and non-properly terminated strings. */
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map file '%s'", mapfile);
npth_unprotect ();
}
maphd = OpenFileMapping (FILE_MAP_ALL_ACCESS, FALSE, mapfile);
if (DBG_IPC)
{
npth_protect ();
log_debug ("ssh map handle %p\n", maphd);
npth_unprotect ();
}
if (!maphd || maphd == INVALID_HANDLE_VALUE)
return 0;
npth_protect ();
mysid = w32_get_user_sid ();
if (!mysid)
{
log_error ("error getting my sid\n");
goto leave;
}
w32rc = GetSecurityInfo (maphd, SE_KERNEL_OBJECT,
OWNER_SECURITY_INFORMATION,
&mapsid, NULL, NULL, NULL,
&psd);
if (w32rc)
{
log_error ("error getting sid of ssh map file: rc=%d", w32rc);
goto leave;
}
if (DBG_IPC)
{
char *sidstr;
if (!ConvertSidToStringSid (mysid, &sidstr))
sidstr = NULL;
log_debug (" my sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
if (!ConvertSidToStringSid (mapsid, &sidstr))
sidstr = NULL;
log_debug ("ssh map file sid: '%s'", sidstr? sidstr: "[error]");
LocalFree (sidstr);
}
if (!EqualSid (mysid, mapsid))
{
log_error ("ssh map file has a non-matching sid\n");
goto leave;
}
data = MapViewOfFile (maphd, FILE_MAP_ALL_ACCESS, 0, 0, 0);
if (DBG_IPC)
log_debug ("ssh IPC buffer at %p\n", data);
if (!data)
goto leave;
/* log_printhex ("request:", data, 20); */
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
goto leave;
}
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno) );
goto leave;
}
agent_init_default_ctrl (ctrl);
if (!serve_mmapped_ssh_request (ctrl, data, PUTTY_IPC_MAXLEN))
ret = 1; /* Valid ssh message has been constructed. */
agent_deinit_default_ctrl (ctrl);
/* log_printhex (" reply:", data, 20); */
leave:
xfree (ctrl);
if (data)
UnmapViewOfFile (data);
xfree (mapsid);
if (psd)
LocalFree (psd);
xfree (mysid);
CloseHandle (maphd);
npth_unprotect ();
return ret;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
/* The thread handling Putty's IPC requests. */
static void *
putty_message_thread (void *arg)
{
WNDCLASS wndwclass = {0, putty_message_proc, 0, 0,
NULL, NULL, NULL, NULL, NULL, "Pageant"};
HWND hwnd;
MSG msg;
(void)arg;
if (opt.verbose)
log_info ("putty message loop thread started\n");
/* The message loop runs as thread independent from our nPth system.
This also means that we need to make sure that we switch back to
our system before calling any no-windows function. */
npth_unprotect ();
/* First create a window to make sure that a message queue exists
for this thread. */
if (!RegisterClass (&wndwclass))
{
npth_protect ();
log_error ("error registering Pageant window class");
return NULL;
}
hwnd = CreateWindowEx (0, "Pageant", "Pageant", 0,
0, 0, 0, 0,
HWND_MESSAGE, /* hWndParent */
NULL, /* hWndMenu */
NULL, /* hInstance */
NULL); /* lpParm */
if (!hwnd)
{
npth_protect ();
log_error ("error creating Pageant window");
return NULL;
}
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
/* Back to nPth. */
npth_protect ();
if (opt.verbose)
log_info ("putty message loop thread stopped\n");
return NULL;
}
#define BUFSIZE (5 * 1024)
/* The thread handling Win32-OpenSSH requests through NamedPipe. */
static void *
win32_openssh_thread (void *arg)
{
HANDLE pipe;
(void)arg;
if (opt.verbose)
log_info ("Win32-OpenSSH thread started\n");
while (1)
{
ctrl_t ctrl = NULL;
estream_t ssh_stream = NULL;
es_syshd_t syshd;
npth_unprotect ();
pipe = CreateNamedPipeA (win32_openssh_support, PIPE_ACCESS_DUPLEX,
(PIPE_TYPE_BYTE | PIPE_READMODE_BYTE
| PIPE_WAIT | PIPE_REJECT_REMOTE_CLIENTS),
PIPE_UNLIMITED_INSTANCES,
BUFSIZE, BUFSIZE, 0, NULL);
if (pipe == INVALID_HANDLE_VALUE)
{
npth_protect ();
log_error ("cannot create pipe: %ld\n", GetLastError ());
break;
}
if (ConnectNamedPipe (pipe, NULL) == 0)
{
npth_protect ();
CloseHandle (pipe);
log_error ("Error at ConnectNamedPipe: %ld\n", GetLastError ());
break;
}
npth_protect ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
CloseHandle (pipe);
log_error ("error allocating connection control data: %s\n",
strerror (errno));
break;
}
#if _WIN32_WINNT >= 0x600
if (!GetNamedPipeClientProcessId (pipe, &ctrl->client_pid))
log_info ("failed to get client process id: %ld\n", GetLastError ());
else
ctrl->client_uid = -1;
#endif
ctrl->session_env = session_env_new ();
if (!ctrl->session_env)
{
log_error ("error allocating session environment block: %s\n",
strerror (errno));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
CloseHandle (pipe);
break;
}
agent_init_default_ctrl (ctrl);
syshd.type = ES_SYSHD_HANDLE;
syshd.u.handle = pipe;
ssh_stream = es_sysopen (&syshd, "r+b");
if (!ssh_stream)
{
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
CloseHandle (pipe);
break;
}
start_command_handler_ssh_stream (ctrl, ssh_stream);
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
CloseHandle (pipe);
}
if (opt.verbose)
log_info ("Win32-OpenSSH thread stopped\n");
return NULL;
}
#endif /*HAVE_W32_SYSTEM*/
static void *
do_start_connection_thread (ctrl_t ctrl)
{
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose > 1 && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
start_command_handler (ctrl, GNUPG_INVALID_FD, ctrl->thread_startup.fd);
if (opt.verbose > 1 && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread_std (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
/* This is the extra socket connection thread's main function. */
static void *
start_connection_thread_extra (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_extra))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 1;
return do_start_connection_thread (ctrl);
}
/* This is the browser socket connection thread's main function. */
static void *
start_connection_thread_browser (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_browser))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
ctrl->restricted = 2;
return do_start_connection_thread (ctrl);
}
/* This is the ssh connection thread's main function. */
static void *
start_connection_thread_ssh (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce_ssh))
return NULL;
active_connections++;
agent_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
start_command_handler_ssh (ctrl, ctrl->thread_startup.fd);
if (opt.verbose)
log_info (_("ssh handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
agent_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
void
agent_kick_the_loop (void)
{
/* Kick the select loop. */
#ifdef HAVE_W32_SYSTEM
int ret = SetEvent (the_event2);
if (ret == 0)
log_error ("SetEvent for agent_kick_the_loop failed: %s\n",
w32_strerror (-1));
#else
# ifdef HAVE_PSELECT_NO_EINTR
write (event_pipe_fd, "", 1);
# else
int ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("sending signal for agent_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
# endif
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd,
gnupg_fd_t listen_fd_extra,
gnupg_fd_t listen_fd_browser,
gnupg_fd_t listen_fd_ssh,
int reliable_homedir_inotify)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
struct timespec *tp;
#ifdef HAVE_W32_SYSTEM
HANDLE events[3];
unsigned int events_set;
#else
int signo;
# ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
# endif
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread_std },
{ "extra", start_connection_thread_extra },
{ "browser", start_connection_thread_browser },
{ "ssh", start_connection_thread_ssh }
};
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n",
strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
# ifdef HAVE_PSELECT_NO_EINTR
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
event_pipe_fd = pipe_fd[1];
# else
main_thread_pid = getpid ();
# endif
#else
events[0] = get_agent_daemon_notify_event ();
events[1] = the_event2 = create_an_event ();
events[2] = INVALID_HANDLE_VALUE;
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if (!reliable_homedir_inotify)
home_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (!disable_check_own_socket && sock_inotify_fd == -1)
{
npth_t thread;
err = npth_create (&thread, &tattr, check_own_socket_thread, NULL);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
}
#endif
if ((HAVE_PARENT_PID_SUPPORT && parent_pid != (pid_t)(-1))
|| !have_homedir_inotify)
{
npth_t thread;
err = npth_create (&thread, &tattr, check_others_thread, NULL);
if (err)
log_error ("error spawning check_others_thread: %s\n", strerror (err));
}
/* On Windows we need to fire up a separate thread to listen for
requests from Putty (an SSH client), so we can replace Putty's
Pageant (its ssh-agent implementation). */
#ifdef HAVE_W32_SYSTEM
if (putty_support)
{
npth_t thread;
ret = npth_create (&thread, &tattr, putty_message_thread, NULL);
if (ret)
log_error ("error spawning putty message loop: %s\n", strerror (ret));
}
if (win32_openssh_support)
{
npth_t thread;
ret = npth_create (&thread, &tattr, win32_openssh_thread, NULL);
if (ret)
log_error ("error spawning Win32-OpenSSH loop: %s\n", strerror (ret));
}
#endif /*HAVE_W32_SYSTEM*/
/* Set a flag to tell call-scd.c that it may enable event
notifications. */
opt.sigusr2_enabled = 1;
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2NUM (listen_fd);
if (listen_fd_extra != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_extra), &fdset);
if (FD2INT (listen_fd_extra) > nfd)
nfd = FD2NUM (listen_fd_extra);
}
if (listen_fd_browser != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_browser), &fdset);
if (FD2INT (listen_fd_browser) > nfd)
nfd = FD2NUM (listen_fd_browser);
}
if (listen_fd_ssh != GNUPG_INVALID_FD)
{
FD_SET ( FD2INT(listen_fd_ssh), &fdset);
if (FD2INT (listen_fd_ssh) > nfd)
nfd = FD2NUM (listen_fd_ssh);
}
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
listentbl[1].l_fd = listen_fd_extra;
listentbl[2].l_fd = listen_fd_browser;
listentbl[3].l_fd = listen_fd_ssh;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (active_connections == 0 || is_supervised)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (nfd < pipe_fd[0])
nfd = pipe_fd[0];
#endif
tp = agent_cache_expiration ();
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, tp,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, tp,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if (events_set & 1)
agent_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
#ifndef HAVE_W32_SYSTEM
if ((problem_detected & AGENT_PROBLEM_PARENT_HAS_GONE))
{
shutdown_pending = 2;
log_info ("parent process died - shutting down\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
agent_exit (0);
}
#endif
if ((problem_detected & AGENT_PROBLEM_SOCKET_TAKEOVER))
{
/* We may not remove the socket as it is now in use by another
server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
if ((problem_detected & AGENT_PROBLEM_HOMEDIR_REMOVED))
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
if (ret <= 0)
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, GPG_AGENT_SOCK_NAME))
{
/* We may not remove the socket (if any), as it may be now
in use by another server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = assuan_sock_accept (listentbl[idx].l_fd,
(struct sockaddr *)&paddr, &plen);
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else if ( !(ctrl->session_env = session_env_new ()))
{
log_error ("error allocating session env block for %s: %s\n",
listentbl[idx].name, strerror (errno) );
xfree (ctrl);
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
#ifdef HAVE_W32_SYSTEM
if (the_event2 != INVALID_HANDLE_VALUE)
CloseHandle (the_event2);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
npth_attr_destroy (&tattr);
}
#if CHECK_OWN_SOCKET_INTERVAL > 0
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* Check whether we are still listening on our own socket. In case
another gpg-agent process started after us has taken ownership of
our socket, we would linger around without any real task. Thus we
better check once in a while whether we are really needed. */
static int
do_check_own_socket (const char *sockname)
{
int rc;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
if (ctx)
assuan_release (ctx);
return rc;
}
/* The thread running the actual check. */
static void *
check_own_socket_thread (void *arg)
{
char *sockname;
(void)arg;
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return NULL; /* Out of memory. */
while (!problem_detected)
{
if (shutdown_pending)
goto leave;
gnupg_sleep (CHECK_OWN_SOCKET_INTERVAL);
if (do_check_own_socket (sockname))
problem_detected |= AGENT_PROBLEM_SOCKET_TAKEOVER;
}
agent_kick_the_loop ();
leave:
xfree (sockname);
return NULL;
}
#endif
/* The thread running other checks. */
static void *
check_others_thread (void *arg)
{
const char *homedir = gnupg_homedir ();
(void)arg;
while (!problem_detected)
{
struct stat statbuf;
if (shutdown_pending)
goto leave;
gnupg_sleep (CHECK_PROBLEMS_INTERVAL);
/* If we are running as a child of another process, check whether
the parent is still alive and shutdown if not. */
#ifndef HAVE_W32_SYSTEM
if (parent_pid != (pid_t)(-1) && kill (parent_pid, 0))
problem_detected |= AGENT_PROBLEM_PARENT_HAS_GONE;
#endif /*HAVE_W32_SYSTEM*/
/* Check whether the homedir is still available. */
if (!have_homedir_inotify
&& gnupg_stat (homedir, &statbuf) && errno == ENOENT)
problem_detected |= AGENT_PROBLEM_HOMEDIR_REMOVED;
}
agent_kick_the_loop ();
leave:
return NULL;
}
/* Figure out whether an agent is available and running. Prints an
error if not. If SILENT is true, no messages are printed.
Returns 0 if the agent is running. */
static int
check_for_running_agent (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no gpg-agent running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("gpg-agent running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/agent/protect.c b/agent/protect.c
index 7197cf7e6..6c9bbaebc 100644
--- a/agent/protect.c
+++ b/agent/protect.c
@@ -1,1775 +1,1775 @@
/* protect.c - Un/Protect a secret key
* Copyright (C) 1998-2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* Copyright (C) 1998-2003, 2007, 2009, 2011, 2013-2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else
# include <sys/times.h>
#endif
#include "agent.h"
#include "cvt-openpgp.h"
#include "../common/sexp-parse.h"
#include "../common/openpgpdefs.h" /* For s2k functions. */
/* The protection mode for encryption. The supported modes for
decryption are listed in agent_unprotect(). */
#define PROT_CIPHER GCRY_CIPHER_AES128
#define PROT_CIPHER_STRING "aes"
#define PROT_CIPHER_KEYLEN (128/8)
/* A table containing the information needed to create a protected
private key. */
static const struct {
const char *algo;
const char *parmlist;
int prot_from, prot_to;
int ecc_hack;
} protect_info[] = {
{ "rsa", "nedpqu", 2, 5 },
{ "dsa", "pqgyx", 4, 4 },
{ "elg", "pgyx", 3, 3 },
{ "ecdsa","pabgnqd", 6, 6, 1 },
{ "ecdh", "pabgnqd", 6, 6, 1 },
{ "ecc", "pabgnqd", 6, 6, 1 },
{ NULL }
};
/* The number of milliseconds we use in the S2K function and the
* calibrated count value. A count value of zero indicates that the
* calibration has not yet been done or needs to be done again. */
static unsigned int s2k_calibration_time = AGENT_S2K_CALIBRATION;
static unsigned long s2k_calibrated_count;
/* A helper object for time measurement. */
struct calibrate_time_s
{
#ifdef HAVE_W32_SYSTEM
FILETIME creation_time, exit_time, kernel_time, user_time;
#else
clock_t ticks;
#endif
};
static int
hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt, unsigned long s2kcount,
unsigned char *key, size_t keylen);
/*
* Determine if we can use clock_gettime with CLOCK_THREAD_CPUTIME_ID,
* at compile time.
*/
#if defined (CLOCK_THREAD_CPUTIME_ID)
# if _POSIX_THREAD_CPUTIME > 0
# define USE_CLOCK_GETTIME 1
# elif _POSIX_THREAD_CPUTIME == 0
/*
* In this case, we should check sysconf with _POSIX_THREAD_CPUTIME at
* run time. As heuristics, for system with newer GNU C library, we
* can assume it is available.
*/
# if __GLIBC__ >= 2 && __GLIBC_MINOR__ >= 17
# define USE_CLOCK_GETTIME 1
# endif
# endif
#else
#undef USE_CLOCK_GETTIME
#endif
/* Get the process time and store it in DATA. */
static void
calibrate_get_time (struct calibrate_time_s *data)
{
#ifdef HAVE_W32_SYSTEM
GetProcessTimes (GetCurrentProcess (),
&data->creation_time, &data->exit_time,
&data->kernel_time, &data->user_time);
#elif defined (USE_CLOCK_GETTIME)
struct timespec tmp;
clock_gettime (CLOCK_THREAD_CPUTIME_ID, &tmp);
data->ticks = (clock_t)(((unsigned long long)tmp.tv_sec * 1000000000 +
tmp.tv_nsec) * CLOCKS_PER_SEC / 1000000000);
#else
data->ticks = clock ();
#endif
}
static unsigned long
calibrate_elapsed_time (struct calibrate_time_s *starttime)
{
struct calibrate_time_s stoptime;
calibrate_get_time (&stoptime);
#ifdef HAVE_W32_SYSTEM
{
unsigned long long t1, t2;
t1 = (((unsigned long long)starttime->kernel_time.dwHighDateTime << 32)
+ starttime->kernel_time.dwLowDateTime);
t1 += (((unsigned long long)starttime->user_time.dwHighDateTime << 32)
+ starttime->user_time.dwLowDateTime);
t2 = (((unsigned long long)stoptime.kernel_time.dwHighDateTime << 32)
+ stoptime.kernel_time.dwLowDateTime);
t2 += (((unsigned long long)stoptime.user_time.dwHighDateTime << 32)
+ stoptime.user_time.dwLowDateTime);
return (unsigned long)((t2 - t1)/10000);
}
#else
return (unsigned long)((((double) (stoptime.ticks - starttime->ticks))
/CLOCKS_PER_SEC)*1000);
#endif
}
/* Run a test hashing for COUNT and return the time required in
milliseconds. */
static unsigned long
calibrate_s2k_count_one (unsigned long count)
{
int rc;
char keybuf[PROT_CIPHER_KEYLEN];
struct calibrate_time_s starttime;
calibrate_get_time (&starttime);
rc = hash_passphrase ("123456789abcdef0", GCRY_MD_SHA1,
3, "saltsalt", count, keybuf, sizeof keybuf);
if (rc)
BUG ();
return calibrate_elapsed_time (&starttime);
}
/* Measure the time we need to do the hash operations and deduce an
S2K count which requires roughly some targeted amount of time. */
static unsigned long
calibrate_s2k_count (void)
{
unsigned long count;
unsigned long ms;
for (count = 65536; count; count *= 2)
{
ms = calibrate_s2k_count_one (count);
if (opt.verbose > 1)
log_info ("S2K calibration: %lu -> %lums\n", count, ms);
if (ms > s2k_calibration_time)
break;
}
count = (unsigned long)(((double)count / ms) * s2k_calibration_time);
count /= 1024;
count *= 1024;
if (count < 65536)
count = 65536;
if (opt.verbose)
{
ms = calibrate_s2k_count_one (count);
log_info ("S2K calibration: %lu -> %lums\n", count, ms);
}
return count;
}
/* Set the calibration time. This may be called early at startup or
* at any time. Thus it should one set variables. */
void
set_s2k_calibration_time (unsigned int milliseconds)
{
if (!milliseconds)
milliseconds = AGENT_S2K_CALIBRATION;
else if (milliseconds > 60 * 1000)
milliseconds = 60 * 1000; /* Cap at 60 seconds. */
s2k_calibration_time = milliseconds;
s2k_calibrated_count = 0; /* Force re-calibration. */
}
/* Return the calibrated S2K count. This is only public for the use
* of the Assuan getinfo s2k_count_cal command. */
unsigned long
get_calibrated_s2k_count (void)
{
if (!s2k_calibrated_count)
s2k_calibrated_count = calibrate_s2k_count ();
/* Enforce a lower limit. */
return s2k_calibrated_count < 65536 ? 65536 : s2k_calibrated_count;
}
/* Return the standard S2K count. */
unsigned long
get_standard_s2k_count (void)
{
if (opt.s2k_count)
return opt.s2k_count < 65536 ? 65536 : opt.s2k_count;
return get_calibrated_s2k_count ();
}
/* Return the milliseconds required for the standard S2K
* operation. */
unsigned long
get_standard_s2k_time (void)
{
return calibrate_s2k_count_one (get_standard_s2k_count ());
}
/* Same as get_standard_s2k_count but return the count in the encoding
as described by rfc4880. */
unsigned char
get_standard_s2k_count_rfc4880 (void)
{
unsigned long iterations;
unsigned int count;
unsigned char result;
unsigned char c=0;
iterations = get_standard_s2k_count ();
if (iterations >= 65011712)
return 255;
/* Need count to be in the range 16-31 */
for (count=iterations>>6; count>=32; count>>=1)
c++;
result = (c<<4)|(count-16);
if (S2K_DECODE_COUNT(result) < iterations)
result++;
return result;
}
/* Calculate the MIC for a private key or shared secret S-expression.
SHA1HASH should point to a 20 byte buffer. This function is
suitable for all algorithms. */
static gpg_error_t
calculate_mic (const unsigned char *plainkey, unsigned char *sha1hash)
{
const unsigned char *hash_begin, *hash_end;
const unsigned char *s;
size_t n;
int is_shared_secret;
s = plainkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "private-key"))
is_shared_secret = 0;
else if (smatch (&s, n, "shared-secret"))
is_shared_secret = 1;
else
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
hash_begin = s;
if (!is_shared_secret)
{
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* Skip the algorithm name. */
}
while (*s == '(')
{
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
if ( *s != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
s++;
}
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
hash_end = s;
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash,
hash_begin, hash_end - hash_begin);
return 0;
}
/* Encrypt the parameter block starting at PROTBEGIN with length
PROTLEN using the utf8 encoded key PASSPHRASE and return the entire
encrypted block in RESULT or return with an error code. SHA1HASH
is the 20 byte SHA-1 hash required for the integrity code.
The parameter block is expected to be an incomplete canonical
encoded S-Expression of the form (example in advanced format):
(d #046129F..[some bytes not shown]..81#)
(p #00e861b..[some bytes not shown]..f1#)
(q #00f7a7c..[some bytes not shown]..61#)
(u #304559a..[some bytes not shown]..9b#)
the returned block is the S-Expression:
(protected mode (parms) encrypted_octet_string)
*/
static int
do_encryption (const unsigned char *hashbegin, size_t hashlen,
const unsigned char *protbegin, size_t protlen,
const char *passphrase,
const char *timestamp_exp, size_t timestamp_exp_len,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count)
{
gcry_cipher_hd_t hd;
const char *modestr;
int enclen, outlen;
unsigned char *iv = NULL;
unsigned int ivsize; /* Size of the buffer allocated for IV. */
const unsigned char *s2ksalt; /* Points into IV. */
int rc;
char *outbuf = NULL;
char *p;
int saltpos, ivpos, encpos;
s2ksalt = iv; /* Silence compiler warning. */
*resultlen = 0;
*result = NULL;
modestr = "openpgp-s2k3-ocb-aes";
rc = gcry_cipher_open (&hd, PROT_CIPHER,
GCRY_CIPHER_MODE_OCB,
GCRY_CIPHER_SECURE);
if (rc)
return rc;
/* We need to work on a copy of the data because this makes it
* easier to add the trailer and the padding and more important we
* have to prefix the text with 2 parenthesis. Due to OCB mode we
* have to allocate enough space for just:
*
* ((<parameter_list>))
*/
/* (( )) */
outlen = 2 + protlen + 2 ;
enclen = outlen + 16 /* taglen */;
outbuf = gcry_malloc_secure (enclen);
if (!outbuf)
{
rc = out_of_core ();
goto leave;
}
/* Allocate a buffer for the nonce and the salt. */
if (!rc)
{
/* Allocate random bytes to be used as nonce and s2k salt. The
* nonce is set later because for OCB we need to set the key
* first. */
ivsize = 12 + 8;
iv = xtrymalloc (ivsize);
if (!iv)
rc = gpg_error_from_syserror ();
else
{
gcry_create_nonce (iv, ivsize);
s2ksalt = iv + ivsize - 8;
}
}
/* Hash the passphrase and set the key. */
if (!rc)
{
unsigned char *key;
size_t keylen = PROT_CIPHER_KEYLEN;
key = gcry_malloc_secure (keylen);
if (!key)
rc = out_of_core ();
else
{
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
3, s2ksalt,
s2k_count? s2k_count:get_standard_s2k_count(),
key, keylen);
if (!rc)
rc = gcry_cipher_setkey (hd, key, keylen);
xfree (key);
}
}
if (rc)
goto leave;
/* Set the IV/nonce. */
rc = gcry_cipher_setiv (hd, iv, 12);
if (rc)
goto leave;
/* In OCB Mode we use only the public key parameters as AAD. */
rc = gcry_cipher_authenticate (hd, hashbegin, protbegin - hashbegin);
if (!rc)
rc = gcry_cipher_authenticate (hd, timestamp_exp, timestamp_exp_len);
if (!rc)
rc = gcry_cipher_authenticate
(hd, protbegin+protlen, hashlen - (protbegin+protlen - hashbegin));
/* Encrypt. */
if (!rc)
{
p = outbuf;
*p++ = '(';
*p++ = '(';
memcpy (p, protbegin, protlen);
p += protlen;
*p++ = ')';
*p++ = ')';
log_assert ( p - outbuf == outlen);
gcry_cipher_final (hd);
rc = gcry_cipher_encrypt (hd, outbuf, outlen, NULL, 0);
if (!rc)
{
log_assert (outlen + 16 == enclen);
rc = gcry_cipher_gettag (hd, outbuf + outlen, 16);
}
}
if (rc)
goto leave;
/* Release cipher handle and check for errors. */
gcry_cipher_close (hd);
/* Now allocate the buffer we want to return. This is
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 salt no_of_iterations) 16byte_iv)
encrypted_octet_string)
- in canoncical format of course. We use asprintf and %n modifier
+ in canonical format of course. We use asprintf and %n modifier
and dummy values as placeholders. */
{
char countbuf[35];
snprintf (countbuf, sizeof countbuf, "%lu",
s2k_count ? s2k_count : get_standard_s2k_count ());
p = xtryasprintf
("(9:protected%d:%s((4:sha18:%n_8bytes_%u:%s)%d:%n%*s)%d:%n%*s)",
(int)strlen (modestr), modestr,
&saltpos,
(unsigned int)strlen (countbuf), countbuf,
12, &ivpos, 12, "",
enclen, &encpos, enclen, "");
if (!p)
{
gpg_error_t tmperr = out_of_core ();
xfree (iv);
xfree (outbuf);
return tmperr;
}
}
*resultlen = strlen (p);
*result = (unsigned char*)p;
memcpy (p+saltpos, s2ksalt, 8);
memcpy (p+ivpos, iv, 12);
memcpy (p+encpos, outbuf, enclen);
xfree (iv);
xfree (outbuf);
return 0;
leave:
gcry_cipher_close (hd);
xfree (iv);
xfree (outbuf);
return rc;
}
/* Protect the key encoded in canonical format in PLAINKEY. We assume
* a valid S-Exp here. */
int
agent_protect (const unsigned char *plainkey, const char *passphrase,
unsigned char **result, size_t *resultlen,
unsigned long s2k_count)
{
int rc;
const char *parmlist;
int prot_from_idx, prot_to_idx;
const unsigned char *s;
const unsigned char *hash_begin, *hash_end;
const unsigned char *prot_begin, *prot_end, *real_end;
size_t n;
int c, infidx, i;
char timestamp_exp[35];
unsigned char *protected;
size_t protectedlen;
int depth = 0;
unsigned char *p;
int have_curve = 0;
/* Create an S-expression with the protected-at timestamp. */
memcpy (timestamp_exp, "(12:protected-at15:", 19);
gnupg_get_isotime (timestamp_exp+19);
timestamp_exp[19+15] = ')';
/* Parse original key. */
s = plainkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
hash_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
for (infidx=0; protect_info[infidx].algo
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
;
if (!protect_info[infidx].algo)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
/* The parser below is a complete mess: To make it robust for ECC
use we should reorder the s-expression to include only what we
really need and thus guarantee the right order for saving stuff.
This should be done before calling this function and maybe with
the help of the new gcry_sexp_extract_param. */
parmlist = protect_info[infidx].parmlist;
prot_from_idx = protect_info[infidx].prot_from;
prot_to_idx = protect_info[infidx].prot_to;
prot_begin = prot_end = NULL;
for (i=0; (c=parmlist[i]); i++)
{
if (i == prot_from_idx)
prot_begin = s;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (n != 1 || c != *s)
{
if (n == 5 && !memcmp (s, "curve", 5)
&& !i && protect_info[infidx].ecc_hack)
{
/* This is a private ECC key but the first parameter is
the name of the curve. We change the parameter list
here to the one we expect in this case. */
have_curve = 1;
parmlist = "?qd";
prot_from_idx = 2;
prot_to_idx = 2;
}
else if (n == 5 && !memcmp (s, "flags", 5)
&& i == 1 && have_curve)
{
/* "curve" followed by "flags": Change again. */
parmlist = "??qd";
prot_from_idx = 3;
prot_to_idx = 3;
}
else
return gpg_error (GPG_ERR_INV_SEXP);
}
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
if (i == prot_to_idx)
prot_end = s;
s++;
}
if (*s != ')' || !prot_begin || !prot_end )
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
hash_end = s;
s++;
/* Skip to the end of the S-expression. */
log_assert (depth == 1);
rc = sskip (&s, &depth);
if (rc)
return rc;
log_assert (!depth);
real_end = s-1;
rc = do_encryption (hash_begin, hash_end - hash_begin + 1,
prot_begin, prot_end - prot_begin + 1,
passphrase, timestamp_exp, sizeof (timestamp_exp),
&protected, &protectedlen, s2k_count);
if (rc)
return rc;
/* Now create the protected version of the key. Note that the 10
extra bytes are for the inserted "protected-" string (the
beginning of the plaintext reads: "((11:private-key(" ). The 35
term is the space for (12:protected-at15:<timestamp>). */
*resultlen = (10
+ (prot_begin-plainkey)
+ protectedlen
+ 35
+ (real_end-prot_end));
*result = p = xtrymalloc (*resultlen);
if (!p)
{
gpg_error_t tmperr = out_of_core ();
xfree (protected);
return tmperr;
}
memcpy (p, "(21:protected-", 14);
p += 14;
memcpy (p, plainkey+4, prot_begin - plainkey - 4);
p += prot_begin - plainkey - 4;
memcpy (p, protected, protectedlen);
p += protectedlen;
memcpy (p, timestamp_exp, 35);
p += 35;
memcpy (p, prot_end+1, real_end - prot_end);
p += real_end - prot_end;
log_assert ( p - *result == *resultlen);
xfree (protected);
return 0;
}
/* Do the actual decryption and check the return list for consistency. */
static gpg_error_t
do_decryption (const unsigned char *aad_begin, size_t aad_len,
const unsigned char *aadhole_begin, size_t aadhole_len,
const unsigned char *protected, size_t protectedlen,
const char *passphrase,
const unsigned char *s2ksalt, unsigned long s2kcount,
const unsigned char *iv, size_t ivlen,
int prot_cipher, int prot_cipher_keylen, int is_ocb,
unsigned char **result)
{
int rc;
int blklen;
gcry_cipher_hd_t hd;
unsigned char *outbuf;
size_t reallen;
blklen = gcry_cipher_get_algo_blklen (prot_cipher);
if (is_ocb)
{
/* OCB does not require a multiple of the block length but we
* check that it is long enough for the 128 bit tag and that we
* have the 96 bit nonce. */
if (protectedlen < (4 + 16) || ivlen != 12)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
else
{
if (protectedlen < 4 || (protectedlen%blklen))
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
rc = gcry_cipher_open (&hd, prot_cipher,
is_ocb? GCRY_CIPHER_MODE_OCB :
GCRY_CIPHER_MODE_CBC,
GCRY_CIPHER_SECURE);
if (rc)
return rc;
outbuf = gcry_malloc_secure (protectedlen);
if (!outbuf)
rc = out_of_core ();
/* Hash the passphrase and set the key. */
if (!rc)
{
unsigned char *key;
key = gcry_malloc_secure (prot_cipher_keylen);
if (!key)
rc = out_of_core ();
else
{
rc = hash_passphrase (passphrase, GCRY_MD_SHA1,
3, s2ksalt, s2kcount, key, prot_cipher_keylen);
if (!rc)
rc = gcry_cipher_setkey (hd, key, prot_cipher_keylen);
xfree (key);
}
}
/* Set the IV/nonce. */
if (!rc)
{
rc = gcry_cipher_setiv (hd, iv, ivlen);
}
/* Decrypt. */
if (!rc)
{
if (is_ocb)
{
rc = gcry_cipher_authenticate (hd, aad_begin,
aadhole_begin - aad_begin);
if (!rc)
rc = gcry_cipher_authenticate
(hd, aadhole_begin + aadhole_len,
aad_len - (aadhole_begin+aadhole_len - aad_begin));
if (!rc)
{
gcry_cipher_final (hd);
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen - 16,
protected, protectedlen - 16);
}
if (!rc)
{
rc = gcry_cipher_checktag (hd, protected + protectedlen - 16, 16);
if (gpg_err_code (rc) == GPG_ERR_CHECKSUM)
{
/* Return Bad Passphrase instead of checksum error */
rc = gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
}
}
else
{
rc = gcry_cipher_decrypt (hd, outbuf, protectedlen,
protected, protectedlen);
}
}
/* Release cipher handle and check for errors. */
gcry_cipher_close (hd);
if (rc)
{
xfree (outbuf);
return rc;
}
/* Do a quick check on the data structure. */
if (*outbuf != '(' && outbuf[1] != '(')
{
xfree (outbuf);
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
/* Check that we have a consistent S-Exp. */
reallen = gcry_sexp_canon_len (outbuf, protectedlen, NULL, NULL);
if (!reallen || (reallen + blklen < protectedlen) )
{
xfree (outbuf);
return gpg_error (GPG_ERR_BAD_PASSPHRASE);
}
*result = outbuf;
return 0;
}
/* Merge the parameter list contained in CLEARTEXT with the original
* protect lists PROTECTEDKEY by replacing the list at REPLACEPOS.
* Return the new list in RESULT and the MIC value in the 20 byte
* buffer SHA1HASH; if SHA1HASH is NULL no MIC will be computed.
* CUTOFF and CUTLEN will receive the offset and the length of the
* resulting list which should go into the MIC calculation but then be
* removed. */
static gpg_error_t
merge_lists (const unsigned char *protectedkey,
size_t replacepos,
const unsigned char *cleartext,
unsigned char *sha1hash,
unsigned char **result, size_t *resultlen,
size_t *cutoff, size_t *cutlen)
{
size_t n, newlistlen;
unsigned char *newlist, *p;
const unsigned char *s;
const unsigned char *startpos, *endpos;
int i, rc;
*result = NULL;
*resultlen = 0;
*cutoff = 0;
*cutlen = 0;
if (replacepos < 26)
return gpg_error (GPG_ERR_BUG);
/* Estimate the required size of the resulting list. We have a large
safety margin of >20 bytes (FIXME: MIC hash from CLEARTEXT and the
removed "protected-" */
newlistlen = gcry_sexp_canon_len (protectedkey, 0, NULL, NULL);
if (!newlistlen)
return gpg_error (GPG_ERR_BUG);
n = gcry_sexp_canon_len (cleartext, 0, NULL, NULL);
if (!n)
return gpg_error (GPG_ERR_BUG);
newlistlen += n;
newlist = gcry_malloc_secure (newlistlen);
if (!newlist)
return out_of_core ();
/* Copy the initial segment */
strcpy ((char*)newlist, "(11:private-key");
p = newlist + 15;
memcpy (p, protectedkey+15+10, replacepos-15-10);
p += replacepos-15-10;
/* Copy the cleartext. */
s = cleartext;
if (*s != '(' && s[1] != '(')
{
xfree (newlist);
return gpg_error (GPG_ERR_BUG); /*we already checked this */
}
s += 2;
startpos = s;
while ( *s == '(' )
{
s++;
n = snext (&s);
if (!n)
goto invalid_sexp;
s += n;
n = snext (&s);
if (!n)
goto invalid_sexp;
s += n;
if ( *s != ')' )
goto invalid_sexp;
s++;
}
if ( *s != ')' )
goto invalid_sexp;
endpos = s;
s++;
/* Intermezzo: Get the MIC if requested. */
if (sha1hash)
{
if (*s != '(')
goto invalid_sexp;
s++;
n = snext (&s);
if (!smatch (&s, n, "hash"))
goto invalid_sexp;
n = snext (&s);
if (!smatch (&s, n, "sha1"))
goto invalid_sexp;
n = snext (&s);
if (n != 20)
goto invalid_sexp;
memcpy (sha1hash, s, 20);
s += n;
if (*s != ')')
goto invalid_sexp;
}
/* Append the parameter list. */
memcpy (p, startpos, endpos - startpos);
p += endpos - startpos;
/* Skip over the protected list element in the original list. */
s = protectedkey + replacepos;
log_assert (*s == '(');
s++;
i = 1;
rc = sskip (&s, &i);
if (rc)
goto failure;
/* Record the position of the optional protected-at expression. */
if (*s == '(')
{
const unsigned char *save_s = s;
s++;
n = snext (&s);
if (smatch (&s, n, "protected-at"))
{
i = 1;
rc = sskip (&s, &i);
if (rc)
goto failure;
*cutlen = s - save_s;
}
s = save_s;
}
startpos = s;
i = 2; /* we are inside this level */
rc = sskip (&s, &i);
if (rc)
goto failure;
log_assert (s[-1] == ')');
endpos = s; /* one behind the end of the list */
/* Append the rest. */
if (*cutlen)
*cutoff = p - newlist;
memcpy (p, startpos, endpos - startpos);
p += endpos - startpos;
/* ready */
*result = newlist;
*resultlen = newlistlen;
return 0;
failure:
wipememory (newlist, newlistlen);
xfree (newlist);
return rc;
invalid_sexp:
wipememory (newlist, newlistlen);
xfree (newlist);
return gpg_error (GPG_ERR_INV_SEXP);
}
/* Unprotect the key encoded in canonical format. We assume a valid
S-Exp here. If a protected-at item is available, its value will
be stored at protected_at unless this is NULL. */
gpg_error_t
agent_unprotect (ctrl_t ctrl,
const unsigned char *protectedkey, const char *passphrase,
gnupg_isotime_t protected_at,
unsigned char **result, size_t *resultlen)
{
static const struct {
const char *name; /* Name of the protection method. */
int algo; /* (A zero indicates the "openpgp-native" hack.) */
int keylen; /* Used key length in bytes. */
unsigned int is_ocb:1;
} algotable[] = {
{ "openpgp-s2k3-sha1-aes-cbc", GCRY_CIPHER_AES128, (128/8)},
{ "openpgp-s2k3-sha1-aes256-cbc", GCRY_CIPHER_AES256, (256/8)},
{ "openpgp-s2k3-ocb-aes", GCRY_CIPHER_AES128, (128/8), 1},
{ "openpgp-native", 0, 0 }
};
int rc;
const unsigned char *s;
const unsigned char *protect_list;
size_t n;
int infidx, i;
unsigned char sha1hash[20], sha1hash2[20];
const unsigned char *s2ksalt;
unsigned long s2kcount;
const unsigned char *iv;
int prot_cipher, prot_cipher_keylen;
int is_ocb;
const unsigned char *aad_begin, *aad_end, *aadhole_begin, *aadhole_end;
const unsigned char *prot_begin;
unsigned char *cleartext;
unsigned char *final;
size_t finallen;
size_t cutoff, cutlen;
if (protected_at)
*protected_at = 0;
s = protectedkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "protected-private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
{
aad_begin = aad_end = s;
aad_end++;
i = 1;
rc = sskip (&aad_end, &i);
if (rc)
return rc;
}
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
for (infidx=0; protect_info[infidx].algo
&& !smatch (&s, n, protect_info[infidx].algo); infidx++)
;
if (!protect_info[infidx].algo)
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
/* See whether we have a protected-at timestamp. */
protect_list = s; /* Save for later. */
if (protected_at)
{
while (*s == '(')
{
prot_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "protected-at"))
{
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (n != 15)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
memcpy (protected_at, s, 15);
protected_at[15] = 0;
break;
}
s += n;
i = 1;
rc = sskip (&s, &i);
if (rc)
return rc;
}
}
/* Now find the list with the protected information. Here is an
example for such a list:
(protected openpgp-s2k3-sha1-aes-cbc
((sha1 <salt> <count>) <Initialization_Vector>)
<encrypted_data>)
*/
s = protect_list;
for (;;)
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
prot_begin = s;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "protected"))
break;
s += n;
i = 1;
rc = sskip (&s, &i);
if (rc)
return rc;
}
/* found */
{
aadhole_begin = aadhole_end = prot_begin;
aadhole_end++;
i = 1;
rc = sskip (&aadhole_end, &i);
if (rc)
return rc;
}
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
/* Lookup the protection algo. */
prot_cipher = 0; /* (avoid gcc warning) */
prot_cipher_keylen = 0; /* (avoid gcc warning) */
is_ocb = 0;
for (i=0; i < DIM (algotable); i++)
if (smatch (&s, n, algotable[i].name))
{
prot_cipher = algotable[i].algo;
prot_cipher_keylen = algotable[i].keylen;
is_ocb = algotable[i].is_ocb;
break;
}
if (i == DIM (algotable))
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
if (!prot_cipher) /* This is "openpgp-native". */
{
gcry_sexp_t s_prot_begin;
rc = gcry_sexp_sscan (&s_prot_begin, NULL,
prot_begin,
gcry_sexp_canon_len (prot_begin, 0,NULL,NULL));
if (rc)
return rc;
rc = convert_from_openpgp_native (ctrl, s_prot_begin, passphrase, &final);
gcry_sexp_release (s_prot_begin);
if (!rc)
{
*result = final;
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
}
return rc;
}
if (*s != '(' || s[1] != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s += 2;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "sha1"))
return gpg_error (GPG_ERR_UNSUPPORTED_PROTECTION);
n = snext (&s);
if (n != 8)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
s2ksalt = s;
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
/* We expect a list close as next, so we can simply use strtoul()
here. We might want to check that we only have digits - but this
is nothing we should worry about */
if (s[n] != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
/* Old versions of gpg-agent used the funny floating point number in
a byte encoding as specified by OpenPGP. However this is not
needed and thus we now store it as a plain unsigned integer. We
can easily distinguish the old format by looking at its value:
Less than 256 is an old-style encoded number; other values are
plain integers. In any case we check that they are at least
65536 because we never used a lower value in the past and we
should have a lower limit. */
s2kcount = strtoul ((const char*)s, NULL, 10);
if (!s2kcount)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
if (s2kcount < 256)
s2kcount = (16ul + (s2kcount & 15)) << ((s2kcount >> 4) + 6);
if (s2kcount < 65536)
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
s += n;
s++; /* skip list end */
n = snext (&s);
if (is_ocb)
{
if (n != 12) /* Wrong size of the nonce. */
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
else
{
if (n != 16) /* Wrong blocksize for IV (we support only 128 bit). */
return gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
}
iv = s;
s += n;
if (*s != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
cleartext = NULL; /* Avoid cc warning. */
rc = do_decryption (aad_begin, aad_end - aad_begin,
aadhole_begin, aadhole_end - aadhole_begin,
s, n,
passphrase, s2ksalt, s2kcount,
iv, is_ocb? 12:16,
prot_cipher, prot_cipher_keylen, is_ocb,
&cleartext);
if (rc)
return rc;
rc = merge_lists (protectedkey, prot_begin-protectedkey, cleartext,
is_ocb? NULL : sha1hash,
&final, &finallen, &cutoff, &cutlen);
/* Albeit cleartext has been allocated in secure memory and thus
xfree will wipe it out, we do an extra wipe just in case
somethings goes badly wrong. */
wipememory (cleartext, n);
xfree (cleartext);
if (rc)
return rc;
if (!is_ocb)
{
rc = calculate_mic (final, sha1hash2);
if (!rc && memcmp (sha1hash, sha1hash2, 20))
rc = gpg_error (GPG_ERR_CORRUPTED_PROTECTION);
if (rc)
{
wipememory (final, finallen);
xfree (final);
return rc;
}
}
/* Now remove the part which is included in the MIC but should not
go into the final thing. */
if (cutlen)
{
memmove (final+cutoff, final+cutoff+cutlen, finallen-cutoff-cutlen);
finallen -= cutlen;
}
*result = final;
*resultlen = gcry_sexp_canon_len (final, 0, NULL, NULL);
return 0;
}
/* Check the type of the private key, this is one of the constants:
PRIVATE_KEY_UNKNOWN if we can't figure out the type (this is the
value 0), PRIVATE_KEY_CLEAR for an unprotected private key.
PRIVATE_KEY_PROTECTED for an protected private key or
PRIVATE_KEY_SHADOWED for a sub key where the secret parts are
stored elsewhere. Finally PRIVATE_KEY_OPENPGP_NONE may be returned
is the key is still in the openpgp-native format but without
protection. */
int
agent_private_key_type (const unsigned char *privatekey)
{
const unsigned char *s;
size_t n;
int i;
s = privatekey;
if (*s != '(')
return PRIVATE_KEY_UNKNOWN;
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN;
if (smatch (&s, n, "protected-private-key"))
{
/* We need to check whether this is openpgp-native protected
with the protection method "none". In that case we return a
different key type so that the caller knows that there is no
need to ask for a passphrase. */
if (*s != '(')
return PRIVATE_KEY_PROTECTED; /* Unknown sexp - assume protected. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s += n; /* Skip over the algo */
/* Find the (protected ...) list. */
for (;;)
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "protected"))
break;
s += n;
i = 1;
if (sskip (&s, &i))
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
}
/* Found - Is this openpgp-native? */
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "openpgp-native")) /* Yes. */
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Unknown sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s += n; /* Skip over "openpgp-private-key". */
/* Find the (protection ...) list. */
for (;;)
{
if (*s != '(')
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
s++;
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "protection"))
break;
s += n;
i = 1;
if (sskip (&s, &i))
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
}
/* Found - Is the mode "none"? */
n = snext (&s);
if (!n)
return PRIVATE_KEY_UNKNOWN; /* Invalid sexp. */
if (smatch (&s, n, "none"))
return PRIVATE_KEY_OPENPGP_NONE; /* Yes. */
}
return PRIVATE_KEY_PROTECTED;
}
if (smatch (&s, n, "shadowed-private-key"))
return PRIVATE_KEY_SHADOWED;
if (smatch (&s, n, "private-key"))
return PRIVATE_KEY_CLEAR;
return PRIVATE_KEY_UNKNOWN;
}
/* Transform a passphrase into a suitable key of length KEYLEN and
store this key in the caller provided buffer KEY. The caller must
provide an HASHALGO, a valid S2KMODE (see rfc-2440) and depending on
that mode an S2KSALT of 8 random bytes and an S2KCOUNT.
Returns an error code on failure. */
static int
hash_passphrase (const char *passphrase, int hashalgo,
int s2kmode,
const unsigned char *s2ksalt,
unsigned long s2kcount,
unsigned char *key, size_t keylen)
{
/* The key derive function does not support a zero length string for
the passphrase in the S2K modes. Return a better suited error
code than GPG_ERR_INV_DATA. */
if (!passphrase || !*passphrase)
return gpg_error (GPG_ERR_NO_PASSPHRASE);
return gcry_kdf_derive (passphrase, strlen (passphrase),
s2kmode == 3? GCRY_KDF_ITERSALTED_S2K :
s2kmode == 1? GCRY_KDF_SALTED_S2K :
s2kmode == 0? GCRY_KDF_SIMPLE_S2K : GCRY_KDF_NONE,
hashalgo, s2ksalt, 8, s2kcount,
keylen, key);
}
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)
{
return hash_passphrase (passphrase, hashalgo, s2kmode, s2ksalt,
S2K_DECODE_COUNT (s2kcount),
key, keylen);
}
/* Create an canonical encoded S-expression with the shadow info from
a card's SERIALNO and the IDSTRING. */
unsigned char *
make_shadow_info (const char *serialno, const char *idstring)
{
const char *s;
char *info, *p;
char numbuf[20];
size_t n;
for (s=serialno, n=0; *s && s[1]; s += 2)
n++;
info = p = xtrymalloc (1 + sizeof numbuf + n
+ sizeof numbuf + strlen (idstring) + 1 + 1);
if (!info)
return NULL;
*p++ = '(';
p = stpcpy (p, smklen (numbuf, sizeof numbuf, n, NULL));
for (s=serialno; *s && s[1]; s += 2)
*(unsigned char *)p++ = xtoi_2 (s);
p = stpcpy (p, smklen (numbuf, sizeof numbuf, strlen (idstring), NULL));
p = stpcpy (p, idstring);
*p++ = ')';
*p = 0;
return (unsigned char *)info;
}
/* Create a shadow key from a public key. We use the shadow protocol
"t1-v1" and insert the S-expressionn SHADOW_INFO. The resulting
S-expression is returned in an allocated buffer RESULT will point
to. The input parameters are expected to be valid canonicalized
S-expressions */
int
agent_shadow_key_type (const unsigned char *pubkey,
const unsigned char *shadow_info,
const unsigned char *type,
unsigned char **result)
{
const unsigned char *s;
const unsigned char *point;
size_t n;
int depth = 0;
char *p;
size_t pubkey_len = gcry_sexp_canon_len (pubkey, 0, NULL,NULL);
size_t shadow_info_len = gcry_sexp_canon_len (shadow_info, 0, NULL,NULL);
if (!pubkey_len || !shadow_info_len)
return gpg_error (GPG_ERR_INV_VALUE);
s = pubkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "public-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* skip over the algorithm name */
while (*s != ')')
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
s++;
}
point = s; /* insert right before the point */
depth--;
s++;
log_assert (depth == 1);
/* Calculate required length by taking in account: the "shadowed-"
prefix, the "shadowed", shadow type as well as some parenthesis */
n = 12 + pubkey_len + 1 + 3+8 + 2+5 + shadow_info_len + 1;
*result = xtrymalloc (n);
p = (char*)*result;
if (!p)
return out_of_core ();
p = stpcpy (p, "(20:shadowed-private-key");
/* (10:public-key ...)*/
memcpy (p, pubkey+14, point - (pubkey+14));
p += point - (pubkey+14);
p += sprintf (p, "(8:shadowed%d:%s", (int)strlen(type), type);
memcpy (p, shadow_info, shadow_info_len);
p += shadow_info_len;
*p++ = ')';
memcpy (p, point, pubkey_len - (point - pubkey));
p += pubkey_len - (point - pubkey);
return 0;
}
int
agent_shadow_key (const unsigned char *pubkey,
const unsigned char *shadow_info,
unsigned char **result)
{
return agent_shadow_key_type (pubkey, shadow_info, "t1-v1", result);
}
/* Parse a canonical encoded shadowed key and return a pointer to the
inner list with the shadow_info and the shadow type */
gpg_error_t
agent_get_shadow_info_type (const unsigned char *shadowkey,
unsigned char const **shadow_info,
unsigned char **shadow_type)
{
const unsigned char *s, *saved_s;
size_t n, saved_n;
int depth = 0;
(void)depth;
s = shadowkey;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (!smatch (&s, n, "shadowed-private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n; /* skip over the algorithm name */
for (;;)
{
if (*s == ')')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
depth++;
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "shadowed"))
break;
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s +=n; /* skip value */
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
depth--;
s++;
}
/* Found the shadowed list, S points to the protocol */
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
saved_s = s;
saved_n = n;
if (smatch (&s, n, "t1-v1") || smatch(&s, n, "tpm2-v1"))
{
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
if (shadow_info)
*shadow_info = s;
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_PROTOCOL);
s = saved_s;
n = saved_n;
if (shadow_type)
{
char *buf = xtrymalloc(n+1);
if (!buf)
return gpg_error_from_syserror ();
memcpy (buf, s, n);
buf[n] = '\0';
*shadow_type = buf;
}
return 0;
}
gpg_error_t
agent_get_shadow_info (const unsigned char *shadowkey,
unsigned char const **shadow_info)
{
return agent_get_shadow_info_type (shadowkey, shadow_info, NULL);
}
int
agent_is_tpm2_key (gcry_sexp_t s_skey)
{
unsigned char *buf;
unsigned char *type;
size_t len;
gpg_error_t err;
err = make_canon_sexp (s_skey, &buf, &len);
if (err)
return 0;
err = agent_get_shadow_info_type (buf, NULL, &type);
xfree (buf);
if (err)
return 0;
err = strcmp (type, "tpm2-v1") == 0;
xfree (type);
return err;
}
gpg_error_t
agent_get_shadow_type (const unsigned char *shadowkey,
unsigned char **shadow_type)
{
return agent_get_shadow_info_type (shadowkey, NULL, shadow_type);
}
/* Parse the canonical encoded SHADOW_INFO S-expression. On success
the hex encoded serial number is returned as a malloced strings at
R_HEXSN and the Id string as a malloced string at R_IDSTR. On
error an error code is returned and NULL is stored at the result
parameters addresses. If the serial number or the ID string is not
required, NULL may be passed for them. Note that R_PINLEN is
currently not used by any caller. */
gpg_error_t
parse_shadow_info (const unsigned char *shadow_info,
char **r_hexsn, char **r_idstr, int *r_pinlen)
{
const unsigned char *s;
size_t n;
if (r_hexsn)
*r_hexsn = NULL;
if (r_idstr)
*r_idstr = NULL;
if (r_pinlen)
*r_pinlen = 0;
s = shadow_info;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (r_hexsn)
{
*r_hexsn = bin2hex (s, n, NULL);
if (!*r_hexsn)
return gpg_error_from_syserror ();
}
s += n;
n = snext (&s);
if (!n)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
return gpg_error (GPG_ERR_INV_SEXP);
}
if (r_idstr)
{
*r_idstr = xtrymalloc (n+1);
if (!*r_idstr)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
return gpg_error_from_syserror ();
}
memcpy (*r_idstr, s, n);
(*r_idstr)[n] = 0;
}
/* Parse the optional PINLEN. */
n = snext (&s);
if (!n)
return 0;
if (r_pinlen)
{
char *tmpstr = xtrymalloc (n+1);
if (!tmpstr)
{
if (r_hexsn)
{
xfree (*r_hexsn);
*r_hexsn = NULL;
}
if (r_idstr)
{
xfree (*r_idstr);
*r_idstr = NULL;
}
return gpg_error_from_syserror ();
}
memcpy (tmpstr, s, n);
tmpstr[n] = 0;
*r_pinlen = (int)strtol (tmpstr, NULL, 10);
xfree (tmpstr);
}
return 0;
}
diff --git a/agent/sexp-secret.c b/agent/sexp-secret.c
index ac8daa910..661681620 100644
--- a/agent/sexp-secret.c
+++ b/agent/sexp-secret.c
@@ -1,142 +1,142 @@
/* sexp-secret.c - SEXP handling of the secret key
* Copyright (C) 2020 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "agent.h"
#include "../common/sexp-parse.h"
/*
- * When it's for ECC, fixup private key part in the cannonical SEXP
+ * When it's for ECC, fixup private key part in the canonical SEXP
* representation in BUF. If not ECC, do nothing.
*/
gpg_error_t
fixup_when_ecc_private_key (unsigned char *buf, size_t *buflen_p)
{
const unsigned char *s;
char curve_name[256] = { 0, };
size_t n;
size_t buflen = *buflen_p;
s = buf;
if (*s != '(')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (smatch (&s, n, "shadowed-private-key"))
return 0; /* Nothing to do. */
if (!smatch (&s, n, "private-key"))
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if (*s != '(')
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
s++;
n = snext (&s);
if (!smatch (&s, n, "ecc"))
return 0;
/* It's ECC */
while (*s == '(')
{
s++;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
if (n == 5 && !memcmp (s, "curve", 5))
{
s += n;
n = snext (&s);
if (!n || n >= sizeof curve_name)
return gpg_error (GPG_ERR_INV_SEXP);
memcpy (curve_name, s, n);
curve_name[n] = 0;
s += n;
}
else if (n == 1 && *s == 'd')
{
unsigned char *s0;
size_t n0;
s += n;
s0 = (unsigned char *)s;
n = snext (&s);
n0 = s - s0;
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
else if (!*s /* Leading 0x00 added at the front for classic curve */
&& strcmp (curve_name, "Ed25519")
&& strcmp (curve_name, "Ed448")
&& strcmp (curve_name, "X448"))
{
size_t numsize;
n--;
buflen--;
numsize = snprintf (s0, s-s0+1, "%u:", (unsigned int)n);
memmove (s0+numsize, s+1, buflen - (s - buf));
memset (s0+numsize+buflen - (s - buf), 0, (n0 - numsize) + 1);
buflen -= (n0 - numsize);
s = s0+numsize+n;
*buflen_p = buflen;
}
else
s += n;
}
else
{
s += n;
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
}
if ( *s != ')' )
return gpg_error (GPG_ERR_INV_SEXP);
s++;
}
if (*s != ')')
return gpg_error (GPG_ERR_INV_SEXP);
s++;
return 0;
}
/*
* Scan BUF to get SEXP, put into RESULT. Error offset will be in the
* pointer at R_ERROFF. For ECC, the private part 'd' will be fixed
* up; That part may have 0x00 prefix of signed MPI encoding, which is
* incompatible to opaque MPI handling.
*/
gpg_error_t
sexp_sscan_private_key (gcry_sexp_t *result, size_t *r_erroff,
unsigned char *buf)
{
gpg_error_t err;
size_t buflen, buflen0;
buflen = buflen0 = gcry_sexp_canon_len (buf, 0, NULL, NULL);
err = fixup_when_ecc_private_key (buf, &buflen);
if (!err)
err = gcry_sexp_sscan (result, r_erroff, (char*)buf, buflen0);
wipememory (buf, buflen0);
return err;
}
diff --git a/agent/trustlist.c b/agent/trustlist.c
index fce23de15..a37705b8e 100644
--- a/agent/trustlist.c
+++ b/agent/trustlist.c
@@ -1,860 +1,860 @@
/* trustlist.c - Maintain the list of trusted keys
* Copyright (C) 2002, 2004, 2006, 2007, 2009,
* 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <sys/stat.h>
#include <npth.h>
#include "agent.h"
#include <assuan.h> /* fixme: need a way to avoid assuan calls here */
#include "../common/i18n.h"
/* A structure to store the information from the trust file. */
struct trustitem_s
{
struct
{
unsigned int disabled:1; /* This entry is disabled. */
unsigned int for_pgp:1; /* Set by '*' or 'P' as first flag. */
unsigned int for_smime:1; /* Set by '*' or 'S' as first flag. */
unsigned int relax:1; /* Relax checking of root certificate
constraints. */
unsigned int cm:1; /* Use chain model for validation. */
unsigned int qual:1; /* Root CA for qualified signatures. */
unsigned int de_vs:1; /* Root CA for de-vs compliant PKI. */
} flags;
unsigned char fpr[20]; /* The binary fingerprint. */
};
typedef struct trustitem_s trustitem_t;
/* Malloced table and its allocated size with all trust items. */
static trustitem_t *trusttable;
static size_t trusttablesize;
/* A mutex used to protect the table. */
static npth_mutex_t trusttable_lock;
static const char headerblurb[] =
"# This is the list of trusted keys. Comment lines, like this one, as\n"
"# well as empty lines are ignored. Lines have a length limit but this\n"
"# is not a serious limitation as the format of the entries is fixed and\n"
"# checked by gpg-agent. A non-comment line starts with optional white\n"
-"# space, followed by the SHA-1 fingerpint in hex, followed by a flag\n"
+"# space, followed by the SHA-1 fingerprint in hex, followed by a flag\n"
"# which may be one of 'P', 'S' or '*' and optionally followed by a list of\n"
"# other flags. The fingerprint may be prefixed with a '!' to mark the\n"
"# key as not trusted. You should give the gpg-agent a HUP or run the\n"
"# command \"gpgconf --reload gpg-agent\" after changing this file.\n"
"\n\n"
"# Include the default trust list\n"
"include-default\n"
"\n";
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
void
initialize_module_trustlist (void)
{
static int initialized;
int err;
if (!initialized)
{
err = npth_mutex_init (&trusttable_lock, NULL);
if (err)
log_fatal ("failed to init mutex in %s: %s\n", __FILE__,strerror (err));
initialized = 1;
}
}
static void
lock_trusttable (void)
{
int err;
err = npth_mutex_lock (&trusttable_lock);
if (err)
log_fatal ("failed to acquire mutex in %s: %s\n", __FILE__, strerror (err));
}
static void
unlock_trusttable (void)
{
int err;
err = npth_mutex_unlock (&trusttable_lock);
if (err)
log_fatal ("failed to release mutex in %s: %s\n", __FILE__, strerror (err));
}
/* Clear the trusttable. The caller needs to make sure that the
trusttable is locked. */
static inline void
clear_trusttable (void)
{
xfree (trusttable);
trusttable = NULL;
trusttablesize = 0;
}
/* Return the name of the system trustlist. Caller must free. */
static char *
make_sys_trustlist_name (void)
{
if (opt.sys_trustlist_name
&& (strchr (opt.sys_trustlist_name, '/')
|| strchr (opt.sys_trustlist_name, '\\')
|| (*opt.sys_trustlist_name == '~'
&& opt.sys_trustlist_name[1] == '/')))
return make_absfilename (opt.sys_trustlist_name, NULL);
else
return make_filename (gnupg_sysconfdir (),
(opt.sys_trustlist_name ?
opt.sys_trustlist_name : "trustlist.txt"),
NULL);
}
static gpg_error_t
read_one_trustfile (const char *fname, int systrust,
trustitem_t **addr_of_table,
size_t *addr_of_tablesize,
int *addr_of_tableidx)
{
gpg_error_t err = 0;
estream_t fp;
int n, c;
char *p, line[256];
trustitem_t *table, *ti;
int tableidx;
size_t tablesize;
int lnr = 0;
table = *addr_of_table;
tablesize = *addr_of_tablesize;
tableidx = *addr_of_tableidx;
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
while (es_fgets (line, DIM(line)-1, fp))
{
lnr++;
n = strlen (line);
if (!n || line[n-1] != '\n')
{
/* Eat until end of line. */
while ( (c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
continue;
}
line[--n] = 0; /* Chop the LF. */
if (n && line[n-1] == '\r')
line[--n] = 0; /* Chop an optional CR. */
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
if (!strncmp (p, "include-default", 15)
&& (!p[15] || spacep (p+15)))
{
char *etcname;
gpg_error_t err2;
gpg_err_code_t ec;
if (systrust)
{
log_error (_("statement \"%s\" ignored in '%s', line %d\n"),
"include-default", fname, lnr);
continue;
}
/* fixme: Should check for trailing garbage. */
etcname = make_sys_trustlist_name ();
if ( !strcmp (etcname, fname) ) /* Same file. */
log_info (_("statement \"%s\" ignored in '%s', line %d\n"),
"include-default", fname, lnr);
else if ((ec=gnupg_access (etcname, F_OK)) && ec == GPG_ERR_ENOENT)
{
/* A non existent system trustlist is not an error.
Just print a note. */
log_info (_("system trustlist '%s' not available\n"), etcname);
}
else
{
err2 = read_one_trustfile (etcname, 1,
&table, &tablesize, &tableidx);
if (err2)
err = err2;
}
xfree (etcname);
continue;
}
if (tableidx == tablesize) /* Need more space. */
{
trustitem_t *tmp;
size_t tmplen;
tmplen = tablesize + 20;
tmp = xtryrealloc (table, tmplen * sizeof *table);
if (!tmp)
{
err = gpg_error_from_syserror ();
goto leave;
}
table = tmp;
tablesize = tmplen;
}
ti = table + tableidx;
memset (&ti->flags, 0, sizeof ti->flags);
if (*p == '!')
{
ti->flags.disabled = 1;
p++;
while (spacep (p))
p++;
}
n = hexcolon2bin (p, ti->fpr, 20);
if (n < 0)
{
log_error (_("bad fingerprint in '%s', line %d\n"), fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
continue;
}
p += n;
for (; spacep (p); p++)
;
/* Process the first flag which needs to be the first for
backward compatibility. */
if (!*p || *p == '*' )
{
ti->flags.for_smime = 1;
ti->flags.for_pgp = 1;
}
else if ( *p == 'P' || *p == 'p')
{
ti->flags.for_pgp = 1;
}
else if ( *p == 'S' || *p == 's')
{
ti->flags.for_smime = 1;
}
else
{
log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
continue;
}
p++;
if ( *p && !spacep (p) )
{
log_error (_("invalid keyflag in '%s', line %d\n"), fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
continue;
}
/* Now check for more key-value pairs of the form NAME[=VALUE]. */
while (*p)
{
for (; spacep (p); p++)
;
if (!*p)
break;
n = strcspn (p, "= \t");
if (p[n] == '=')
{
log_error ("assigning a value to a flag is not yet supported; "
"in '%s', line %d\n", fname, lnr);
err = gpg_error (GPG_ERR_BAD_DATA);
p++;
}
else if (n == 5 && !memcmp (p, "relax", 5))
ti->flags.relax = 1;
else if (n == 2 && !memcmp (p, "cm", 2))
ti->flags.cm = 1;
else if (n == 4 && !memcmp (p, "qual", 4) && systrust)
ti->flags.qual = 1;
else if (n == 4 && !memcmp (p, "de-vs", 4) && systrust)
ti->flags.de_vs = 1;
else
log_error ("flag '%.*s' in '%s', line %d ignored\n",
n, p, fname, lnr);
p += n;
}
tableidx++;
}
if ( !err && !es_feof (fp) )
{
err = gpg_error_from_syserror ();
log_error (_("error reading '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
}
leave:
es_fclose (fp);
*addr_of_table = table;
*addr_of_tablesize = tablesize;
*addr_of_tableidx = tableidx;
return err;
}
/* Read the trust files and update the global table on success. The
trusttable is assumed to be locked. */
static gpg_error_t
read_trustfiles (void)
{
gpg_error_t err;
trustitem_t *table, *ti;
int tableidx;
size_t tablesize;
char *fname;
int systrust = 0;
gpg_err_code_t ec;
tablesize = 20;
table = xtrycalloc (tablesize, sizeof *table);
if (!table)
return gpg_error_from_syserror ();
tableidx = 0;
if (opt.no_user_trustlist)
fname = NULL;
else
{
fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
xfree (table);
return err;
}
}
if (!fname || (ec = gnupg_access (fname, F_OK)))
{
if (!fname)
; /* --no-user-trustlist active. */
else if ( ec == GPG_ERR_ENOENT )
; /* Silently ignore a non-existing trustfile. */
else
{
err = gpg_error (ec);
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
}
xfree (fname);
fname = make_sys_trustlist_name ();
systrust = 1;
}
err = read_one_trustfile (fname, systrust, &table, &tablesize, &tableidx);
xfree (fname);
if (err)
{
xfree (table);
if (gpg_err_code (err) == GPG_ERR_ENOENT)
{
/* Take a missing trustlist as an empty one. */
clear_trusttable ();
err = 0;
}
return err;
}
/* Fixme: we should drop duplicates and sort the table. */
ti = xtryrealloc (table, (tableidx?tableidx:1) * sizeof *table);
if (!ti)
{
err = gpg_error_from_syserror ();
xfree (table);
return err;
}
/* Replace the trusttable. */
xfree (trusttable);
trusttable = ti;
trusttablesize = tableidx;
return 0;
}
/* Check whether the given fpr is in our trustdb. We expect FPR to be
an all uppercase hexstring of 40 characters. If ALREADY_LOCKED is
true the function assumes that the trusttable is already locked. */
static gpg_error_t
istrusted_internal (ctrl_t ctrl, const char *fpr, int *r_disabled,
int already_locked)
{
gpg_error_t err = 0;
int locked = already_locked;
trustitem_t *ti;
size_t len;
unsigned char fprbin[20];
if (r_disabled)
*r_disabled = 0;
if ( hexcolon2bin (fpr, fprbin, 20) < 0 )
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (!already_locked)
{
lock_trusttable ();
locked = 1;
}
if (!trusttable)
{
err = read_trustfiles ();
if (err)
{
log_error (_("error reading list of trusted root certificates\n"));
goto leave;
}
}
if (trusttable)
{
for (ti=trusttable, len = trusttablesize; len; ti++, len--)
if (!memcmp (ti->fpr, fprbin, 20))
{
if (ti->flags.disabled && r_disabled)
*r_disabled = 1;
/* Print status messages only if we have not been called
in a locked state. */
if (already_locked)
;
else if (ti->flags.relax || ti->flags.cm || ti->flags.qual
|| ti->flags.de_vs)
{
unlock_trusttable ();
locked = 0;
err = 0;
if (ti->flags.relax)
err = agent_write_status (ctrl,"TRUSTLISTFLAG", "relax",NULL);
if (!err && ti->flags.cm)
err = agent_write_status (ctrl,"TRUSTLISTFLAG", "cm", NULL);
if (!err && ti->flags.qual)
err = agent_write_status (ctrl,"TRUSTLISTFLAG", "qual",NULL);
if (!err && ti->flags.de_vs)
err = agent_write_status (ctrl,"TRUSTLISTFLAG", "de-vs",NULL);
}
if (!err)
err = ti->flags.disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
goto leave;
}
}
err = gpg_error (GPG_ERR_NOT_TRUSTED);
leave:
if (locked && !already_locked)
unlock_trusttable ();
return err;
}
/* Check whether the given fpr is in our trustdb. We expect FPR to be
an all uppercase hexstring of 40 characters. */
gpg_error_t
agent_istrusted (ctrl_t ctrl, const char *fpr, int *r_disabled)
{
return istrusted_internal (ctrl, fpr, r_disabled, 0);
}
/* Write all trust entries to FP. */
gpg_error_t
agent_listtrusted (void *assuan_context)
{
trustitem_t *ti;
char key[51];
gpg_error_t err;
size_t len;
lock_trusttable ();
if (!trusttable)
{
err = read_trustfiles ();
if (err)
{
unlock_trusttable ();
log_error (_("error reading list of trusted root certificates\n"));
return err;
}
}
if (trusttable)
{
for (ti=trusttable, len = trusttablesize; len; ti++, len--)
{
if (ti->flags.disabled)
continue;
bin2hex (ti->fpr, 20, key);
key[40] = ' ';
key[41] = ((ti->flags.for_smime && ti->flags.for_pgp)? '*'
: ti->flags.for_smime? 'S': ti->flags.for_pgp? 'P':' ');
key[42] = '\n';
assuan_send_data (assuan_context, key, 43);
assuan_send_data (assuan_context, NULL, 0); /* flush */
}
}
unlock_trusttable ();
return 0;
}
/* Create a copy of string with colons inserted after each two bytes.
Caller needs to release the string. In case of a memory failure,
NULL is returned. */
static char *
insert_colons (const char *string)
{
char *buffer, *p;
size_t n = strlen (string);
size_t nnew = n + (n+1)/2;
p = buffer = xtrymalloc ( nnew + 1 );
if (!buffer)
return NULL;
while (*string)
{
*p++ = *string++;
if (*string)
{
*p++ = *string++;
if (*string)
*p++ = ':';
}
}
*p = 0;
log_assert (strlen (buffer) <= nnew);
return buffer;
}
/* To pretty print DNs in the Pinentry, we replace slashes by
REPLSTRING. The caller needs to free the returned string. NULL is
returned on error with ERRNO set. */
static char *
reformat_name (const char *name, const char *replstring)
{
const char *s;
char *newname;
char *d;
size_t count;
size_t replstringlen = strlen (replstring);
/* If the name does not start with a slash it is not a preformatted
DN and thus we don't bother to reformat it. */
if (*name != '/')
return xtrystrdup (name);
/* Count the names. Note that a slash contained in a DN part is
expected to be C style escaped and thus the slashes we see here
are the actual part delimiters. */
for (s=name+1, count=0; *s; s++)
if (*s == '/')
count++;
newname = xtrymalloc (strlen (name) + count*replstringlen + 1);
if (!newname)
return NULL;
for (s=name+1, d=newname; *s; s++)
if (*s == '/')
d = stpcpy (d, replstring);
else
*d++ = *s;
*d = 0;
return newname;
}
/* Insert the given fpr into our trustdb. We expect FPR to be an all
uppercase hexstring of 40 characters. FLAG is either 'P' or 'C'.
This function does first check whether that key has already been
put into the trustdb and returns success in this case. Before a
FPR actually gets inserted, the user is asked by means of the
Pinentry whether this is actual what he wants to do. */
gpg_error_t
agent_marktrusted (ctrl_t ctrl, const char *name, const char *fpr, int flag)
{
gpg_error_t err = 0;
gpg_err_code_t ec;
char *desc;
char *fname;
estream_t fp;
char *fprformatted;
char *nameformatted;
int is_disabled;
int yes_i_trust;
/* Check whether we are at all allowed to modify the trustlist.
This is useful so that the trustlist may be a symlink to a global
trustlist with only admin privileges to modify it. Of course
this is not a secure way of denying access, but it avoids the
usual clicking on an Okay button most users are used to. */
fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
if (!fname)
return gpg_error_from_syserror ();
if ((ec = gnupg_access (fname, W_OK)) && ec != GPG_ERR_ENOENT)
{
xfree (fname);
return gpg_error (GPG_ERR_EPERM);
}
xfree (fname);
if (!agent_istrusted (ctrl, fpr, &is_disabled))
{
return 0; /* We already got this fingerprint. Silently return
success. */
}
/* This feature must explicitly been enabled. */
if (!opt.allow_mark_trusted)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (is_disabled)
{
/* There is an disabled entry in the trustlist. Return an error
so that the user won't be asked again for that one. Changing
this flag with the integrated marktrusted feature is and will
not be made possible. */
return gpg_error (GPG_ERR_NOT_TRUSTED);
}
/* Insert a new one. */
nameformatted = reformat_name (name, "%0A ");
if (!nameformatted)
return gpg_error_from_syserror ();
/* First a general question whether this is trusted. */
desc = xtryasprintf (
/* TRANSLATORS: This prompt is shown by the Pinentry
and has one special property: A "%%0A" is used by
Pinentry to insert a line break. The double
percent sign is actually needed because it is also
a printf format string. If you need to insert a
plain % sign, you need to encode it as "%%25". The
"%s" gets replaced by the name as stored in the
certificate. */
L_("Do you ultimately trust%%0A"
" \"%s\"%%0A"
"to correctly certify user certificates?"),
nameformatted);
if (!desc)
{
xfree (nameformatted);
return out_of_core ();
}
err = agent_get_confirmation (ctrl, desc, L_("Yes"), L_("No"), 1);
xfree (desc);
if (!err)
yes_i_trust = 1;
else if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
yes_i_trust = 0;
else
{
xfree (nameformatted);
return err;
}
fprformatted = insert_colons (fpr);
if (!fprformatted)
{
xfree (nameformatted);
return out_of_core ();
}
/* If the user trusts this certificate he has to verify the
fingerprint of course. */
if (yes_i_trust)
{
desc = xtryasprintf
(
/* TRANSLATORS: This prompt is shown by the Pinentry and has
one special property: A "%%0A" is used by Pinentry to
insert a line break. The double percent sign is actually
needed because it is also a printf format string. If you
need to insert a plain % sign, you need to encode it as
- "%%25". The second "%s" gets replaced by a hexdecimal
+ "%%25". The second "%s" gets replaced by a hexadecimal
fingerprint string whereas the first one receives the name
as stored in the certificate. */
L_("Please verify that the certificate identified as:%%0A"
" \"%s\"%%0A"
"has the fingerprint:%%0A"
" %s"), nameformatted, fprformatted);
if (!desc)
{
xfree (fprformatted);
xfree (nameformatted);
return out_of_core ();
}
/* TRANSLATORS: "Correct" is the label of a button and intended
to be hit if the fingerprint matches the one of the CA. The
other button is "the default "Cancel" of the Pinentry. */
err = agent_get_confirmation (ctrl, desc, L_("Correct"), L_("Wrong"), 1);
xfree (desc);
if (gpg_err_code (err) == GPG_ERR_NOT_CONFIRMED)
yes_i_trust = 0;
else if (err)
{
xfree (fprformatted);
xfree (nameformatted);
return err;
}
}
/* Now check again to avoid duplicates. We take the lock to make
sure that nobody else plays with our file and force a reread. */
lock_trusttable ();
clear_trusttable ();
if (!istrusted_internal (ctrl, fpr, &is_disabled, 1) || is_disabled)
{
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return is_disabled? gpg_error (GPG_ERR_NOT_TRUSTED) : 0;
}
fname = make_filename_try (gnupg_homedir (), "trustlist.txt", NULL);
if (!fname)
{
err = gpg_error_from_syserror ();
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return err;
}
if ((ec = access (fname, F_OK)) && ec == GPG_ERR_ENOENT)
{
fp = es_fopen (fname, "wx,mode=-rw-r");
if (!fp)
{
err = gpg_error (ec);
log_error ("can't create '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return err;
}
es_fputs (headerblurb, fp);
es_fclose (fp);
}
fp = es_fopen (fname, "a+,mode=-rw-r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
return err;
}
/* Append the key. */
es_fputs ("\n# ", fp);
xfree (nameformatted);
nameformatted = reformat_name (name, "\n# ");
if (!nameformatted || strchr (name, '\n'))
{
/* Note that there should never be a LF in NAME but we better
play safe and print a sanitized version in this case. */
es_write_sanitized (fp, name, strlen (name), NULL, NULL);
}
else
es_fputs (nameformatted, fp);
es_fprintf (fp, "\n%s%s %c%s\n", yes_i_trust?"":"!", fprformatted, flag,
flag == 'S'? " relax":"");
if (es_ferror (fp))
err = gpg_error_from_syserror ();
if (es_fclose (fp))
err = gpg_error_from_syserror ();
clear_trusttable ();
xfree (fname);
unlock_trusttable ();
xfree (fprformatted);
xfree (nameformatted);
if (!err)
bump_key_eventcounter ();
return err;
}
/* This function may be called to force reloading of the
trustlist. */
void
agent_reload_trustlist (void)
{
/* All we need to do is to delete the trusttable. At the next
access it will get re-read. */
lock_trusttable ();
clear_trusttable ();
unlock_trusttable ();
bump_key_eventcounter ();
}
diff --git a/build-aux/speedo.mk b/build-aux/speedo.mk
index e92a94231..a98d61f71 100644
--- a/build-aux/speedo.mk
+++ b/build-aux/speedo.mk
@@ -1,1414 +1,1414 @@
# speedo.mk - Speedo rebuilds speedily.
# Copyright (C) 2008, 2014, 2019 g10 Code GmbH
#
# speedo 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.
#
# speedo 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 <http://www.gnu.org/licenses/>.
# speedo builds gnupg-related packages from GIT and installs them in a
# user directory, thereby providing a non-obstrusive test environment.
# speedo does only work with GNU make. The build system is similar to
# that of gpg4win. The following commands are supported:
#
# make -f speedo.mk all pkg2rep=/dir/with/tarballs
# or
# make -f speedo.mk
#
# Builds all packages and installs them under PLAY/inst. At the end,
# speedo prints commands that can be executed in the local shell to
# make use of the installed packages.
#
# make -f speedo.mk clean
# or
# make -f speedo.mk clean-PACKAGE
#
# Removes all packages or the package PACKAGE from the installation
# and build tree. A subsequent make will rebuild these (and only
# these) packages.
#
# make -f speedo.mk report
# or
# make -f speedo.mk report-PACKAGE
#
# Lists packages and versions.
#
# The information required to sign the tarballs and binaries
# are expected in the developer specific file ~/.gnupg-autogen.rc".
# Use "gpg-authcode-sign.sh --template" to create a template.
# We need to know our own name.
SPEEDO_MK := $(realpath $(lastword $(MAKEFILE_LIST)))
.PHONY : help native w32-installer w32-source w32-wixlib
.PHONY : git-native git-w32-installer git-w32-source
.PHONY : this-native this-w32-installer this-w32-source
help:
@echo 'usage: make -f speedo.mk TARGET'
@echo ' with TARGET being one of:'
@echo ' help This help'
@echo ' native Native build of the GnuPG core'
@echo ' w32-installer Build a Windows installer'
@echo ' w32-source Pack a source archive'
@echo ' w32-release Build a Windows release'
@echo ' w32-wixlib Build a wixlib for MSI packages'
@echo ' w32-sign-installer Sign the installer'
@echo
@echo 'You may append INSTALL_PREFIX=<dir> for native builds.'
@echo 'Prepend TARGET with "git-" to build from GIT repos.'
@echo 'Prepend TARGET with "this-" to build from the source tarball.'
@echo 'Use STATIC=1 to build with statically linked libraries.'
@echo 'Use SELFCHECK=1 for additional check of the gnupg version.'
@echo 'Use CUSTOM_SWDB=1 for an already downloaded swdb.lst.'
@echo 'Use WIXPREFIX to provide the WIX binaries for the MSI package.'
@echo ' Using WIX also requires wine with installed wine mono.'
@echo ' See help-wixlib for more information'
@echo 'Set W32VERSION=w64 to build a 64 bit Windows version.'
help-wixlib:
@echo 'The buildsystem can create a wixlib to build MSI packages.'
@echo ''
@echo 'On debian install the packages "wine"'
@echo ' apt-get install wine'
@echo ''
@echo 'Download the wine-mono msi:'
@echo ' https://dl.winehq.org/wine/wine-mono/'
@echo ''
@echo 'Install it:'
@echo ' wine msiexec /i ~/Downloads/wine-mono-4.9.4.msi'
@echo ''
@echo 'Download the wix toolset binary zip from:'
@echo ' https://github.com/wixtoolset/wix3/releases'
@echo 'The default folder searches for ~/w32root/wixtools'
@echo 'Alternative locations can be passed by WIXPREFIX variable'
@echo ' unzip -d ~/w32root/wixtools ~/Downloads/wix311-binaries.zip'
@echo ''
@echo 'Afterwards w32-msi-release will also build a wixlib.'
# NB: we can't use +$(MAKE) here because we would need to define the
# dependencies of our packages. This does not make much sense given that
# we have a clear order in how they are build and concurrent builds
# would anyway clutter up the logs.
SPEEDOMAKE := $(MAKE) -f $(SPEEDO_MK) UPD_SWDB=1
native: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=release all
git-native: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=git all
this-native: check-tools
$(SPEEDOMAKE) TARGETOS=native WHAT=this all
w32-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release installer
git-w32-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=git installer
this-w32-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=this CUSTOM_SWDB=1 installer
w32-wixlib: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release wixlib
git-w32-wixlib: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=git wixlib
this-w32-wixlib: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=this CUSTOM_SWDB=1 wixlib
w32-source: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release dist-source
git-w32-source: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=git dist-source
this-w32-source: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=this CUSTOM_SWDB=1 dist-source
w32-release: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release installer-from-source
w32-msi-release: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release \
WITH_WIXLIB=1 installer-from-source
w32-sign-installer: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release sign-installer
w32-release-offline: check-tools
$(SPEEDOMAKE) TARGETOS=w32 WHAT=release \
CUSTOM_SWDB=1 pkgrep=${HOME}/b pkg10rep=${HOME}/b \
installer-from-source
# Set this to "git" to build from git,
# to "release" from tarballs,
# to "this" from the unpacked sources.
WHAT=git
# Set target to "native" or "w32".
TARGETOS=
# To build a 64 bit Windows version also change this to "w64"
W32VERSION=w32
# Set to 1 to use a pre-installed swdb.lst instead of the online version.
CUSTOM_SWDB=0
# Set to 1 to really download the swdb.
UPD_SWDB=0
# Set to 1 to run an additional GnuPG version check
SELFCHECK=0
# Set to 1 to build with statically linked libraries.
STATIC=0
# Set to the location of the directory with tarballs of
# external packages.
TARBALLS=$(shell pwd)/../tarballs
# Number of parallel make jobs for each package
MAKE_J=6
# Name to use for the w32 installer and sources
INST_NAME=gnupg-w32
-# Use this to override the installaion directory for native builds.
+# Use this to override the installation directory for native builds.
INSTALL_PREFIX=none
# Set this to the location of wixtools
WIXPREFIX=$(shell readlink -f ~/w32root/wixtools)
-# If patchelf(1) is not availale disable the command.
+# If patchelf(1) is not available disable the command.
PATCHELF := $(shell patchelf --version 2>/dev/null >/dev/null || echo "echo please run: ")patchelf
# Read signing information from ~/.gnupg-autogen.rc
define READ_AUTOGEN_template
$(1) = $$(shell grep '^[[:blank:]]*$(1)[[:blank:]]*=' $$$$HOME/.gnupg-autogen.rc|cut -d= -f2|xargs)
endef
$(eval $(call READ_AUTOGEN_template,OVERRIDE_TARBALLS))
# All files given in AUTHENTICODE_FILES are signed before
# they are put into the installer.
AUTHENTICODE_FILES= \
dirmngr.exe \
dirmngr_ldap.exe \
gpg-agent.exe \
gpg-connect-agent.exe \
gpg-preset-passphrase.exe \
gpg-check-pattern.exe \
gpg-wks-client.exe \
gpg.exe \
gpgconf.exe \
gpgme-w32spawn.exe \
gpgsm.exe \
gpgtar.exe \
gpgv.exe \
gpg-card.exe \
keyboxd.exe \
libassuan-0.dll \
libgcrypt-20.dll \
libgpg-error-0.dll \
libgpgme-11.dll \
libksba-8.dll \
libnpth-0.dll \
libsqlite3-0.dll \
pinentry-w32.exe \
scdaemon.exe \
zlib1.dll
# Directory names.
# They must be absolute, as we switch directories pretty often.
root := $(shell pwd)/PLAY
sdir := $(root)/src
bdir := $(root)/build
bdir6:= $(root)/build-w64
ifeq ($(INSTALL_PREFIX),none)
idir := $(root)/inst
else
idir := $(abspath $(INSTALL_PREFIX))
endif
idir6:= $(root)/inst-w64
stampdir := $(root)/stamps
topsrc := $(shell cd $(dir $(SPEEDO_MK)).. && pwd)
auxsrc := $(topsrc)/build-aux/speedo
patdir := $(topsrc)/build-aux/speedo/patches
w32src := $(topsrc)/build-aux/speedo/w32
# =====BEGIN LIST OF PACKAGES=====
# The packages that should be built. The order is also the build order.
# Fixme: Do we need to build pkg-config for cross-building?
speedo_spkgs = \
libgpg-error npth libgcrypt \
zlib bzip2 sqlite \
libassuan libksba ntbtls gnupg
ifeq ($(STATIC),0)
speedo_spkgs += gpgme
endif
ifeq ($(TARGETOS),w32)
speedo_spkgs += pinentry
endif
# =====END LIST OF PACKAGES=====
# Packages which are additionally build for 64 bit Windows. They are
# only used for gpgex and thus we need to build them only if we want
# a full installer.
ifeq ($(W32VERSION),w64)
# Keep this empty
speedo_w64_spkgs =
else
speedo_w64_spkgs =
endif
# Packages which use the gnupg autogen.sh build style
speedo_gnupg_style = \
libgpg-error npth libgcrypt \
libassuan libksba ntbtls gnupg gpgme \
pinentry
# Packages which use only make and no build directory
speedo_make_only_style = \
zlib bzip2
# Get the content of the software DB.
ifeq ($(CUSTOM_SWDB),1)
getswdb_options = --skip-download --skip-verify
else
getswdb_options =
endif
ifeq ($(SELFCHECK),0)
getswdb_options += --skip-selfcheck
endif
ifeq ($(UPD_SWDB),1)
SWDB := $(shell $(topsrc)/build-aux/getswdb.sh $(getswdb_options) && echo okay)
ifeq ($(strip $(SWDB)),)
ifneq ($(WHAT),git)
$(error Error getting GnuPG software version database)
endif
endif
# Version numbers of the released packages
gnupg_ver_this = $(shell cat $(topsrc)/VERSION)
gnupg_ver := $(shell awk '$$1=="gnupg26_ver" {print $$2}' swdb.lst)
libgpg_error_ver := $(shell awk '$$1=="libgpg_error_ver" {print $$2}' swdb.lst)
libgpg_error_sha1:= $(shell awk '$$1=="libgpg_error_sha1" {print $$2}' swdb.lst)
libgpg_error_sha2:= $(shell awk '$$1=="libgpg_error_sha2" {print $$2}' swdb.lst)
npth_ver := $(shell awk '$$1=="npth_ver" {print $$2}' swdb.lst)
npth_sha1 := $(shell awk '$$1=="npth_sha1" {print $$2}' swdb.lst)
npth_sha2 := $(shell awk '$$1=="npth_sha2" {print $$2}' swdb.lst)
libgcrypt_ver := $(shell awk '$$1=="libgcrypt_ver" {print $$2}' swdb.lst)
libgcrypt_sha1 := $(shell awk '$$1=="libgcrypt_sha1" {print $$2}' swdb.lst)
libgcrypt_sha2 := $(shell awk '$$1=="libgcrypt_sha2" {print $$2}' swdb.lst)
libassuan_ver := $(shell awk '$$1=="libassuan_ver" {print $$2}' swdb.lst)
libassuan_sha1 := $(shell awk '$$1=="libassuan_sha1" {print $$2}' swdb.lst)
libassuan_sha2 := $(shell awk '$$1=="libassuan_sha2" {print $$2}' swdb.lst)
libksba_ver := $(shell awk '$$1=="libksba_ver" {print $$2}' swdb.lst)
libksba_sha1 := $(shell awk '$$1=="libksba_sha1" {print $$2}' swdb.lst)
libksba_sha2 := $(shell awk '$$1=="libksba_sha2" {print $$2}' swdb.lst)
ntbtls_ver := $(shell awk '$$1=="ntbtls_ver" {print $$2}' swdb.lst)
ntbtls_sha1 := $(shell awk '$$1=="ntbtls_sha1" {print $$2}' swdb.lst)
ntbtls_sha2 := $(shell awk '$$1=="ntbtls_sha2" {print $$2}' swdb.lst)
gpgme_ver := $(shell awk '$$1=="gpgme_ver" {print $$2}' swdb.lst)
gpgme_sha1 := $(shell awk '$$1=="gpgme_sha1" {print $$2}' swdb.lst)
gpgme_sha2 := $(shell awk '$$1=="gpgme_sha2" {print $$2}' swdb.lst)
pinentry_ver := $(shell awk '$$1=="pinentry_ver" {print $$2}' swdb.lst)
pinentry_sha1 := $(shell awk '$$1=="pinentry_sha1" {print $$2}' swdb.lst)
pinentry_sha2 := $(shell awk '$$1=="pinentry_sha2" {print $$2}' swdb.lst)
zlib_ver := $(shell awk '$$1=="zlib_ver" {print $$2}' swdb.lst)
zlib_sha1 := $(shell awk '$$1=="zlib_sha1_gz" {print $$2}' swdb.lst)
zlib_sha2 := $(shell awk '$$1=="zlib_sha2_gz" {print $$2}' swdb.lst)
bzip2_ver := $(shell awk '$$1=="bzip2_ver" {print $$2}' swdb.lst)
bzip2_sha1 := $(shell awk '$$1=="bzip2_sha1_gz" {print $$2}' swdb.lst)
bzip2_sha2 := $(shell awk '$$1=="bzip2_sha2_gz" {print $$2}' swdb.lst)
sqlite_ver := $(shell awk '$$1=="sqlite_ver" {print $$2}' swdb.lst)
sqlite_sha1 := $(shell awk '$$1=="sqlite_sha1_gz" {print $$2}' swdb.lst)
sqlite_sha2 := $(shell awk '$$1=="sqlite_sha2_gz" {print $$2}' swdb.lst)
$(info Information from the version database:)
$(info GnuPG ..........: $(gnupg_ver) (building $(gnupg_ver_this)))
$(info GpgRT ..........: $(libgpg_error_ver))
$(info Npth ...........: $(npth_ver))
$(info Libgcrypt ......: $(libgcrypt_ver))
$(info Libassuan ......: $(libassuan_ver))
$(info Libksba ........: $(libksba_ver))
$(info Zlib ...........: $(zlib_ver))
$(info Bzip2 ..........: $(bzip2_ver))
$(info SQLite .........: $(sqlite_ver))
$(info NtbTLS .. ......: $(ntbtls_ver))
$(info GPGME ..........: $(gpgme_ver))
$(info Pinentry .......: $(pinentry_ver))
endif
$(info Information for this run:)
$(info Build type .....: $(WHAT))
$(info Target .........: $(TARGETOS))
ifeq ($(TARGETOS),w32)
ifeq ($(W32VERSION),w64)
$(info Windows version : 64 bit)
else
$(info Windows version : 32 bit)
ifneq ($(W32VERSION),w32)
$(error W32VERSION is not set to a proper value: Use only w32 or w64)
endif
endif
endif
# Version number for external packages
pkg_config_ver = 0.23
libiconv_ver = 1.14
gettext_ver = 0.18.2.1
# The GIT repository. Using a local repo is much faster.
#gitrep = git://git.gnupg.org
gitrep = ${HOME}/s
# The tarball directories
pkgrep = https://gnupg.org/ftp/gcrypt
pkg10rep = ftp://ftp.g10code.com/g10code
pkg2rep = $(TARBALLS)
# For each package, the following variables can be defined:
#
# speedo_pkg_PACKAGE_git: The GIT repository that should be built.
# speedo_pkg_PACKAGE_gitref: The GIT revision to checkout
#
# speedo_pkg_PACKAGE_tar: URL to the tar file that should be built.
#
# Exactly one of the above variables is required. Note that this
# version of speedo does not cache repositories or tar files, and does
# not test the integrity of the downloaded software. If you care
# about this, you can also specify filenames to locally verified files.
# Filenames are differentiated from URLs by starting with a slash '/'.
#
# speedo_pkg_PACKAGE_configure: Extra arguments to configure.
#
# speedo_pkg_PACKAGE_make_args: Extra arguments to make.
#
# speedo_pkg_PACKAGE_make_args_inst: Extra arguments to make install.
#
# Note that you can override the defaults in this file in a local file
# "config.mk"
ifeq ($(WHAT),this)
else ifeq ($(WHAT),git)
speedo_pkg_libgpg_error_git = $(gitrep)/libgpg-error
speedo_pkg_libgpg_error_gitref = master
speedo_pkg_npth_git = $(gitrep)/npth
speedo_pkg_npth_gitref = master
speedo_pkg_libassuan_git = $(gitrep)/libassuan
speedo_pkg_libassuan_gitref = master
speedo_pkg_libgcrypt_git = $(gitrep)/libgcrypt
speedo_pkg_libgcrypt_gitref = master
speedo_pkg_libksba_git = $(gitrep)/libksba
speedo_pkg_libksba_gitref = master
speedo_pkg_ntbtls_git = $(gitrep)/ntbtls
speedo_pkg_ntbtls_gitref = master
speedo_pkg_gpgme_git = $(gitrep)/gpgme
speedo_pkg_gpgme_gitref = master
speedo_pkg_pinentry_git = $(gitrep)/pinentry
speedo_pkg_pinentry_gitref = master
else ifeq ($(WHAT),release)
speedo_pkg_libgpg_error_tar = \
$(pkgrep)/libgpg-error/libgpg-error-$(libgpg_error_ver).tar.bz2
speedo_pkg_npth_tar = \
$(pkgrep)/npth/npth-$(npth_ver).tar.bz2
speedo_pkg_libassuan_tar = \
$(pkgrep)/libassuan/libassuan-$(libassuan_ver).tar.bz2
speedo_pkg_libgcrypt_tar = \
$(pkgrep)/libgcrypt/libgcrypt-$(libgcrypt_ver).tar.bz2
speedo_pkg_libksba_tar = \
$(pkgrep)/libksba/libksba-$(libksba_ver).tar.bz2
speedo_pkg_ntbtls_tar = \
$(pkgrep)/ntbtls/ntbtls-$(ntbtls_ver).tar.bz2
speedo_pkg_gpgme_tar = \
$(pkgrep)/gpgme/gpgme-$(gpgme_ver).tar.bz2
speedo_pkg_pinentry_tar = \
$(pkgrep)/pinentry/pinentry-$(pinentry_ver).tar.bz2
else
$(error invalid value for WHAT (use on of: git release this))
endif
speedo_pkg_pkg_config_tar = $(pkg2rep)/pkg-config-$(pkg_config_ver).tar.gz
speedo_pkg_zlib_tar = $(pkgrep)/zlib/zlib-$(zlib_ver).tar.gz
speedo_pkg_bzip2_tar = $(pkgrep)/bzip2/bzip2-$(bzip2_ver).tar.gz
speedo_pkg_sqlite_tar = $(pkgrep)/sqlite/sqlite-autoconf-$(sqlite_ver).tar.gz
speedo_pkg_libiconv_tar = $(pkg2rep)/libiconv-$(libiconv_ver).tar.gz
speedo_pkg_gettext_tar = $(pkg2rep)/gettext-$(gettext_ver).tar.gz
#
# Package build options
#
speedo_pkg_npth_configure = --enable-static
speedo_pkg_libgpg_error_configure = --enable-static
speedo_pkg_w64_libgpg_error_configure = --enable-static
speedo_pkg_libassuan_configure = --enable-static
speedo_pkg_w64_libassuan_configure = --enable-static
speedo_pkg_libgcrypt_configure = --disable-static
speedo_pkg_libksba_configure = --disable-static
speedo_pkg_ntbtls_configure = --enable-static
ifeq ($(STATIC),1)
speedo_pkg_npth_configure += --disable-shared
speedo_pkg_libgpg_error_configure += --disable-shared
speedo_pkg_libassuan_configure += --disable-shared
speedo_pkg_libgcrypt_configure += --disable-shared
speedo_pkg_libksba_configure += --disable-shared
endif
# For now we build ntbtls only static
speedo_pkg_ntbtls_configure = --disable-shared
ifeq ($(TARGETOS),w32)
speedo_pkg_gnupg_configure = \
--disable-g13 --enable-ntbtls --disable-tpm2d
else
speedo_pkg_gnupg_configure = --disable-g13 --enable-wks-tools
endif
speedo_pkg_gnupg_extracflags =
# Create the version info files only for W32 so that they won't get
# installed if for example INSTALL_PREFIX=/usr/local is used.
ifeq ($(TARGETOS),w32)
define speedo_pkg_gnupg_post_install
(set -e; \
sed -n 's/.*PACKAGE_VERSION "\(.*\)"/\1/p' config.h >$(idir)/INST_VERSION; \
sed -n 's/.*W32INFO_VI_PRODUCTVERSION \(.*\)/\1/p' common/w32info-rc.h \
|sed 's/,/./g' >$(idir)/INST_PROD_VERSION )
endef
endif
# The LDFLAGS was needed for -lintl for glib.
speedo_pkg_gpgme_configure = \
--disable-static --disable-w32-glib \
--with-gpg-error-prefix=$(idir) \
LDFLAGS=-L$(idir)/lib
speedo_pkg_pinentry_configure += \
--disable-pinentry-qt5 \
--disable-pinentry-qt \
--disable-pinentry-fltk \
--disable-pinentry-tty \
CPPFLAGS=-I$(idir)/include \
LDFLAGS=-L$(idir)/lib \
CXXFLAGS=-static-libstdc++
#
# External packages
#
# gcc 10.2 takes __udivdi3 from the exception handler DLL and thus
# requires it. This is a regression from gcc 8.3 and earlier. To fix
# this we need to pass -static-libgcc.
ifeq ($(TARGETOS),w32)
speedo_pkg_zlib_make_args = \
-fwin32/Makefile.gcc PREFIX=$(host)- IMPLIB=libz.dll.a \
LDFLAGS=-static-libgcc
speedo_pkg_zlib_make_args_inst = \
-fwin32/Makefile.gcc \
BINARY_PATH=$(idir)/bin INCLUDE_PATH=$(idir)/include \
LIBRARY_PATH=$(idir)/lib SHARED_MODE=1 IMPLIB=libz.dll.a
# Zlib needs some special magic to generate a libtool file.
# We also install the pc file here.
define speedo_pkg_zlib_post_install
(set -e; mkdir $(idir)/lib/pkgconfig || true; \
cp $(auxsrc)/zlib.pc $(idir)/lib/pkgconfig/; \
cd $(idir); \
echo "# Generated by libtool" > lib/libz.la \
echo "dlname='../bin/zlib1.dll'" >> lib/libz.la; \
echo "library_names='libz.dll.a'" >> lib/libz.la; \
echo "old_library='libz.a'" >> lib/libz.la; \
echo "dependency_libs=''" >> lib/libz.la; \
echo "current=1" >> lib/libz.la; \
echo "age=2" >> lib/libz.la; \
echo "revision=5" >> lib/libz.la; \
echo "installed=yes" >> lib/libz.la; \
echo "shouldnotlink=no" >> lib/libz.la; \
echo "dlopen=''" >> lib/libz.la; \
echo "dlpreopen=''" >> lib/libz.la; \
echo "libdir=\"$(idir)/lib\"" >> lib/libz.la)
endef
endif
ifeq ($(TARGETOS),w32)
speedo_pkg_bzip2_make_args = \
CC="$(host)-gcc" AR="$(host)-ar" RANLIB="$(host)-ranlib"
speedo_pkg_bzip2_make_args_inst = \
PREFIX=$(idir) CC="$(host)-gcc" AR="$(host)-ar" RANLIB="$(host)-ranlib"
endif
speedo_pkg_w64_libiconv_configure = \
--enable-shared=no --enable-static=yes
speedo_pkg_gettext_configure = \
--with-lib-prefix=$(idir) --with-libiconv-prefix=$(idir) \
CPPFLAGS=-I$(idir)/include LDFLAGS=-L$(idir)/lib
speedo_pkg_w64_gettext_configure = \
--with-lib-prefix=$(idir) --with-libiconv-prefix=$(idir) \
CPPFLAGS=-I$(idir6)/include LDFLAGS=-L$(idir6)/lib
speedo_pkg_gettext_extracflags = -O2
# We only need gettext-runtime and there is sadly no top level
# configure option for this
speedo_pkg_gettext_make_dir = gettext-runtime
# ---------
all: all-speedo
install: install-speedo
report: report-speedo
clean: clean-speedo
ifeq ($(W32VERSION),w64)
W32CC_PREFIX = x86_64
else
W32CC_PREFIX = i686
endif
ifeq ($(TARGETOS),w32)
STRIP = $(W32CC_PREFIX)-w64-mingw32-strip
W32STRIP32 = i686-w64-mingw32-strip
else
STRIP = strip
endif
W32CC = $(W32CC_PREFIX)-w64-mingw32-gcc
W32CC32 = i686-w64-mingw32-gcc
-include config.mk
#
# The generic speedo code
#
MKDIR=mkdir
MAKENSIS=makensis
WINE=wine
SHA1SUM := $(shell $(topsrc)/build-aux/getswdb.sh --find-sha1sum)
ifeq ($(SHA1SUM),false)
$(error The sha1sum tool is missing)
endif
SHA2SUM := $(shell $(topsrc)/build-aux/getswdb.sh --find-sha256sum)
ifeq ($(SHA2SUM),false)
$(error The sha256sum tool is missing)
endif
BUILD_ISODATE=$(shell date -u +%Y-%m-%d)
BUILD_DATESTR=$(subst -,,$(BUILD_ISODATE))
# The next two macros will work only after gnupg has been build.
ifeq ($(TARGETOS),w32)
INST_VERSION=$(shell head -1 $(idir)/INST_VERSION)
INST_PROD_VERSION=$(shell head -1 $(idir)/INST_PROD_VERSION)
endif
# List with packages
speedo_build_list = $(speedo_spkgs)
speedo_w64_build_list = $(speedo_w64_spkgs)
# To avoid running external commands during the read phase (":=" style
# assignments), we check that the targetos has been given
ifneq ($(TARGETOS),)
# Determine build and host system
build := $(shell $(topsrc)/autogen.sh --silent --print-build)
ifeq ($(TARGETOS),w32)
speedo_autogen_buildopt := --build-$(W32VERSION)
speedo_autogen_buildopt6 := --build-w64
host := $(shell $(topsrc)/autogen.sh --silent --print-host --build-$(W32VERSION))
host6:= $(shell $(topsrc)/autogen.sh --silent --print-host --build-w64)
speedo_host_build_option := --host=$(host) --build=$(build)
speedo_host_build_option6 := --host=$(host6) --build=$(build)
speedo_w32_cflags := -mms-bitfields
else
speedo_autogen_buildopt :=
host :=
speedo_host_build_option :=
speedo_w32_cflags :=
endif
ifeq ($(MAKE_J),)
speedo_makeopt=
else
speedo_makeopt=-j$(MAKE_J)
endif
# End non-empty TARGETOS
endif
# The playground area is our scratch area, where we unpack, build and
# install the packages.
$(stampdir)/stamp-directories:
$(MKDIR) -p $(root)
$(MKDIR) -p $(stampdir)
$(MKDIR) -p $(sdir)
$(MKDIR) -p $(bdir)
$(MKDIR) -p $(idir)
ifeq ($(TARGETOS),w32)
$(MKDIR) -p $(bdir6)
$(MKDIR) -p $(idir6)
endif
touch $(stampdir)/stamp-directories
# Frob the name $1 by converting all '-' and '+' characters to '_'.
define FROB_macro
$(subst +,_,$(subst -,_,$(1)))
endef
# Get the variable $(1) (which may contain '-' and '+' characters).
define GETVAR
$($(call FROB_macro,$(1)))
endef
# Set a couple of common variables.
define SETVARS
pkg="$(1)"; \
git="$(call GETVAR,speedo_pkg_$(1)_git)"; \
gitref="$(call GETVAR,speedo_pkg_$(1)_gitref)"; \
tar="$(call GETVAR,speedo_pkg_$(1)_tar)"; \
ver="$(call GETVAR,$(1)_ver)"; \
sha2="$(call GETVAR,$(1)_sha2)"; \
sha1="$(call GETVAR,$(1)_sha1)"; \
pkgsdir="$(sdir)/$(1)"; \
if [ "$(1)" = "gnupg" ]; then \
git=''; \
gitref=''; \
tar=''; \
pkgsdir="$(topsrc)"; \
fi; \
pkgbdir="$(bdir)/$(1)"; \
pkgcfg="$(call GETVAR,speedo_pkg_$(1)_configure)"; \
tmp="$(speedo_w32_cflags) \
$(call GETVAR,speedo_pkg_$(1)_extracflags)"; \
if [ x$$$$(echo "$$$$tmp" | tr -d '[:space:]')x != xx ]; then \
pkgextracflags="CFLAGS=\"$$$$tmp\""; \
else \
pkgextracflags=; \
fi; \
pkgmkdir="$(call GETVAR,speedo_pkg_$(1)_make_dir)"; \
pkgmkargs="$(call GETVAR,speedo_pkg_$(1)_make_args)"; \
pkgmkargs_inst="$(call GETVAR,speedo_pkg_$(1)_make_args_inst)"; \
pkgmkargs_uninst="$(call GETVAR,speedo_pkg_$(1)_make_args_uninst)"; \
export PKG_CONFIG="/usr/bin/pkg-config"; \
export PKG_CONFIG_PATH="$(idir)/lib/pkgconfig"; \
[ "$(TARGETOS)" != native ] && export PKG_CONFIG_LIBDIR=""; \
export SYSROOT="$(idir)"; \
export PATH="$(idir)/bin:$${PATH}"; \
export LD_LIBRARY_PATH="$(idir)/lib:$${LD_LIBRARY_PATH}"
endef
define SETVARS_W64
pkg="$(1)"; \
git="$(call GETVAR,speedo_pkg_$(1)_git)"; \
gitref="$(call GETVAR,speedo_pkg_$(1)_gitref)"; \
tar="$(call GETVAR,speedo_pkg_$(1)_tar)"; \
ver="$(call GETVAR,$(1)_ver)"; \
sha2="$(call GETVAR,$(1)_sha2)"; \
sha1="$(call GETVAR,$(1)_sha1)"; \
pkgsdir="$(sdir)/$(1)"; \
if [ "$(1)" = "gnupg" ]; then \
git=''; \
gitref=''; \
tar=''; \
pkgsdir="$(topsrc)"; \
fi; \
pkgbdir="$(bdir6)/$(1)"; \
pkgcfg="$(call GETVAR,speedo_pkg_w64_$(1)_configure)"; \
tmp="$(speedo_w32_cflags) \
$(call GETVAR,speedo_pkg_$(1)_extracflags)"; \
if [ x$$$$(echo "$$$$tmp" | tr -d '[:space:]')x != xx ]; then \
pkgextracflags="CFLAGS=\"$$$$tmp\""; \
else \
pkgextracflags=; \
fi; \
pkgmkdir="$(call GETVAR,speedo_pkg_$(1)_make_dir)"; \
pkgmkargs="$(call GETVAR,speedo_pkg_$(1)_make_args)"; \
pkgmkargs_inst="$(call GETVAR,speedo_pkg_$(1)_make_args_inst)"; \
pkgmkargs_uninst="$(call GETVAR,speedo_pkg_$(1)_make_args_uninst)"; \
export PKG_CONFIG="/usr/bin/pkg-config"; \
export PKG_CONFIG_PATH="$(idir6)/lib/pkgconfig"; \
[ "$(TARGETOS)" != native ] && export PKG_CONFIG_LIBDIR=""; \
export SYSROOT="$(idir6)"; \
export PATH="$(idir6)/bin:$${PATH}"; \
export LD_LIBRARY_PATH="$(idir6)/lib:$${LD_LIBRARY_PATH}"
endef
# Template for source packages.
# Note that the gnupg package is special: The package source dir is
# the same as the topsrc dir and thus we need to detect the gnupg
# package and cd to that directory. We also test that no in-source build
# has been done. autogen.sh is not run for gnupg.
#
define SPKG_template
$(stampdir)/stamp-$(1)-00-unpack: $(stampdir)/stamp-directories
@echo "speedo: /*"
@echo "speedo: * $(1)"
@echo "speedo: */"
@(set -e; cd $(sdir); \
$(call SETVARS,$(1)); \
if [ "$(WHAT)" = "this" ]; then \
echo "speedo: using included source"; \
elif [ "$(1)" = "gnupg" ]; then \
cd $$$${pkgsdir}; \
if [ -f config.log ]; then \
echo "GnuPG has already been build in-source" >&2 ;\
echo "Please run \"make distclean\" and retry" >&2 ;\
exit 1 ; \
fi; \
echo "speedo: unpacking gnupg not needed"; \
elif [ -n "$$$${git}" ]; then \
echo "speedo: unpacking $(1) from $$$${git}:$$$${gitref}"; \
git clone -b "$$$${gitref}" "$$$${git}" "$$$${pkg}"; \
cd "$$$${pkg}"; \
AUTOGEN_SH_SILENT=1 ./autogen.sh; \
elif [ -n "$$$${tar}" ]; then \
tar2="$(OVERRIDE_TARBALLS)/$$$$(basename $$$${tar})";\
if [ -f "$$$${tar2}" ]; then \
tar="$$$$tar2"; \
echo "speedo: /*"; \
echo "speedo: * Note: using an override"; \
echo "speedo: */"; \
fi; \
echo "speedo: unpacking $(1) from $$$${tar}"; \
case "$$$${tar}" in \
*.gz) pretar=zcat ;; \
*.bz2) pretar=bzcat ;; \
*.xz) pretar=xzcat ;; \
*) pretar=cat ;; \
esac; \
[ -f tmp.tgz ] && rm tmp.tgz; \
case "$$$${tar}" in \
/*) $$$${pretar} < $$$${tar} | tar xf - ;; \
*) wget -q -O - $$$${tar} | tee tmp.tgz \
| $$$${pretar} | tar x$$$${opt}f - ;; \
esac; \
if [ -f tmp.tgz ]; then \
if [ -n "$$$${sha2}" ]; then \
tmp=$$$$($(SHA2SUM) <tmp.tgz|cut -d' ' -f1);\
if [ "$$$${tmp}" != "$$$${sha2}" ]; then \
echo "speedo:"; \
echo "speedo: ERROR: SHA-256 checksum mismatch for $(1)";\
echo "speedo:"; \
exit 1; \
fi; \
elif [ -n "$$$${sha1}" ]; then \
tmp=$$$$($(SHA1SUM) <tmp.tgz|cut -d' ' -f1);\
if [ "$$$${tmp}" != "$$$${sha1}" ]; then \
echo "speedo:"; \
echo "speedo: ERROR: SHA-1 checksum mismatch for $(1)";\
echo "speedo:"; \
exit 1; \
fi; \
else \
echo "speedo:"; \
echo "speedo: Warning: No checksum known for $(1)";\
echo "speedo:"; \
fi; \
rm tmp.tgz; \
fi; \
base=`echo "$$$${tar}" | sed -e 's,^.*/,,' \
| sed -e 's,\.tar.*$$$$,,'`; \
mv $$$${base} $(1); \
patch="$(patdir)/$(1)-$$$${base#$(1)-}.patch";\
patchx="$(patdir)/$(1).patch"; \
if [ -x "$$$${patch}" ]; then \
echo "speedo: applying patch $$$${patch}"; \
cd $(1); "$$$${patch}"; \
elif [ -x "$$$${patchx}" ]; then \
echo "speedo: applying patch $$$${patchx}";\
cd $(1); "$$$${patchx}"; \
elif [ -f "$$$${patch}" ]; then \
echo "speedo: warning: $$$${patch} is not executable"; \
fi; \
else \
echo "speedo: unpacking $(1) from UNKNOWN"; \
fi)
@touch $(stampdir)/stamp-$(1)-00-unpack
$(stampdir)/stamp-$(1)-01-configure: $(stampdir)/stamp-$(1)-00-unpack
@echo "speedo: configuring $(1)"
ifneq ($(findstring $(1),$(speedo_make_only_style)),)
@echo "speedo: configure run not required"
else ifneq ($(findstring $(1),$(speedo_gnupg_style)),)
@($(call SETVARS,$(1)); \
mkdir "$$$${pkgbdir}"; \
cd "$$$${pkgbdir}"; \
if [ -n "$(speedo_autogen_buildopt)" ]; then \
eval AUTOGEN_SH_SILENT=1 $(W32VERSION)root="$(idir)" \
"$$$${pkgsdir}/autogen.sh" \
$(speedo_autogen_buildopt) \
$$$${pkgcfg} $$$${pkgextracflags}; \
else \
eval "$$$${pkgsdir}/configure" \
--silent \
--enable-maintainer-mode \
--prefix="$(idir)" \
$$$${pkgcfg} $$$${pkgextracflags}; \
fi)
else
@($(call SETVARS,$(1)); \
mkdir "$$$${pkgbdir}"; \
cd "$$$${pkgbdir}"; \
eval "$$$${pkgsdir}/configure" \
--silent $(speedo_host_build_option) \
--prefix="$(idir)" \
$$$${pkgcfg} $$$${pkgextracflags}; \
)
endif
@touch $(stampdir)/stamp-$(1)-01-configure
# Note that unpack has no 64 bit version because it is just the source.
# Fixme: We should use templates to create the standard and w64
# version of these rules.
$(stampdir)/stamp-w64-$(1)-01-configure: $(stampdir)/stamp-$(1)-00-unpack
@echo "speedo: configuring $(1) (64 bit)"
ifneq ($(findstring $(1),$(speedo_make_only_style)),)
@echo "speedo: configure run not required"
else ifneq ($(findstring $(1),$(speedo_gnupg_style)),)
@($(call SETVARS_W64,$(1)); \
mkdir "$$$${pkgbdir}"; \
cd "$$$${pkgbdir}"; \
if [ -n "$(speedo_autogen_buildopt)" ]; then \
eval AUTOGEN_SH_SILENT=1 w64root="$(idir6)" \
"$$$${pkgsdir}/autogen.sh" \
$(speedo_autogen_buildopt6) \
$$$${pkgcfg} $$$${pkgextracflags}; \
else \
eval "$$$${pkgsdir}/configure" \
--silent \
--enable-maintainer-mode \
--prefix="$(idir6)" \
$$$${pkgcfg} $$$${pkgextracflags}; \
fi)
else
@($(call SETVARS_W64,$(1)); \
mkdir "$$$${pkgbdir}"; \
cd "$$$${pkgbdir}"; \
eval "$$$${pkgsdir}/configure" \
--silent $(speedo_host_build_option6) \
--prefix="$(idir6)" \
$$$${pkgcfg} $$$${pkgextracflags}; \
)
endif
@touch $(stampdir)/stamp-w64-$(1)-01-configure
$(stampdir)/stamp-$(1)-02-make: $(stampdir)/stamp-$(1)-01-configure
@echo "speedo: making $(1)"
ifneq ($(findstring $(1),$(speedo_make_only_style)),)
@($(call SETVARS,$(1)); \
cd "$$$${pkgsdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
if test "$$$${pkg}" = zlib -a "$(TARGETOS)" != w32 ; then \
./configure --prefix="$(idir)" ; \
fi ;\
$(MAKE) --no-print-directory $(speedo_makeopt) $$$${pkgmkargs} V=0)
else
@($(call SETVARS,$(1)); \
cd "$$$${pkgbdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
$(MAKE) --no-print-directory $(speedo_makeopt) $$$${pkgmkargs} V=0)
endif
@touch $(stampdir)/stamp-$(1)-02-make
$(stampdir)/stamp-w64-$(1)-02-make: $(stampdir)/stamp-w64-$(1)-01-configure
@echo "speedo: making $(1) (64 bit)"
ifneq ($(findstring $(1),$(speedo_make_only_style)),)
@($(call SETVARS_W64,$(1)); \
cd "$$$${pkgsdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
$(MAKE) --no-print-directory $(speedo_makeopt) $$$${pkgmkargs} V=0)
else
@($(call SETVARS_W64,$(1)); \
cd "$$$${pkgbdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
$(MAKE) --no-print-directory $(speedo_makeopt) $$$${pkgmkargs} V=0)
endif
@touch $(stampdir)/stamp-w64-$(1)-02-make
# Note that post_install must come last because it may be empty and
# "; ;" is a syntax error.
$(stampdir)/stamp-$(1)-03-install: $(stampdir)/stamp-$(1)-02-make
@echo "speedo: installing $(1)"
ifneq ($(findstring $(1),$(speedo_make_only_style)),)
@($(call SETVARS,$(1)); \
cd "$$$${pkgsdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
$(MAKE) --no-print-directory $$$${pkgmkargs_inst} install V=0;\
$(call speedo_pkg_$(call FROB_macro,$(1))_post_install))
else
@($(call SETVARS,$(1)); \
cd "$$$${pkgbdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
$(MAKE) --no-print-directory $$$${pkgmkargs_inst} install-strip V=0;\
$(call speedo_pkg_$(call FROB_macro,$(1))_post_install))
endif
touch $(stampdir)/stamp-$(1)-03-install
$(stampdir)/stamp-w64-$(1)-03-install: $(stampdir)/stamp-w64-$(1)-02-make
@echo "speedo: installing $(1) (64 bit)"
ifneq ($(findstring $(1),$(speedo_make_only_style)),)
@($(call SETVARS_W64,$(1)); \
cd "$$$${pkgsdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
$(MAKE) --no-print-directory $$$${pkgmkargs_inst} install V=0;\
$(call speedo_pkg_$(call FROB_macro,$(1))_post_install))
else
@($(call SETVARS_W64,$(1)); \
cd "$$$${pkgbdir}"; \
test -n "$$$${pkgmkdir}" && cd "$$$${pkgmkdir}"; \
$(MAKE) --no-print-directory $$$${pkgmkargs_inst} install-strip V=0;\
$(call speedo_pkg_$(call FROB_macro,$(1))_post_install))
endif
touch $(stampdir)/stamp-w64-$(1)-03-install
$(stampdir)/stamp-final-$(1): $(stampdir)/stamp-$(1)-03-install
@($(call SETVARS,$(1)); \
printf "%-14s %-12s %s\n" $(1) "$$$${ver}" "$$$${sha1}" \
>> $(bdir)/pkg-versions.txt)
@echo "speedo: $(1) done"
@touch $(stampdir)/stamp-final-$(1)
$(stampdir)/stamp-w64-final-$(1): $(stampdir)/stamp-w64-$(1)-03-install
@echo "speedo: $(1) (64 bit) done"
@touch $(stampdir)/stamp-w64-final-$(1)
.PHONY : clean-$(1)
clean-$(1):
@echo "speedo: uninstalling $(1)"
@($(call SETVARS,$(1)); \
(cd "$$$${pkgbdir}" 2>/dev/null && \
$(MAKE) --no-print-directory \
$$$${pkgmkargs_uninst} uninstall V=0 ) || true;\
if [ "$(1)" = "gnupg" ]; then \
rm -fR "$$$${pkgbdir}" || true ;\
else \
rm -fR "$$$${pkgsdir}" "$$$${pkgbdir}" || true;\
fi)
-rm -f $(stampdir)/stamp-final-$(1) $(stampdir)/stamp-$(1)-*
.PHONY : build-$(1)
build-$(1): $(stampdir)/stamp-final-$(1)
.PHONY : report-$(1)
report-$(1):
@($(call SETVARS,$(1)); \
echo -n $(1):\ ; \
if [ -n "$$$${git}" ]; then \
if [ -e "$$$${pkgsdir}/.git" ]; then \
cd "$$$${pkgsdir}" && \
git describe ; \
else \
echo missing; \
fi \
elif [ -n "$$$${tar}" ]; then \
base=`echo "$$$${tar}" | sed -e 's,^.*/,,' \
| sed -e 's,\.tar.*$$$$,,'`; \
echo $$$${base} ; \
fi)
endef
# Insert the template for each source package.
$(foreach spkg, $(speedo_spkgs), $(eval $(call SPKG_template,$(spkg))))
$(stampdir)/stamp-final: $(stampdir)/stamp-directories clean-pkg-versions
ifeq ($(TARGETOS),w32)
$(stampdir)/stamp-final: $(addprefix $(stampdir)/stamp-w64-final-,$(speedo_w64_build_list))
endif
$(stampdir)/stamp-final: $(addprefix $(stampdir)/stamp-final-,$(speedo_build_list))
touch $(stampdir)/stamp-final
clean-pkg-versions:
@: >$(bdir)/pkg-versions.txt
all-speedo: $(stampdir)/stamp-final
ifneq ($(TARGETOS),w32)
@(set -e;\
cd "$(idir)"; \
echo "speedo: Making RPATH relative";\
for d in bin sbin libexec lib; do \
for f in $$(find $$d -type f); do \
if file $$f | grep ELF >/dev/null; then \
$(PATCHELF) --set-rpath '$$ORIGIN/../lib' $$f; \
fi; \
done; \
done; \
echo "sysconfdir = /etc/gnupg" >bin/gpgconf.ctl ;\
echo "rootdir = $(idir)" >>bin/gpgconf.ctl ;\
echo "speedo: /*" ;\
echo "speedo: * Now copy $(idir)/ to the final location and" ;\
echo "speedo: * adjust $(idir)/bin/gpgconf.ctl accordingly" ;\
echo "speedo: * Or run:" ;\
echo "speedo: * make -f $(topsrc)/build-aux/speedo.mk install SYSROOT=/usr/local/gnupg26" ;\
echo "speedo: * ldconfig -n /usr/local/gnupg26/lib";\
echo "speedo: */")
endif
# No dependencies for the install target; instead we test whether
# some of the to be installed files are available. This avoids
# accidental rebuilds under a wrong account.
install-speedo:
ifneq ($(TARGETOS),w32)
@(set -e; \
cd "$(idir)"; \
if [ x"$$SYSROOT" = x ]; then \
echo "speedo: ERROR: SYSROOT has not been given";\
echo "speedo: Set SYSROOT to the desired install directory";\
echo "speedo: Example:";\
echo "speedo: make -f $(topsrc)/build-aux/speedo.mk install SYSROOT=/usr/local/gnupg26";\
echo "speedo: ldconfig -n /usr/local/gnupg26/lib";\
exit 1;\
fi;\
if [ ! -d "$$SYSROOT"/bin ]; then if ! mkdir "$$SYSROOT"/bin; then \
echo "speedo: error creating target directory";\
exit 1;\
fi; fi;\
if ! touch "$$SYSROOT"/bin/gpgconf.ctl; then \
echo "speedo: Error writing $$SYSROOT/bin/gpgconf.ctl";\
echo "speedo: Please check the permissions";\
exit 1;\
fi;\
if [ ! -f bin/gpgconf.ctl ]; then \
echo "speedo: ERROR: Nothing to install";\
echo "speedo: Please run a build first";\
echo "speedo: Example:";\
echo "speedo: make -f build-aux/speedo.mk native";\
exit 1;\
fi;\
echo "speedo: Installing files to $$SYSROOT";\
find . -type f -executable \
-exec install -Dm 755 "{}" "$$SYSROOT/{}" \; ;\
find . -type f \! -executable \
-exec install -Dm 644 "{}" "$$SYSROOT/{}" \; ;\
echo "sysconfdir = /etc/gnupg" > "$$SYSROOT"/bin/gpgconf.ctl ;\
echo "rootdir = $$SYSROOT" >> "$$SYSROOT"/bin/gpgconf.ctl ;\
echo '/*' ;\
echo " * Installation to $$SYSROOT done" ;\
echo ' */' )
endif
report-speedo: $(addprefix report-,$(speedo_build_list))
# Just to check if we caught all stamps.
clean-stamps:
$(RM) -fR $(stampdir)
clean-speedo:
$(RM) -fR PLAY
#
# Windows installer
#
# {{{
ifeq ($(TARGETOS),w32)
dist-source: installer
for i in 00 01 02 03; do sleep 1;touch PLAY/stamps/stamp-*-${i}-*;done
(set -e;\
tarname="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).tar" ;\
[ -f "$$tarname" ] && rm "$$tarname" ;\
tar -C $(topsrc) -cf "$$tarname" --exclude-backups --exclude-vcs \
--transform='s,^\./,$(INST_NAME)-$(INST_VERSION)/,' \
--anchored --exclude './PLAY' . ;\
tar --totals -rf "$$tarname" --exclude-backups --exclude-vcs \
--transform='s,^,$(INST_NAME)-$(INST_VERSION)/,' \
PLAY/stamps/stamp-*-00-unpack PLAY/src swdb.lst swdb.lst.sig ;\
[ -f "$$tarname".xz ] && rm "$$tarname".xz;\
xz -T0 "$$tarname" ;\
)
# Extract the two latest news entries. */
$(bdir)/NEWS.tmp: $(topsrc)/NEWS
awk '/^Notewo/ {if(okay>1){exit}; okay++};okay {print $0}' \
<$(topsrc)/NEWS >$(bdir)/NEWS.tmp
# Sort the file with the package versions.
$(bdir)/pkg-versions.sorted: $(bdir)/pkg-versions.txt
grep -v '^gnupg ' <$(bdir)/pkg-versions.txt \
| sort | uniq >$(bdir)/pkg-versions.sorted
$(bdir)/README.txt: $(bdir)/NEWS.tmp $(topsrc)/README $(w32src)/README.txt \
$(w32src)/pkg-copyright.txt $(bdir)/pkg-versions.sorted
sed -e '/^;.*/d;' \
-e '/!NEWSFILE!/{r $(bdir)/NEWS.tmp' -e 'd;}' \
-e '/!GNUPGREADME!/{r $(topsrc)/README' -e 'd;}' \
-e '/!PKG-COPYRIGHT!/{r $(w32src)/pkg-copyright.txt' -e 'd;}' \
-e '/!PKG-VERSIONS!/{r $(bdir)/pkg-versions.sorted' -e 'd;}' \
-e 's,!VERSION!,$(INST_VERSION),g' \
< $(w32src)/README.txt \
| sed -e '/^#/d' \
| awk '{printf "%s\r\n", $$0}' >$(bdir)/README.txt
$(bdir)/g4wihelp.dll: $(w32src)/g4wihelp.c $(w32src)/exdll.h $(w32src)/exdll.c
(set -e; cd $(bdir); \
$(W32CC32) -DUNICODE -static-libgcc -I . -O2 -c \
-o exdll.o $(w32src)/exdll.c; \
$(W32CC32) -DUNICODE -static-libgcc -I. -shared -O2 \
-o g4wihelp.dll $(w32src)/g4wihelp.c exdll.o \
-lwinmm -lgdi32 -luserenv \
-lshell32 -loleaut32 -lshlwapi -lmsimg32; \
$(W32STRIP32) g4wihelp.dll)
w32_insthelpers: $(bdir)/g4wihelp.dll
$(bdir)/inst-options.ini: $(w32src)/inst-options.ini
cat $(w32src)/inst-options.ini >$(bdir)/inst-options.ini
extra_installer_options =
# Note that we sign only when doing the final installer.
installer: all w32_insthelpers $(w32src)/inst-options.ini $(bdir)/README.txt
(set -e;\
cd "$(idir)"; \
if echo "$(idir)" | grep -q '/PLAY-release/' ; then \
for f in $(AUTHENTICODE_FILES); do \
if [ -f "bin/$$f" ]; then \
$(call AUTHENTICODE_sign,"bin/$$f","bin/$$f");\
elif [ -f "libexec/$$f" ]; then \
$(call AUTHENTICODE_sign,"libexec/$$f","libexec/$$f");\
else \
echo "speedo: WARNING: file '$$f' not available for signing";\
fi;\
done; \
fi \
)
$(MAKENSIS) -V2 \
-DINST_DIR=$(idir) \
-DINST6_DIR=$(idir6) \
-DBUILD_DIR=$(bdir) \
-DTOP_SRCDIR=$(topsrc) \
-DW32_SRCDIR=$(w32src) \
-DBUILD_ISODATE=$(BUILD_ISODATE) \
-DBUILD_DATESTR=$(BUILD_DATESTR) \
-DNAME=$(INST_NAME) \
-DVERSION=$(INST_VERSION) \
-DPROD_VERSION=$(INST_PROD_VERSION) \
$(extra_installer_options) $(w32src)/inst.nsi
@echo "Ready: $(idir)/$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe"
# We use the installer target to ensure everything is done and signed
wixlib: installer $(bdir)/README.txt $(w32src)/wixlib.wxs
if [ -z "$$(which $(WINE))" ]; then \
echo "ERROR: For the w32-wixlib wine needs to be installed."; \
echo "ERROR: see 'help-w32-wixlib'"; \
exit 1; \
fi;
if [ ! -d "$(WIXPREFIX)" ]; then \
echo "ERROR: You must set WIXPREFIX to an installation of wixtools."; \
echo "ERROR: see 'help-w32-wixlib'"; \
exit 1; \
fi;
(if [ -z "$$WINEPREFIX" ]; then \
WINEPREFIX="$$HOME/.wine"; \
if [ ! -e "$$WINEPREFIX/dosdevices" ]; then \
echo "ERROR: No wine prefix found under $$WINEPREFIX"; \
exit 1; \
fi; \
fi; \
WINEINST=$$WINEPREFIX/dosdevices/k:; \
WINESRC=$$WINEPREFIX/dosdevices/i:; \
WINEBUILD=$$WINEPREFIX/dosdevices/j:; \
if [ -e "$$WINEINST" ]; then \
echo "ERROR: $$WINEINST already exists. Please remove."; \
exit 1; \
fi; \
if [ -e "$$WINESRC" ]; then \
echo "ERROR: $$WINESRC already exists. Please remove."; \
exit 1; \
fi; \
if [ -e "$$WINEBUILD" ]; then \
echo "ERROR: $$WINEBUILD already exists. Please remove."; \
exit 1; \
fi; \
echo "$(INST_NAME)" > $(bdir)/VERSION; \
echo "$(INST_VERSION)" >> $(bdir)/VERSION; \
MSI_VERSION=$$(echo $(INST_VERSION) | tr -s \\-beta .); \
(ln -s $(idir) $$WINEINST; \
ln -s $(w32src) $$WINESRC; \
ln -s $(bdir) $$WINEBUILD; \
$(WINE) $(WIXPREFIX)/candle.exe \
-dSourceDir=k: \
-dBuildDir=j: \
-dVersion=$$MSI_VERSION \
-out k:\\$(INST_NAME).wixobj \
-pedantic -wx i:\\wixlib.wxs ;\
$(WINE) $(WIXPREFIX)/lit.exe \
-out k:\\$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).wixlib \
-bf \
-wx \
-pedantic \
k:\\$(INST_NAME).wixobj \
); \
(rm $$WINEINST; rm $$WINESRC; rm $$WINEBUILD;) \
)
define MKSWDB_commands
( pref="#+macro: gnupg26_w32_$(3)" ;\
echo "$${pref}ver $(INST_VERSION)_$(BUILD_DATESTR)" ;\
echo "$${pref}date $(2)" ;\
echo "$${pref}size $$(wc -c <$(1)|awk '{print int($$1/1024)}')k";\
echo "$${pref}sha1 $$(sha1sum <$(1)|cut -d' ' -f1)" ;\
echo "$${pref}sha2 $$(sha256sum <$(1)|cut -d' ' -f1)" ;\
) | tee $(1).swdb
endef
# Sign the file $1 and save the result as $2
define AUTHENTICODE_sign
(set -e; \
if gpg-authcode-sign.sh --version >/dev/null; then \
gpg-authcode-sign.sh "$(1)" "$(2)"; \
else \
echo 2>&1 "warning: Please install gpg-authcode-sign.sh to sign files." ;\
[ "$(1)" != "$(2)" ] && cp "$(1)" "$(2)" ;\
fi)
endef
# Help target for testing to sign a file.
# Usage: make -f speedo.mk test-authenticode-sign TARGETOS=w32 FILE=foo.exe
test-authenticode-sign:
(set -e; \
echo "Test signining of $(FILE)" ; \
$(call AUTHENTICODE_sign,"$(FILE)","$(FILE)");\
)
# Build the installer from the source tarball.
installer-from-source: dist-source
(set -e;\
[ -d PLAY-release ] && rm -rf PLAY-release; \
mkdir PLAY-release;\
cd PLAY-release; \
tar xJf "../$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).tar.xz";\
cd $(INST_NAME)-$(INST_VERSION); \
$(MAKE) -f build-aux/speedo.mk this-w32-installer SELFCHECK=0;\
if [ -d "$(WIXPREFIX)" -a x"$(WITH_WIXLIB)" = x1 ]; then \
$(MAKE) -f build-aux/speedo.mk this-w32-wixlib SELFCHECK=0;\
fi; \
reldate="$$(date -u +%Y-%m-%d)" ;\
exefile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe" ;\
cp "PLAY/inst/$$exefile" ../.. ;\
exefile="../../$$exefile" ;\
$(call MKSWDB_commands,$${exefile},$${reldate}); \
msifile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).wixlib"; \
if [ -e "PLAY/inst/$${msifile}" ]; then \
cp "PLAY/inst/$$msifile" ../..; \
msifile="../../$$msifile" ; \
$(call MKSWDB_commands,$${msifile},$${reldate},"wixlib_"); \
fi \
)
# This target repeats some of the installer-from-source steps but it
# is intended to be called interactively, so that the passphrase can be
# entered.
sign-installer:
@(set -e; \
cd PLAY-release; \
cd $(INST_NAME)-$(INST_VERSION); \
reldate="$$(date -u +%Y-%m-%d)" ;\
exefile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).exe" ;\
msifile="$(INST_NAME)-$(INST_VERSION)_$(BUILD_DATESTR).wixlib" ;\
echo "speedo: /*" ;\
echo "speedo: * Signing installer" ;\
echo "speedo: */" ;\
$(call AUTHENTICODE_sign,"PLAY/inst/$$exefile","../../$$exefile");\
exefile="../../$$exefile" ;\
msifile="../../$$msifile" ;\
$(call MKSWDB_commands,$${exefile},$${reldate}); \
if [ -f "$${msifile}" ]; then \
$(call MKSWDB_commands,$${msifile},$${reldate},"wixlib_"); \
fi; \
echo "speedo: /* (osslsigncode verify disabled) */" ;\
echo osslsigncode verify $${exefile} \
)
endif
# }}} W32
#
-# Check availibility of standard tools and prepare everything.
+# Check availability of standard tools and prepare everything.
#
check-tools: $(stampdir)/stamp-directories
#
# Mark phony targets
#
.PHONY: all all-speedo report-speedo clean-stamps clean-speedo installer \
w32_insthelpers check-tools clean-pkg-versions install-speedo install
diff --git a/build-aux/speedo/w32/README.txt b/build-aux/speedo/w32/README.txt
index 7c2909507..df7b3a807 100644
--- a/build-aux/speedo/w32/README.txt
+++ b/build-aux/speedo/w32/README.txt
@@ -1,88 +1,88 @@
;; README.txt -*- coding: latin-1; -*-
;; This is the README installed for Windows. Lines with a
;; semicolon in the first column are considered a comment and not
;; included in the actually installed version. Certain keywords are
;; replaced by the Makefile; those words are enclosed by exclamation
;; marks.
GNU Privacy Guard for Windows
===============================
This is GnuPG for Windows, version !VERSION!.
Content:
1. Important notes
2. Changes
3. GnuPG README file
4. Package versions
5. Legal notices
1. Important Notes
==================
This is the core part of the GnuPG system as used by several other
frontend programs. This installer does not provide any graphical
frontend and thus almost everything needs to be done on the command
line. However, a small native Windows GUI tool is included which is
used by GnuPG to ask for passphrases. It provides only the basic
functionality and is installed under the name "pinentry-basic.exe".
Other software using this core component may install a different
version of such a tool under the name "pinentry.exe" or configure the
gpg-agent to use that version.
See https://gnupg.org for latest news. HowTo documents and manuals
can be found there but some have also been installed on your machine.
Development and maintenance of GnuPG is mostly financed by donations;
please see https://gnupg.org/donate/ for details.
2. Record of Changes
====================
This is a list of changes to the GnuPG core for this and the previous
release.
!NEWSFILE!
3. GnuPG README File
====================
Below is the README file as distributed with the GnuPG source.
!GNUPGREADME!
4. Software Versions of the Included Packages
=============================================
-GnuPG for Windows depends on several independet developed packages
+GnuPG for Windows depends on several independent developed packages
which are part of the installation. These packages along with their
version numbers and the SHA-1 checksums of their compressed tarballs
are listed here:
!PKG-VERSIONS!
5. Legal Notices Pertaining to the Individual Packages
======================================================
GnuPG for Windows consist of several independent developed packages,
available under different license conditions. Most of these packages
are however available under the GNU General Public License (GNU GPL).
Common to all is that they are free to use without restrictions, may
be modified and that modifications may be distributed. If the source
file (i.e. gnupg-w32-VERSION_DATE.tar.xz) is distributed along with
the installer and the use of the GNU GPL has been pointed out,
distribution is in all cases possible.
What follows is a list of copyright statements.
!PKG-COPYRIGHT!
***end of file ***
diff --git a/build-aux/speedo/w32/g4wihelp.c b/build-aux/speedo/w32/g4wihelp.c
index bae4b837c..33d186c40 100644
--- a/build-aux/speedo/w32/g4wihelp.c
+++ b/build-aux/speedo/w32/g4wihelp.c
@@ -1,1323 +1,1323 @@
/* g4wihelp.c - NSIS Helper DLL used with gpg4win.
* Copyright (C) 2005, 2023 g10 Code GmbH
* Copyright (C) 2001 Justin Frankel
* Copyright (C) 2016, 2017 Intevation GmbH
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any
* damages arising from the use of this software.
*
* Permission is granted to anyone to use this software for any
* purpose, including commercial applications, and to alter it and
* redistribute it freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must
* not claim that you wrote the original software. If you use this
* software in a product, an acknowledgment in the product
* documentation would be appreciated but is not required.
*
* 2. Altered source versions must be plainly marked as such, and must
* not be misrepresented as being the original software.
*
* 3. This notice may not be removed or altered from any source
* distribution.
************************************************************
* The code for the splash screen has been taken from the Splash
* plugin of the NSIS 2.04 distribution. That code comes without
- * explicit copyright notices in tyhe source files or author names, it
+ * explicit copyright notices in the source files or author names, it
* seems that it has been written by Justin Frankel; not sure about
* the year, though. [wk 2005-11-28]
*
* Fixed some compiler warnings. [wk 2014-02-24].
* Merged code from GnuPG version. [wk 2023-04-24].
*
* Compile time macros:
* ENABLE_SLIDE_SHOW :: Define for Gpg4win.
*/
#include <windows.h>
#include <stdio.h>
#include <tlhelp32.h>
#include <psapi.h>
#include <stdio.h>
#include <string.h>
#include "exdll.h"
/* We keep some code here for documentation reasons. That code has not
* yet been converted to the Unicode NSIS plugin API. */
/* #define ENABLE_SOUND_GADGET 1 */
/* #define ENABLE_SPLASH_GADGET 1 */
/* #define ENABLE_SERVICE_MANAGEMENT 1 */
static HINSTANCE g_hInstance; /* Our Instance. */
static HWND g_hwndParent; /* Handle of parent window or NULL. */
static HBITMAP g_hbm; /* Handle of the splash image. */
static int sleepint; /* Milliseconds to show the spals image. */
#ifdef ENABLE_SLIDE_SHOW
void
slide_stop(HWND hwndParent, int string_size, TCHAR *variables, stack_t **stacktop);
#endif
/* Standard entry point for DLLs. */
int WINAPI
DllMain (HANDLE hinst, DWORD reason, LPVOID reserved)
{
if (reason == DLL_PROCESS_ATTACH)
g_hInstance = hinst;
else if (reason == DLL_PROCESS_DETACH)
{
#ifdef ENABLE_SLIDE_SHOW
slide_stop (NULL, 0, NULL, NULL);
#endif
}
return TRUE;
}
/* Dummy function for testing. */
void __declspec(dllexport)
dummy (HWND hwndParent, int string_size, LPTSTR variables,
stack_t **stacktop, extra_parameters_t *extra)
{
g_hwndParent = hwndParent;
EXDLL_INIT();
// note if you want parameters from the stack, pop them off in order.
// i.e. if you are called via exdll::myFunction file.dat poop.dat
// calling popstring() the first time would give you file.dat,
// and the second time would give you poop.dat.
// you should empty the stack of your parameters, and ONLY your
// parameters.
/* Let's dump the variables. */
{
char line[512];
char *p;
const unsigned char *s = (void*)g_variables;
int i,j;
for (i=0; i < string_size* __INST_LAST; i+=32, s += 32)
{
for (j=0; j < 32; j++)
if (s[j])
break;
if (j != 32)
{
p = line;
*p = 0;
snprintf (p, 10, "%05x: ", i);
p += strlen (p);
for (j=0; j < 32; j++)
{
snprintf (p, 10, "%02x", s[j]);
p += strlen (p);
}
strcat (p, " |");
p += strlen (p);
for (j=0; j < 32; j++)
{
if (s[j] >= 32 && s[j] < 127)
*p = s[j];
else
*p = '.';
p++;
}
strcat (p, "|");
OutputDebugStringA (line);
}
}
}
{
wchar_t buf[1024];
swprintf(buf, 1024,
L"stringsize=%d\r\n$0=%s\r\n$1=%s\r\n$R0=%s\r\n$R1=%s\r\n",
string_size,
getuservariable(INST_0),
getuservariable(INST_1),
getuservariable(INST_R0),
getuservariable(INST_R1));
MessageBoxW(g_hwndParent,buf,0,MB_OK);
swprintf (buf, 1024,
L"autoclose =%d\r\n"
"all_user_var =%d\r\n"
"exec_error =%d\r\n"
"abort =%d\r\n"
"exec_reboot =%d\r\n"
"reboot_called=%d\r\n"
"api_version =%d\r\n"
"silent =%d\r\n"
"instdir_error=%d\r\n"
"rtl =%d\r\n"
"errlvl =%d\r\n",
extra->exec_flags->autoclose,
extra->exec_flags->all_user_var,
extra->exec_flags->exec_error,
extra->exec_flags->abort,
extra->exec_flags->exec_reboot,
extra->exec_flags->reboot_called,
extra->exec_flags->plugin_api_version,
extra->exec_flags->silent,
extra->exec_flags->instdir_error,
extra->exec_flags->rtl,
extra->exec_flags->errlvl);
MessageBoxW(g_hwndParent,buf,0,MB_OK);
}
}
void __declspec(dllexport)
runonce (HWND hwndParent, int string_size, LPTSTR variables,
stack_t **stacktop, extra_parameters_t *extra)
{
LPCWSTR result;
g_hwndParent = hwndParent;
EXDLL_INIT();
CreateMutexW (NULL, 0, getuservariable(INST_R0));
result = GetLastError ()? L"1" : L"0";
setuservariable (INST_R0, result);
}
#ifdef ENABLE_SOUND_GADGET
void __declspec(dllexport)
playsound (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
char fname[MAX_PATH];
g_hwndParent = hwndParent;
EXDLL_INIT();
if (popstring(fname, sizeof fname))
return;
PlaySound (fname, NULL, SND_ASYNC|SND_FILENAME|SND_NODEFAULT);
}
void __declspec(dllexport)
stopsound (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
g_hwndParent = hwndParent;
EXDLL_INIT();
PlaySound (NULL, NULL, 0);
}
#endif /*ENABLE_SOUND_GADGET*/
#ifdef ENABLE_SPLASH_GADGET
/* Windows procedure to control the splashimage. This one pauses the
execution until the sleep time is over or the user closes this
windows. */
static LRESULT CALLBACK
splash_wndproc (HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
LRESULT result = 0;
switch (uMsg)
{
case WM_CREATE:
{
BITMAP bm;
RECT vp;
GetObject(g_hbm, sizeof(bm), (LPSTR)&bm);
SystemParametersInfo(SPI_GETWORKAREA, 0, &vp, 0);
SetWindowLong(hwnd,GWL_STYLE,0);
SetWindowPos(hwnd,NULL,
vp.left+(vp.right-vp.left-bm.bmWidth)/2,
vp.top+(vp.bottom-vp.top-bm.bmHeight)/2,
bm.bmWidth,bm.bmHeight,
SWP_NOZORDER);
ShowWindow(hwnd,SW_SHOW);
SetTimer(hwnd,1,sleepint,NULL);
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
RECT r;
HDC curdc=BeginPaint(hwnd,&ps);
HDC hdc=CreateCompatibleDC(curdc);
HBITMAP oldbm;
GetClientRect(hwnd,&r);
oldbm=(HBITMAP)SelectObject(hdc,g_hbm);
BitBlt(curdc,r.left,r.top,r.right-r.left,r.bottom-r.top,
hdc,0,0,SRCCOPY);
SelectObject(hdc,oldbm);
DeleteDC(hdc);
EndPaint(hwnd,&ps);
}
break;
case WM_CLOSE:
break;
case WM_TIMER:
case WM_LBUTTONDOWN:
DestroyWindow(hwnd);
/*(fall through)*/
default:
result = DefWindowProc (hwnd, uMsg, wParam, lParam);
}
return result;
}
/* Display a splash screen. Call as
g4wihelp::showsplash SLEEP FNAME
With SLEEP being the time in milliseconds to show the splashscreen
and FNAME the complete filename of the image. As of now only BMP
is supported.
*/
void __declspec(dllexport)
showsplash (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
static WNDCLASS wc;
char sleepstr[30];
char fname[MAX_PATH];
int err = 0;
char *p;
char classname[] = "_sp";
g_hwndParent = hwndParent;
EXDLL_INIT();
if (popstring(sleepstr, sizeof sleepstr))
err = 1;
if (popstring(fname, sizeof fname))
err = 1;
if (err)
return;
if (!*fname)
return; /* Nothing to do. */
for (sleepint=0, p=sleepstr; *p >= '0' && *p <= '9'; p++)
{
sleepint *= 10;
sleepint += *p - '0';
}
if (sleepint <= 0)
return; /* Nothing to do. */
wc.lpfnWndProc = splash_wndproc;
wc.hInstance = g_hInstance;
wc.hCursor = LoadCursor(NULL,IDC_ARROW);
wc.lpszClassName = classname;
if (!RegisterClass(&wc))
return; /* Error. */
g_hbm = LoadImage (NULL, fname, IMAGE_BITMAP,
0, 0 , LR_CREATEDIBSECTION|LR_LOADFROMFILE);
if (g_hbm)
{
MSG msg;
HWND hwnd;
hwnd = CreateWindowEx (WS_EX_TOOLWINDOW, classname, classname,
0, 0, 0, 0, 0, (HWND)hwndParent, NULL,
g_hInstance, NULL);
while (IsWindow(hwnd) && GetMessage ( &msg, hwnd, 0, 0))
{
DispatchMessage (&msg);
}
DeleteObject (g_hbm);
g_hbm = NULL;
}
UnregisterClass (classname, g_hInstance);
}
#endif /*ENABLE_SPLASH_GADGET*/
#ifdef ENABLE_SERVICE_MANAGEMENT
/* Use this to report unexpected errors. FIXME: This is really not
very descriptive. */
void
service_error (const char *str)
{
char buf[1024];
snprintf (buf, sizeof (buf), "error: %s: ec=%d\r\n", str,
GetLastError ());
MessageBox(g_hwndParent, buf, 0, MB_OK);
setuservariable (INST_R0, "1");
}
void __declspec(dllexport)
service_create (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
SC_HANDLE sc;
SC_HANDLE service;
const char *result = NULL;
char service_name[256];
char display_name[256];
char program[256];
int err = 0;
g_hwndParent = hwndParent;
EXDLL_INIT();
/* The expected stack layout: service_name, display_name, program. */
if (popstring (service_name, sizeof (service_name)))
err = 1;
if (!err && popstring (display_name, sizeof (display_name)))
err = 1;
if (!err && popstring (program, sizeof (program)))
err = 1;
if (err)
{
setuservariable (INST_R0, "1");
return;
}
sc = OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (sc == NULL)
{
service_error ("OpenSCManager");
return;
}
service = CreateService (sc, service_name, display_name,
SERVICE_ALL_ACCESS, SERVICE_WIN32_OWN_PROCESS,
/* Use SERVICE_DEMAND_START for testing.
FIXME: Currently not configurable by caller. */
SERVICE_AUTO_START,
SERVICE_ERROR_NORMAL, program,
NULL, NULL, NULL,
/* FIXME: Currently not configurable by caller. */
/* FIXME: LocalService or NetworkService
don't work for dirmngr right now. NOTE!
If you change it here, you also should
adjust make-msi.pl for the msi
installer. In the future, this should
be an argument to the function and then
the make-msi.pl script can extract it
from the invocation. */
NULL /* "NT AUTHORITY\\LocalService" */,
NULL);
if (service == NULL)
{
service_error ("CreateService");
CloseServiceHandle (sc);
return;
}
CloseServiceHandle (service);
result = GetLastError () ? "1":"0";
setuservariable (INST_R0, result);
return;
}
/* Requires g_hwndParent to be set! */
SC_HANDLE
service_lookup (char *service_name)
{
SC_HANDLE sc;
SC_HANDLE service;
sc = OpenSCManager (NULL, NULL, SC_MANAGER_ALL_ACCESS);
if (sc == NULL)
{
service_error ("OpenSCManager");
return NULL;
}
service = OpenService (sc, service_name, SC_MANAGER_ALL_ACCESS);
if (service == NULL)
{
/* Fail silently here. */
CloseServiceHandle (sc);
return NULL;
}
CloseServiceHandle (sc);
return service;
}
/* Returns status. */
void __declspec(dllexport)
service_query (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
SC_HANDLE service;
const char *result = NULL;
char service_name[256];
int err = 0;
SERVICE_STATUS status;
g_hwndParent = hwndParent;
EXDLL_INIT();
/* The expected stack layout: service_name argc [argv]. */
if (popstring (service_name, sizeof (service_name)))
err = 1;
if (err)
{
setuservariable (INST_R0, "ERROR");
return;
}
service = service_lookup (service_name);
if (service == NULL)
if (err == 0)
{
setuservariable (INST_R0, "MISSING");
return;
}
err = QueryServiceStatus (service, &status);
if (err == 0)
{
setuservariable (INST_R0, "ERROR");
CloseServiceHandle (service);
return;
}
CloseServiceHandle (service);
switch (status.dwCurrentState)
{
case SERVICE_START_PENDING:
result = "START_PENDING";
break;
case SERVICE_RUNNING:
result = "RUNNING";
break;
case SERVICE_PAUSE_PENDING:
result = "PAUSE_PENDING";
break;
case SERVICE_PAUSED:
result = "PAUSED";
break;
case SERVICE_CONTINUE_PENDING:
result = "CONTINUE_PENDING";
break;
case SERVICE_STOP_PENDING:
result = "STOP_PENDING";
break;
case SERVICE_STOPPED:
result = "STOPPED";
break;
default:
result = "UNKNOWN";
}
setuservariable (INST_R0, result);
return;
}
void __declspec(dllexport)
service_start (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
SC_HANDLE service;
const char *result = NULL;
char service_name[256];
char argc_str[256];
#define NR_ARGS 10
#define ARG_MAX 256
char argv_str[NR_ARGS][ARG_MAX];
const char *argv[NR_ARGS + 1];
int argc;
int i;
int err = 0;
g_hwndParent = hwndParent;
EXDLL_INIT();
/* The expected stack layout: service_name argc [argv]. */
if (popstring (service_name, sizeof (service_name)))
err = 1;
if (!err && popstring (argc_str, sizeof (argc_str)))
err = 1;
if (!err)
{
argc = atoi (argc_str);
for (i = 0; i < argc; i++)
{
if (popstring (argv_str[i], ARG_MAX))
{
err = 1;
break;
}
argv[i] = argv_str[i];
}
argv[i] = NULL;
}
if (err)
{
setuservariable (INST_R0, "1");
return;
}
service = service_lookup (service_name);
if (service == NULL)
return;
err = StartService (service, argc, argc == 0 ? NULL : argv);
if (err == 0)
{
service_error ("StartService");
CloseServiceHandle (service);
return;
}
CloseServiceHandle (service);
setuservariable (INST_R0, "0");
return;
}
void __declspec(dllexport)
service_stop (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
SC_HANDLE service;
const char *result = NULL;
char service_name[256];
int err = 0;
SERVICE_STATUS status;
DWORD timeout = 10000; /* 10 seconds. */
DWORD start_time;
g_hwndParent = hwndParent;
EXDLL_INIT();
/* The expected stack layout: service_name argc [argv]. */
if (popstring (service_name, sizeof (service_name)))
err = 1;
if (err)
{
setuservariable (INST_R0, "1");
return;
}
service = service_lookup (service_name);
if (service == NULL)
return;
err = QueryServiceStatus (service, &status);
if (err == 0)
{
service_error ("QueryService");
CloseServiceHandle (service);
return;
}
if (status.dwCurrentState != SERVICE_STOPPED
&& status.dwCurrentState != SERVICE_STOP_PENDING)
{
err = ControlService (service, SERVICE_CONTROL_STOP, &status);
if (err == 0)
{
service_error ("ControlService");
CloseServiceHandle (service);
return;
}
}
start_time = GetTickCount ();
while (status.dwCurrentState != SERVICE_STOPPED)
{
Sleep (1000); /* One second. */
if (!QueryServiceStatus (service, &status))
{
service_error ("QueryService");
CloseServiceHandle (service);
return;
}
if (status.dwCurrentState == SERVICE_STOPPED)
break;
if (GetTickCount () - start_time > timeout)
{
char buf[1024];
snprintf (buf, sizeof (buf),
"time out waiting for service %s to stop\r\n",
service_name);
MessageBox (g_hwndParent, buf, 0, MB_OK);
setuservariable (INST_R0, "1");
return;
}
}
CloseServiceHandle (service);
setuservariable (INST_R0, "0");
return;
}
void __declspec(dllexport)
service_delete (HWND hwndParent, int string_size, char *variables,
stack_t **stacktop, extra_parameters_t *extra)
{
SC_HANDLE service;
const char *result = NULL;
char service_name[256];
int err = 0;
g_hwndParent = hwndParent;
EXDLL_INIT();
/* The expected stack layout: service_name argc [argv]. */
if (popstring (service_name, sizeof (service_name)))
err = 1;
if (err)
{
setuservariable (INST_R0, "1");
return;
}
service = service_lookup (service_name);
if (service == NULL)
return;
err = DeleteService (service);
if (err == 0)
{
service_error ("DeleteService");
CloseServiceHandle (service);
return;
}
CloseServiceHandle (service);
setuservariable (INST_R0, "0");
return;
}
#endif /*ENABLE_SERVICE_MANAGEMENT*/
/* Extract config file parameters. FIXME: Not particularly robust.
We expect some reasonable formatting. The parser below is very
limited. It expects a command line option /c=FILE or /C=FILE,
where FILE must be enclosed in double-quotes if it contains spaces.
That file should contain a single section [gpg4win] and KEY=VALUE
pairs for each additional configuration file to install. Comments
are supported only on lines by themselves. VALUE can be quoted in
double-quotes, but does not need to be, unless it has whitespace at
the beginning or end. KEY can, for example, be "gpg.conf" (without
the quotes). */
void
config_init (char **keys, char **values, int max)
{
/* First, parse the command line. */
LPCWSTR wcmdline;
char *cmdline;
char *begin = NULL;
char *end = NULL;
char mark;
char *fname;
char *ptr;
FILE *conf;
*keys = NULL;
*values = NULL;
cmdline = malloc (4096);
if (!cmdline)
return;
wcmdline = getuservariable (INST_CMDLINE);
*cmdline = 0;
WideCharToMultiByte(CP_ACP, 0, wcmdline, -1, cmdline, 4095, NULL, NULL);
if (!*cmdline)
return;
mark = (*cmdline == '"') ? (cmdline++, '"') : ' ';
while (*cmdline && *cmdline != mark)
cmdline++;
if (mark == '"' && *cmdline)
cmdline++;
while (*cmdline && *cmdline == ' ')
cmdline++;
while (*cmdline)
{
/* We are at the beginning of a new argument. */
if (cmdline[0] == '/' && (cmdline[1] == 'C' || cmdline[1] == 'c')
&& cmdline[2] == '=')
{
cmdline += 3;
begin = cmdline;
}
while (*cmdline && *cmdline != ' ')
{
/* Skip over quoted parts. */
if (*cmdline == '"')
{
cmdline++;
while (*cmdline && *cmdline != '"')
cmdline++;
if (*cmdline)
cmdline++;
}
else
cmdline++;
}
if (begin && !end)
{
end = cmdline - 1;
break;
}
while (*cmdline && *cmdline == ' ')
cmdline++;
}
if (!begin || begin > end)
return;
/* Strip quotes. */
if (*begin == '"' && *end == '"')
{
begin++;
end--;
}
if (begin > end)
return;
fname = malloc (end - begin + 2);
if (!fname)
return;
ptr = fname;
while (begin <= end)
*(ptr++) = *(begin++);
*ptr = '\0';
conf = fopen (fname, "r");
free (fname);
free (cmdline);
if (!conf)
return;
while (max - 1 > 0)
{
char line[256];
char *ptr2;
if (fgets (line, sizeof (line), conf) == NULL)
break;
ptr = &line[strlen (line)];
while (ptr > line && (ptr[-1] == '\n' || ptr[-1] == '\r'
|| ptr[-1] == ' ' || ptr[-1] == '\t'))
ptr--;
*ptr = '\0';
ptr = line;
while (*ptr && (*ptr == ' ' || *ptr == '\t'))
ptr++;
/* Ignore comment lines. */
/* FIXME: Ignore section markers. */
if (*ptr == '\0' || *ptr == ';' || *ptr == '[')
continue;
begin = ptr;
while (*ptr && *ptr != '=' && *ptr != ' ' && *ptr != '\t')
ptr++;
end = ptr - 1;
while (*ptr && (*ptr == ' ' || *ptr == '\t'))
ptr++;
if (*ptr != '=')
continue;
ptr++;
if (begin > end)
continue;
/* We found a key. */
*keys = malloc (end - begin + 2);
if (!keys)
return;
ptr2 = *keys;
while (begin <= end)
*(ptr2++) = *(begin++);
*ptr2 = '\0';
*values = NULL;
while (*ptr && (*ptr == ' ' || *ptr == '\t'))
ptr++;
begin = ptr;
/* In this case, end points to the byte after the value, which
is OK because that is '\0'. */
end = &line[strlen (line)];
if (begin > end)
begin = end;
/* Strip quotes. */
if (*begin == '"' && end[-1] == '"')
{
begin++;
end--;
*end = '\0';
}
if (begin > end)
return;
*values = malloc (end - begin + 1);
ptr2 = *values;
while (begin <= end)
*(ptr2++) = *(begin++);
keys++;
values++;
max--;
}
fclose (conf);
*keys = NULL;
*values = NULL;
}
char *
config_lookup (char *key)
{
#define MAX_KEYS 128
static int initialised = 0;
static char *keys[MAX_KEYS];
static char *values[MAX_KEYS];
int i;
if (initialised == 0)
{
initialised = 1;
config_init (keys, values, MAX_KEYS);
#if 0
MessageBox(g_hwndParent, "Configuration File:", 0, MB_OK);
i = 0;
while (keys[i])
{
char buf[256];
sprintf (buf, "%s=%s\r\n", keys[i], values[i]);
MessageBox (g_hwndParent, buf, 0, MB_OK);
i++;
}
#endif
}
i = 0;
while (keys[i])
{
if (!strcmp (keys[i], key))
return values[i];
i++;
}
return NULL;
}
void __declspec(dllexport)
config_fetch (HWND hwndParent, int string_size, LPTSTR variables,
stack_t **stacktop, extra_parameters_t *extra)
{
char key[256];
int err = 0;
char *value;
g_hwndParent = hwndParent;
EXDLL_INIT();
/* The expected stack layout: key. */
if (PopStringNA (key, sizeof (key)))
err = 1;
if (err)
{
setuservariable (INST_R0, L"");
return;
}
value = config_lookup (key);
SetUserVariableA (INST_R0, value == NULL ? "" : value);
return;
}
void __declspec(dllexport)
config_fetch_bool (HWND hwndParent, int string_size, LPTSTR variables,
stack_t **stacktop, extra_parameters_t *extra)
{
char key[256];
int err = 0;
char *value;
int result;
g_hwndParent = hwndParent;
EXDLL_INIT();
/* The expected stack layout: key. */
if (PopStringNA (key, sizeof (key)))
err = 1;
if (err)
{
setuservariable (INST_R0, L"");
return;
}
value = config_lookup (key);
if (value == NULL || *value == '\0')
{
setuservariable (INST_R0, L"");
return;
}
result = 0;
if (!strcasecmp (value, "true")
|| !strcasecmp (value, "yes")
|| atoi (value) != 0)
result = 1;
SetUserVariableA (INST_R0, result == 0 ? "0" : "1");
return;
}
/* Return a string from the Win32 Registry or NULL in case of error.
Caller must release the return value. A NULL for root is an alias
for HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE in turn. */
static wchar_t *
read_w32_registry_string (HKEY root, const wchar_t *dir, const wchar_t *name)
{
HKEY root_key;
HKEY key_handle;
DWORD n1, nbytes, type;
wchar_t *result = NULL;
root_key = root;
if (!root_key)
root_key = HKEY_CURRENT_USER;
if (RegOpenKeyExW (root_key, dir, 0, KEY_READ, &key_handle))
{
if (root)
return NULL; /* no need for a RegClose, so return direct */
/* It seems to be common practise to fall back to HKLM. */
if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle))
return NULL; /* still no need for a RegClose, so return direct */
}
nbytes = 1;
if (RegQueryValueExW (key_handle, name, 0, NULL, NULL, &nbytes))
{
if (root)
goto leave;
/* Try to fallback to HKLM also for a missing value. */
RegCloseKey (key_handle);
if (RegOpenKeyExW (HKEY_LOCAL_MACHINE, dir, 0, KEY_READ, &key_handle))
return NULL; /* Nope. */
if (RegQueryValueExW (key_handle, name, 0, NULL, NULL, &nbytes))
goto leave;
}
result = calloc ((n1=nbytes+1), sizeof *result);
if (!result)
goto leave;
if (RegQueryValueExW (key_handle, name, 0, &type,
(unsigned char *)result, &n1))
{
free (result);
result = NULL;
goto leave;
}
result[nbytes] = 0; /* Make sure it is really a string */
leave:
RegCloseKey (key_handle);
return result;
}
/* Registry keys for PATH for HKLM and HKCU. */
#define ENV_HK HKEY_LOCAL_MACHINE
#define ENV_REG L"SYSTEM\\CurrentControlSet\\Control\\" \
"Session Manager\\Environment"
#define ENV_HK_USER HKEY_CURRENT_USER
#define ENV_REG_USER L"Environment"
/* Due to a bug in Windows7 (kb 2685893) we better put a lower limit
* than 8191 on the maximum length of the PATH variable. Note, that
* depending on the used toolchain we used to had a 259 byte limit in
* the past.
* [wk 2023-04-24]: Can this be lifted now that we use the wchar_t API?
*/
#define PATH_LENGTH_LIMIT 2047
void __declspec(dllexport)
path_add (HWND hwndParent, int string_size, LPTSTR variables,
stack_t **stacktop, extra_parameters_t *extra)
{
wchar_t dir[PATH_LENGTH_LIMIT];
wchar_t is_user_install[2];
wchar_t *path;
wchar_t *path_new;
size_t path_new_size;
wchar_t *comp;
const wchar_t delims[] = L";";
int is_user;
HKEY key_handle = 0;
HKEY root_key;
const wchar_t *env_reg;
/* wchar_t *tokctx; Context var for wcstok - not yet needed. */
g_hwndParent = hwndParent;
EXDLL_INIT();
setuservariable (INST_R0, L"0"); /* Default return value. */
/* The expected stack layout: path component. */
if (popstringn (dir, COUNTOF (dir)))
return;
dir[COUNTOF(dir)-1] = 0;
/* The expected stack layout: HKEY component. */
if (popstringn (is_user_install, COUNTOF (is_user_install)))
return;
is_user_install[COUNTOF(is_user_install)-1] = 0;
if (!wcscmp (is_user_install, L"1"))
{
root_key = ENV_HK_USER;
env_reg = ENV_REG_USER;
}
else
{
root_key = ENV_HK;
env_reg = ENV_REG;
}
path = read_w32_registry_string (root_key, env_reg, L"Path");
if (!path)
{
path = wcsdup (L"");
}
/* Old path plus semicolon plus dir plus terminating nul. */
path_new_size = wcslen (path) + 1 + wcslen (dir) + 1;
if (path_new_size > PATH_LENGTH_LIMIT)
{
MessageBox (g_hwndParent, L"PATH env variable too big", 0, MB_OK);
free (path);
return;
}
path_new = calloc (path_new_size, sizeof *path_new);
if (!path_new)
{
free (path);
return;
}
wcscpy (path_new, path);
wcscat (path_new, L";");
wcscat (path_new, dir);
/* Check if the directory already exists in the path. */
comp = wcstok (path, delims/*, &tokctx*/);
do
{
/* MessageBox (g_hwndParent, comp, 0, MB_OK); */
if (!comp)
break;
if (!wcscmp (comp, dir))
{
free (path);
free (path_new);
return;
}
comp = wcstok (NULL, delims/*, &tokctx*/);
}
while (comp);
free (path);
/* Update the path key. */
RegCreateKeyW (root_key, env_reg, &key_handle);
RegSetValueEx (key_handle, L"Path", 0, REG_EXPAND_SZ,
(unsigned char*)path_new,
wcslen (path_new) * sizeof *path_new);
RegCloseKey (key_handle);
SetEnvironmentVariableW(L"PATH", path_new);
free (path_new);
/* MessageBox (g_hwndParent, "XXX 9", 0, MB_OK); */
setuservariable (INST_R0, L"1"); /* success. */
}
void __declspec(dllexport)
path_remove (HWND hwndParent, int string_size, LPTSTR variables,
stack_t **stacktop, extra_parameters_t *extra)
{
wchar_t dir[PATH_LENGTH_LIMIT];
wchar_t is_user_install[2];
wchar_t *path;
wchar_t *path_new;
size_t path_new_size;
wchar_t *comp;
const wchar_t delims[] = L";";
HKEY key_handle = 0;
int changed = 0;
int count = 0;
HKEY root_key;
const wchar_t *env_reg;
/* wchar_t *tokctx; Context var for wcstok - not yet needed. */
g_hwndParent = hwndParent;
EXDLL_INIT();
setuservariable (INST_R0, L"0");
/* The expected stack layout: path component. */
if (popstringn (dir, COUNTOF (dir)))
return;
dir[COUNTOF(dir)-1] = 0;
/* The expected stack layout: HKEY component. */
if (popstringn (is_user_install, COUNTOF (is_user_install)))
return;
is_user_install[COUNTOF(is_user_install)-1] = 0;
if (!wcscmp (is_user_install, L"1"))
{
root_key = ENV_HK_USER;
env_reg = ENV_REG_USER;
}
else
{
root_key = ENV_HK;
env_reg = ENV_REG;
}
path = read_w32_registry_string (root_key, env_reg, L"Path");
if (!path)
return;
/* Old path plus semicolon plus dir plus terminating nul. */
path_new_size = wcslen (path) + 1;
path_new = calloc (path_new_size, sizeof *path_new);
if (!path_new)
{
free (path);
return;
}
/* Compose the new path. */
comp = wcstok (path, delims/*, &tokctx*/);
do
{
if (wcscmp (comp, dir))
{
if (count)
wcscat (path_new, L";");
wcscat (path_new, comp);
count++;
}
else
changed = 1;
}
while ((comp = wcstok (NULL, delims/*, &tokctx*/)));
free (path);
if (!changed)
{
free (path_new);
return;
}
/* Set a key for our CLSID. */
RegCreateKeyW (root_key, env_reg, &key_handle);
RegSetValueEx (key_handle, L"Path", 0, REG_EXPAND_SZ,
(unsigned char*)path_new,
wcslen (path_new) * sizeof *path_new);
RegCloseKey (key_handle);
free (path_new);
setuservariable (INST_R0, L"1"); /* success */
}
/** @brief Kill processes with the name name.
*
* This function tries to kill a process using ExitProcess.
*
* If it does not work it does not work. No return values.
* The intention is to make an effort to kill something during
* installation / uninstallation.
*
* The function signature is explained by NSIS.
*/
void __declspec(dllexport) __cdecl KillProc(HWND hwndParent,
int string_size,
char *variables,
stack_t **stacktop)
{
HANDLE h;
PROCESSENTRY32 pe32;
if (!stacktop || !*stacktop || !(*stacktop)->text)
{
ERRORPRINTF ("Invalid call to KillProc.");
return;
}
h = CreateToolhelp32Snapshot (TH32CS_SNAPPROCESS, 0);
if (h == INVALID_HANDLE_VALUE)
{
ERRORPRINTF ("Failed to create Toolhelp snapshot");
return;
}
pe32.dwSize = sizeof (PROCESSENTRY32);
if (!Process32First (h, &pe32))
{
ERRORPRINTF ("Failed to get first process");
CloseHandle (h);
return;
}
do
{
if (!wcscmp ((*stacktop)->text, pe32.szExeFile))
{
HANDLE hProc = OpenProcess (PROCESS_ALL_ACCESS, FALSE,
pe32.th32ProcessID);
if (!hProc)
{
ERRORPRINTF ("Failed to open process handle.");
continue;
}
if (!TerminateProcess (hProc, 1))
{
ERRORPRINTF ("Failed to terminate process.");
}
CloseHandle (hProc);
}
}
while (Process32Next (h, &pe32));
CloseHandle (h);
}
diff --git a/common/asshelp.c b/common/asshelp.c
index 957ca994d..f17a32e52 100644
--- a/common/asshelp.c
+++ b/common/asshelp.c
@@ -1,747 +1,747 @@
/* asshelp.c - Helper functions for Assuan
* Copyright (C) 2002, 2004, 2007, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "i18n.h"
#include "util.h"
#include "exechelp.h"
#include "sysutils.h"
#include "status.h"
#include "membuf.h"
#include "asshelp.h"
/* The type we use for lock_agent_spawning. */
#ifdef HAVE_W32_SYSTEM
# define lock_spawn_t HANDLE
#else
# define lock_spawn_t dotlock_t
#endif
/* The time we wait until the agent or the dirmngr are ready for
operation after we started them before giving up. */
#define SECS_TO_WAIT_FOR_AGENT 5
#define SECS_TO_WAIT_FOR_KEYBOXD 5
#define SECS_TO_WAIT_FOR_DIRMNGR 5
/* A bitfield that specifies the assuan categories to log. This is
identical to the default log handler of libassuan. We need to do
it ourselves because we use a custom log handler and want to use
the same assuan variables to select the categories to log. */
static int log_cats;
#define TEST_LOG_CAT(x) (!! (log_cats & (1 << (x - 1))))
/* The assuan log monitor used to temporary inhibit log messages from
* assuan. */
static int (*my_log_monitor) (assuan_context_t ctx,
unsigned int cat,
const char *msg);
static int
my_libassuan_log_handler (assuan_context_t ctx, void *hook,
unsigned int cat, const char *msg)
{
unsigned int dbgval;
if (! TEST_LOG_CAT (cat))
return 0;
dbgval = hook? *(unsigned int*)hook : 0;
if (!(dbgval & 1024))
return 0; /* Assuan debugging is not enabled. */
if (ctx && my_log_monitor && !my_log_monitor (ctx, cat, msg))
return 0; /* Temporary disabled. */
if (msg)
log_string (GPGRT_LOGLVL_DEBUG, msg);
return 1;
}
/* Setup libassuan to use our own logging functions. Should be used
early at startup. */
void
setup_libassuan_logging (unsigned int *debug_var_address,
int (*log_monitor)(assuan_context_t ctx,
unsigned int cat,
const char *msg))
{
char *flagstr;
flagstr = getenv ("ASSUAN_DEBUG");
if (flagstr)
log_cats = atoi (flagstr);
else /* Default to log the control channel. */
log_cats = (1 << (ASSUAN_LOG_CONTROL - 1));
my_log_monitor = log_monitor;
assuan_set_log_cb (my_libassuan_log_handler, debug_var_address);
}
/* Change the Libassuan log categories to those given by NEWCATS.
NEWCATS is 0 the default category of ASSUAN_LOG_CONTROL is
selected. Note, that setup_libassuan_logging overrides the values
given here. */
void
set_libassuan_log_cats (unsigned int newcats)
{
if (newcats)
log_cats = newcats;
else /* Default to log the control channel. */
log_cats = (1 << (ASSUAN_LOG_CONTROL - 1));
}
static gpg_error_t
send_one_option (assuan_context_t ctx, gpg_err_source_t errsource,
const char *name, const char *value, int use_putenv)
{
gpg_error_t err;
char *optstr;
(void)errsource;
if (!value || !*value)
err = 0; /* Avoid sending empty strings. */
else if (asprintf (&optstr, "OPTION %s%s=%s",
use_putenv? "putenv=":"", name, value) < 0)
err = gpg_error_from_syserror ();
else
{
err = assuan_transact (ctx, optstr, NULL, NULL, NULL, NULL, NULL, NULL);
xfree (optstr);
}
return err;
}
/* Send the assuan commands pertaining to the pinentry environment. The
OPT_* arguments are optional and may be used to override the
defaults taken from the current locale. */
gpg_error_t
send_pinentry_environment (assuan_context_t ctx,
gpg_err_source_t errsource,
const char *opt_lc_ctype,
const char *opt_lc_messages,
session_env_t session_env)
{
gpg_error_t err = 0;
#if defined(HAVE_SETLOCALE)
char *old_lc = NULL;
#endif
char *dft_lc = NULL;
const char *dft_ttyname;
int iterator;
const char *name, *assname, *value;
int is_default;
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, &assname)))
{
value = session_env_getenv_or_default (session_env, name, NULL);
if (!value)
continue;
if (assname)
err = send_one_option (ctx, errsource, assname, value, 0);
else
{
err = send_one_option (ctx, errsource, name, value, 1);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* Server too old; can't pass the new envvars. */
}
if (err)
return err;
}
dft_ttyname = session_env_getenv_or_default (session_env, "GPG_TTY",
&is_default);
if (dft_ttyname && !is_default)
dft_ttyname = NULL; /* We need the default value. */
/* Send the value for LC_CTYPE. */
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
old_lc = setlocale (LC_CTYPE, NULL);
if (old_lc)
{
old_lc = xtrystrdup (old_lc);
if (!old_lc)
return gpg_error_from_syserror ();
}
dft_lc = setlocale (LC_CTYPE, "");
#endif
if (opt_lc_ctype || (dft_ttyname && dft_lc))
{
err = send_one_option (ctx, errsource, "lc-ctype",
opt_lc_ctype ? opt_lc_ctype : dft_lc, 0);
}
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
if (old_lc)
{
setlocale (LC_CTYPE, old_lc);
xfree (old_lc);
}
#endif
if (err)
return err;
/* Send the value for LC_MESSAGES. */
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
old_lc = setlocale (LC_MESSAGES, NULL);
if (old_lc)
{
old_lc = xtrystrdup (old_lc);
if (!old_lc)
return gpg_error_from_syserror ();
}
dft_lc = setlocale (LC_MESSAGES, "");
#endif
if (opt_lc_messages || (dft_ttyname && dft_lc))
{
err = send_one_option (ctx, errsource, "lc-messages",
opt_lc_messages ? opt_lc_messages : dft_lc, 0);
}
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
if (old_lc)
{
setlocale (LC_MESSAGES, old_lc);
xfree (old_lc);
}
#endif
if (err)
return err;
return 0;
}
/* Lock a spawning process. The caller needs to provide the address
of a variable to store the lock information and the name or the
process. */
static gpg_error_t
lock_spawning (lock_spawn_t *lock, const char *homedir, const char *name,
int verbose)
{
char *fname;
(void)verbose;
*lock = NULL;
fname = make_absfilename_try
(homedir,
!strcmp (name, "agent")? "gnupg_spawn_agent_sentinel":
!strcmp (name, "dirmngr")? "gnupg_spawn_dirmngr_sentinel":
!strcmp (name, "keyboxd")? "gnupg_spawn_keyboxd_sentinel":
/* */ "gnupg_spawn_unknown_sentinel",
NULL);
if (!fname)
return gpg_error_from_syserror ();
*lock = dotlock_create (fname, 0);
xfree (fname);
if (!*lock)
return gpg_error_from_syserror ();
/* FIXME: We should use a timeout of 5000 here - however
make_dotlock does not yet support values other than -1 and 0. */
if (dotlock_take (*lock, -1))
return gpg_error_from_syserror ();
return 0;
}
/* Unlock the spawning process. */
static void
unlock_spawning (lock_spawn_t *lock, const char *name)
{
if (*lock)
{
(void)name;
dotlock_destroy (*lock);
*lock = NULL;
}
}
/* Helper to start a service. SECS gives the number of seconds to
* wait. SOCKNAME is the name of the socket to connect. VERBOSE is
* the usual verbose flag. CTX is the assuan context. CONNECT_FLAGS
* are the assuan connect flags. DID_SUCCESS_MSG will be set to 1 if
* a success messages has been printed.
*/
static gpg_error_t
wait_for_sock (int secs, int module_name_id, const char *sockname,
unsigned int connect_flags,
int verbose, assuan_context_t ctx, int *did_success_msg)
{
gpg_error_t err = 0;
int target_us = secs * 1000000;
int elapsed_us = 0;
/*
* 977us * 1024 = just a little more than 1s.
* so we will double this timeout 10 times in the first
* second, and then switch over to 1s checkins.
*/
int next_sleep_us = 977;
int lastalert = secs+1;
int secsleft;
while (elapsed_us < target_us)
{
if (verbose)
{
secsleft = (target_us - elapsed_us + 999999)/1000000;
/* log_clock ("left=%d last=%d targ=%d elap=%d next=%d\n", */
/* secsleft, lastalert, target_us, elapsed_us, */
/* next_sleep_us); */
if (secsleft < lastalert)
{
log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR?
_("waiting for the dirmngr to come up ... (%ds)\n"):
module_name_id == GNUPG_MODULE_NAME_KEYBOXD?
_("waiting for the keyboxd to come up ... (%ds)\n"):
_("waiting for the agent to come up ... (%ds)\n"),
secsleft);
lastalert = secsleft;
}
}
gnupg_usleep (next_sleep_us);
elapsed_us += next_sleep_us;
err = assuan_socket_connect (ctx, sockname, 0, connect_flags);
if (!err)
{
if (verbose)
{
log_info (module_name_id == GNUPG_MODULE_NAME_DIRMNGR?
_("connection to the dirmngr established\n"):
module_name_id == GNUPG_MODULE_NAME_KEYBOXD?
_("connection to the keyboxd established\n"):
_("connection to the agent established\n"));
*did_success_msg = 1;
}
break;
}
next_sleep_us *= 2;
if (next_sleep_us > 1000000)
next_sleep_us = 1000000;
}
return err;
}
/* Try to connect to a new service via socket or start it if it is not
* running and AUTOSTART is set. Handle the server's initial
* greeting. Returns a new assuan context at R_CTX or an error code.
* MODULE_NAME_ID is one of:
* GNUPG_MODULE_NAME_AGENT
* GNUPG_MODULE_NAME_DIRMNGR
*/
static gpg_error_t
start_new_service (assuan_context_t *r_ctx,
int module_name_id,
gpg_err_source_t errsource,
const char *program_name,
const char *opt_lc_ctype,
const char *opt_lc_messages,
session_env_t session_env,
unsigned int flags,
int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
gpg_error_t err;
assuan_context_t ctx;
int did_success_msg = 0;
char *sockname;
const char *printed_name;
const char *lock_name;
const char *status_start_line;
int no_service_err;
int seconds_to_wait;
unsigned int connect_flags = 0;
const char *argv[6];
*r_ctx = NULL;
err = assuan_new (&ctx);
if (err)
{
log_error ("error allocating assuan context: %s\n", gpg_strerror (err));
return err;
}
switch (module_name_id)
{
case GNUPG_MODULE_NAME_AGENT:
sockname = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
lock_name = "agent";
printed_name = "gpg-agent";
status_start_line = "starting_agent ? 0 0";
no_service_err = GPG_ERR_NO_AGENT;
seconds_to_wait = SECS_TO_WAIT_FOR_AGENT;
break;
case GNUPG_MODULE_NAME_DIRMNGR:
sockname = make_filename (gnupg_socketdir (), DIRMNGR_SOCK_NAME, NULL);
lock_name = "dirmngr";
printed_name = "dirmngr";
status_start_line = "starting_dirmngr ? 0 0";
no_service_err = GPG_ERR_NO_DIRMNGR;
seconds_to_wait = SECS_TO_WAIT_FOR_DIRMNGR;
break;
case GNUPG_MODULE_NAME_KEYBOXD:
sockname = make_filename (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
lock_name = "keyboxd";
printed_name = "keyboxd";
status_start_line = "starting_keyboxd ? 0 0";
no_service_err = GPG_ERR_NO_KEYBOXD;
seconds_to_wait = SECS_TO_WAIT_FOR_KEYBOXD;
connect_flags |= ASSUAN_SOCKET_CONNECT_FDPASSING;
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
assuan_release (ctx);
return err;
}
err = assuan_socket_connect (ctx, sockname, 0, connect_flags);
if (err && (flags & ASSHELP_FLAG_AUTOSTART))
{
char *abs_homedir;
lock_spawn_t lock;
char *program = NULL;
const char *program_arg = NULL;
char *p;
const char *s;
int i;
/* With no success start a new server. */
if (!program_name || !*program_name)
program_name = gnupg_module_name (module_name_id);
else if ((s=strchr (program_name, '|')) && s[1] == '-' && s[2]=='-')
{
/* Hack to insert an additional option on the command line. */
program = xtrystrdup (program_name);
if (!program)
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
xfree (sockname);
assuan_release (ctx);
return tmperr;
}
p = strchr (program, '|');
*p++ = 0;
program_arg = p;
}
if (verbose)
log_info (_("no running %s - starting '%s'\n"),
printed_name, program_name);
if (status_cb)
status_cb (status_cb_arg, STATUS_PROGRESS, status_start_line, NULL);
/* We better pass an absolute home directory to the service just
* in case the service does not convert the passed name to an
* absolute one (which it should do). */
abs_homedir = make_absfilename_try (gnupg_homedir (), NULL);
if (!abs_homedir)
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
log_error ("error building filename: %s\n", gpg_strerror (tmperr));
xfree (sockname);
assuan_release (ctx);
xfree (program);
return tmperr;
}
if (fflush (NULL))
{
gpg_error_t tmperr = gpg_err_make (errsource,
gpg_err_code_from_syserror ());
log_error ("error flushing pending output: %s\n", strerror (errno));
xfree (sockname);
assuan_release (ctx);
xfree (abs_homedir);
xfree (program);
return tmperr;
}
i = 0;
argv[i++] = "--homedir";
argv[i++] = abs_homedir;
if (module_name_id == GNUPG_MODULE_NAME_AGENT)
argv[i++] = "--use-standard-socket";
if (program_arg)
argv[i++] = program_arg;
argv[i++] = "--daemon";
argv[i++] = NULL;
if (!(err = lock_spawning (&lock, gnupg_homedir (), lock_name, verbose))
&& assuan_socket_connect (ctx, sockname, 0, connect_flags))
{
#ifdef HAVE_W32_SYSTEM
err = gnupg_process_spawn (program? program : program_name, argv,
GNUPG_PROCESS_DETACHED, NULL, NULL);
#else /*!W32*/
err = gnupg_process_spawn (program? program : program_name, argv,
0, NULL, NULL);
#endif /*!W32*/
if (err)
log_error ("failed to start %s '%s': %s\n",
printed_name, program? program : program_name,
gpg_strerror (err));
else
err = wait_for_sock (seconds_to_wait, module_name_id,
sockname, connect_flags,
verbose, ctx, &did_success_msg);
}
unlock_spawning (&lock, lock_name);
xfree (abs_homedir);
xfree (program);
}
xfree (sockname);
if (err)
{
if ((flags & ASSHELP_FLAG_AUTOSTART)
|| gpg_err_code (err) != GPG_ERR_ASS_CONNECT_FAILED)
log_error ("can't connect to the %s: %s\n",
printed_name, gpg_strerror (err));
assuan_release (ctx);
return gpg_err_make (errsource, no_service_err);
}
if (debug && !did_success_msg)
log_debug ("connection to the %s established\n", printed_name);
if (module_name_id == GNUPG_MODULE_NAME_AGENT)
err = assuan_transact (ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (!err
&& module_name_id == GNUPG_MODULE_NAME_AGENT)
{
err = send_pinentry_environment (ctx, errsource,
opt_lc_ctype, opt_lc_messages,
session_env);
if (gpg_err_code (err) == GPG_ERR_FORBIDDEN
&& gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT)
{
/* Check whether the agent is in restricted mode. */
if (!assuan_transact (ctx, "GETINFO restricted",
NULL, NULL, NULL, NULL, NULL, NULL))
{
if (verbose)
log_info (_("connection to the agent is in restricted mode\n"));
err = 0;
}
}
}
if (err)
{
assuan_release (ctx);
return err;
}
*r_ctx = ctx;
return 0;
}
/* Try to connect to the agent or start a new one. */
gpg_error_t
start_new_gpg_agent (assuan_context_t *r_ctx,
gpg_err_source_t errsource,
const char *agent_program,
const char *opt_lc_ctype,
const char *opt_lc_messages,
session_env_t session_env,
unsigned int flags,
int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
return start_new_service (r_ctx, GNUPG_MODULE_NAME_AGENT,
errsource, agent_program,
opt_lc_ctype, opt_lc_messages, session_env,
flags, verbose, debug,
status_cb, status_cb_arg);
}
/* Try to connect to the dirmngr via a socket. On platforms
supporting it, start it up if needed and if ASSHELP_FLAG_AUTOSTART is set.
Returns a new assuan context at R_CTX or an error code. */
gpg_error_t
start_new_keyboxd (assuan_context_t *r_ctx,
gpg_err_source_t errsource,
const char *keyboxd_program,
unsigned int flags,
int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
return start_new_service (r_ctx, GNUPG_MODULE_NAME_KEYBOXD,
errsource, keyboxd_program,
NULL, NULL, NULL,
flags, verbose, debug,
status_cb, status_cb_arg);
}
/* Try to connect to the dirmngr via a socket. On platforms
supporting it, start it up if needed and if ASSHELP_FLAG_AUTOSTART is set.
Returns a new assuan context at R_CTX or an error code. */
gpg_error_t
start_new_dirmngr (assuan_context_t *r_ctx,
gpg_err_source_t errsource,
const char *dirmngr_program,
unsigned int flags,
int verbose, int debug,
gpg_error_t (*status_cb)(ctrl_t, int, ...),
ctrl_t status_cb_arg)
{
#ifndef USE_DIRMNGR_AUTO_START
flags &= ~ASSHELP_FLAG_AUTOSTART; /* Clear flag. */
#endif
return start_new_service (r_ctx, GNUPG_MODULE_NAME_DIRMNGR,
errsource, dirmngr_program,
NULL, NULL, NULL,
flags, verbose, debug,
status_cb, status_cb_arg);
}
/* Return the version of a server using "GETINFO version". On success
0 is returned and R_VERSION receives a malloced string with the
version which must be freed by the caller. On error NULL is stored
at R_VERSION and an error code returned. Mode is in general 0 but
certain values may be used to modify the used version command:
MODE == 0 = Use "GETINFO version"
MODE == 2 - Use "SCD GETINFO version"
*/
gpg_error_t
get_assuan_server_version (assuan_context_t ctx, int mode, char **r_version)
{
gpg_error_t err;
membuf_t data;
init_membuf (&data, 64);
err = assuan_transact (ctx,
mode == 2? "SCD GETINFO version"
/**/ : "GETINFO version",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, NULL));
*r_version = NULL;
}
else
{
put_membuf (&data, "", 1);
*r_version = get_membuf (&data, NULL);
if (!*r_version)
err = gpg_error_from_syserror ();
}
return err;
}
/* Print a warning if the server's version number is less than our
* version number. Returns an error code on a connection problem.
- * CTX is the Assuan context, SERVERNAME is the name of teh server,
+ * CTX is the Assuan context, SERVERNAME is the name of the server,
* STATUS_FUNC and STATUS_FUNC_DATA is a callback to emit status
* messages. If PRINT_HINTS is set additional hints are printed. For
* MODE see get_assuan_server_version. */
gpg_error_t
warn_server_version_mismatch (assuan_context_t ctx,
const char *servername, int mode,
gpg_error_t (*status_func)(ctrl_t ctrl,
int status_no,
...),
void *status_func_ctrl,
int print_hints)
{
gpg_error_t err;
char *serverversion;
const char *myversion = gpgrt_strusage (13);
err = get_assuan_server_version (ctx, mode, &serverversion);
if (err)
log_log (gpg_err_code (err) == GPG_ERR_NOT_SUPPORTED?
GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("error getting version from '%s': %s\n"),
servername, gpg_strerror (err));
else if (compare_version_strings (serverversion, myversion) < 0)
{
char *warn;
warn = xtryasprintf (_("server '%s' is older than us (%s < %s)"),
servername, serverversion, myversion);
if (!warn)
err = gpg_error_from_syserror ();
else
{
log_info (_("WARNING: %s\n"), warn);
if (print_hints)
{
log_info (_("Note: Outdated servers may lack important"
" security fixes.\n"));
log_info (_("Note: Use the command \"%s\" to restart them.\n"),
"gpgconf --kill all");
}
if (status_func)
status_func (status_func_ctrl, STATUS_WARNING,
"server_version_mismatch 0", warn, NULL);
xfree (warn);
}
}
xfree (serverversion);
return err;
}
diff --git a/common/audit.c b/common/audit.c
index 42a2cf6d6..551563c61 100644
--- a/common/audit.c
+++ b/common/audit.c
@@ -1,1325 +1,1325 @@
/* audit.c - GnuPG's audit subsystem
* Copyright (C) 2007, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include "util.h"
#include "i18n.h"
#include "audit.h"
#include "audit-events.h"
/* A list to maintain a list of helptags. */
struct helptag_s
{
struct helptag_s *next;
const char *name;
};
typedef struct helptag_s *helptag_t;
/* One log entry. */
struct log_item_s
{
audit_event_t event; /* The event. */
gpg_error_t err; /* The logged error code. */
int intvalue; /* A logged integer value. */
char *string; /* A malloced string or NULL. */
- ksba_cert_t cert; /* A certifciate or NULL. */
+ ksba_cert_t cert; /* A certificate or NULL. */
unsigned int have_err:1;
unsigned int have_intvalue:1;
};
typedef struct log_item_s *log_item_t;
/* The main audit object. */
struct audit_ctx_s
{
const char *failure; /* If set a description of the internal failure. */
audit_type_t type;
log_item_t log; /* The table with the log entries. */
size_t logsize; /* The allocated size for LOG. */
size_t logused; /* The used size of LOG. */
estream_t outstream; /* The current output stream. */
int use_html; /* The output shall be HTML formatted. */
int indentlevel; /* Current level of indentation. */
helptag_t helptags; /* List of help keys. */
};
static void writeout_para (audit_ctx_t ctx,
const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
static void writeout_li (audit_ctx_t ctx, const char *oktext,
const char *format, ...) GPGRT_ATTR_PRINTF(3,4);
static void writeout_rem (audit_ctx_t ctx,
const char *format, ...) GPGRT_ATTR_PRINTF(2,3);
/* Add NAME to the list of help tags. NAME needs to be a const string
an this function merely stores this pointer. */
static void
add_helptag (audit_ctx_t ctx, const char *name)
{
helptag_t item;
for (item=ctx->helptags; item; item = item->next)
if (!strcmp (item->name, name))
return; /* Already in the list. */
item = xtrycalloc (1, sizeof *item);
if (!item)
return; /* Don't care about memory problems. */
item->name = name;
item->next = ctx->helptags;
ctx->helptags = item;
}
/* Remove all help tags from the context. */
static void
clear_helptags (audit_ctx_t ctx)
{
while (ctx->helptags)
{
helptag_t tmp = ctx->helptags->next;
xfree (ctx->helptags);
ctx->helptags = tmp;
}
}
static const char *
event2str (audit_event_t event)
{
/* We need the cast so that compiler does not complain about an
always true comparison (>= 0) for an unsigned value. */
int idx = eventstr_msgidxof ((int)event);
if (idx == -1)
return "Unknown event";
else
return eventstr_msgstr + eventstr_msgidx[idx];
}
/* Create a new audit context. In case of an error NULL is returned
and errno set appropriately. */
audit_ctx_t
audit_new (void)
{
audit_ctx_t ctx;
ctx = xtrycalloc (1, sizeof *ctx);
return ctx;
}
/* Release an audit context. Passing NULL for CTX is allowed and does
nothing. */
void
audit_release (audit_ctx_t ctx)
{
int idx;
if (!ctx)
return;
if (ctx->log)
{
for (idx=0; idx < ctx->logused; idx++)
{
if (ctx->log[idx].string)
xfree (ctx->log[idx].string);
if (ctx->log[idx].cert)
ksba_cert_release (ctx->log[idx].cert);
}
xfree (ctx->log);
}
clear_helptags (ctx);
xfree (ctx);
}
/* Set the type for the audit operation. If CTX is NULL, this is a
dummy function. */
void
audit_set_type (audit_ctx_t ctx, audit_type_t type)
{
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (ctx->type && ctx->type != type)
{
ctx->failure = "conflict in type initialization";
return;
}
ctx->type = type;
}
/* Create a new log item and put it into the table. Return that log
item on success; return NULL on memory failure and mark that in
CTX. */
static log_item_t
create_log_item (audit_ctx_t ctx)
{
log_item_t item, table;
size_t size;
if (!ctx->log)
{
size = 10;
table = xtrymalloc (size * sizeof *table);
if (!table)
{
ctx->failure = "Out of memory in create_log_item";
return NULL;
}
ctx->log = table;
ctx->logsize = size;
item = ctx->log + 0;
ctx->logused = 1;
}
else if (ctx->logused >= ctx->logsize)
{
size = ctx->logsize + 10;
table = xtryrealloc (ctx->log, size * sizeof *table);
if (!table)
{
ctx->failure = "Out of memory while reallocating in create_log_item";
return NULL;
}
ctx->log = table;
ctx->logsize = size;
item = ctx->log + ctx->logused++;
}
else
item = ctx->log + ctx->logused++;
item->event = AUDIT_NULL_EVENT;
item->err = 0;
item->have_err = 0;
item->intvalue = 0;
item->have_intvalue = 0;
item->string = NULL;
item->cert = NULL;
return item;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. */
void
audit_log (audit_ctx_t ctx, audit_event_t event)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also adds the result of the operation
to the log. */
void
audit_log_ok (audit_ctx_t ctx, audit_event_t event, gpg_error_t err)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_ok";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
item->err = err;
item->have_err = 1;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also add the integer VALUE to the log. */
void
audit_log_i (audit_ctx_t ctx, audit_event_t event, int value)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_i";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
item->intvalue = value;
item->have_intvalue = 1;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also add the integer VALUE to the log. */
void
audit_log_s (audit_ctx_t ctx, audit_event_t event, const char *value)
{
log_item_t item;
char *tmp;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_s";
return;
}
tmp = xtrystrdup (value? value : "");
if (!tmp)
{
ctx->failure = "Out of memory in audit_event";
return;
}
if (!(item = create_log_item (ctx)))
{
xfree (tmp);
return;
}
item->event = event;
item->string = tmp;
}
/* Add a new event to the audit log. If CTX is NULL, this function
does nothing. This version also adds the certificate CERT and the
result of an operation to the log. */
void
audit_log_cert (audit_ctx_t ctx, audit_event_t event,
ksba_cert_t cert, gpg_error_t err)
{
log_item_t item;
if (!ctx || ctx->failure)
return; /* Audit not enabled or an internal error has occurred. */
if (!event)
{
ctx->failure = "Invalid event passed to audit_log_cert";
return;
}
if (!(item = create_log_item (ctx)))
return;
item->event = event;
item->err = err;
item->have_err = 1;
if (cert)
{
ksba_cert_ref (cert);
item->cert = cert;
}
}
/* Write TEXT to the outstream. */
static void
writeout (audit_ctx_t ctx, const char *text)
{
if (ctx->use_html)
{
for (; *text; text++)
{
if (*text == '<')
es_fputs ("&lt;", ctx->outstream);
else if (*text == '&')
es_fputs ("&amp;", ctx->outstream);
else
es_putc (*text, ctx->outstream);
}
}
else
es_fputs (text, ctx->outstream);
}
/* Write TEXT to the outstream using a variable argument list. */
static void
writeout_v (audit_ctx_t ctx, const char *format, va_list arg_ptr)
{
char *buf;
gpgrt_vasprintf (&buf, format, arg_ptr);
if (buf)
{
writeout (ctx, buf);
xfree (buf);
}
else
writeout (ctx, "[!!Out of core!!]");
}
/* Write TEXT as a paragraph. */
static void
writeout_para (audit_ctx_t ctx, const char *format, ...)
{
va_list arg_ptr;
if (ctx->use_html)
es_fputs ("<p>", ctx->outstream);
va_start (arg_ptr, format) ;
writeout_v (ctx, format, arg_ptr);
va_end (arg_ptr);
if (ctx->use_html)
es_fputs ("</p>\n", ctx->outstream);
else
es_fputc ('\n', ctx->outstream);
}
static void
enter_li (audit_ctx_t ctx)
{
if (ctx->use_html)
{
if (!ctx->indentlevel)
{
es_fputs ("<table border=\"0\">\n"
" <colgroup>\n"
" <col width=\"80%\" />\n"
" <col width=\"20%\" />\n"
" </colgroup>\n",
ctx->outstream);
}
}
ctx->indentlevel++;
}
static void
leave_li (audit_ctx_t ctx)
{
ctx->indentlevel--;
if (ctx->use_html)
{
if (!ctx->indentlevel)
es_fputs ("</table>\n", ctx->outstream);
}
}
/* Write TEXT as a list element. If OKTEXT is not NULL, append it to
the last line. */
static void
writeout_li (audit_ctx_t ctx, const char *oktext, const char *format, ...)
{
va_list arg_ptr;
const char *color = NULL;
if (ctx->use_html && format && oktext)
{
if (!strcmp (oktext, "Yes")
|| !strcmp (oktext, "good") )
color = "green";
else if (!strcmp (oktext, "No")
|| !strcmp (oktext, "bad") )
color = "red";
}
if (format && oktext)
{
const char *s = NULL;
if (!strcmp (oktext, "Yes"))
oktext = _("Yes");
else if (!strcmp (oktext, "No"))
oktext = _("No");
else if (!strcmp (oktext, "good"))
{
/* TRANSLATORS: Copy the prefix between the vertical bars
verbatim. It will not be printed. */
oktext = _("|audit-log-result|Good");
}
else if (!strcmp (oktext, "bad"))
oktext = _("|audit-log-result|Bad");
else if (!strcmp (oktext, "unsupported"))
oktext = _("|audit-log-result|Not supported");
else if (!strcmp (oktext, "no-cert"))
oktext = _("|audit-log-result|No certificate");
else if (!strcmp (oktext, "disabled"))
oktext = _("|audit-log-result|Not enabled");
else if (!strcmp (oktext, "error"))
oktext = _("|audit-log-result|Error");
else if (!strcmp (oktext, "not-used"))
oktext = _("|audit-log-result|Not used");
else if (!strcmp (oktext, "okay"))
oktext = _("|audit-log-result|Okay");
else if (!strcmp (oktext, "skipped"))
oktext = _("|audit-log-result|Skipped");
else if (!strcmp (oktext, "some"))
oktext = _("|audit-log-result|Some");
else
s = "";
/* If we have set a prefix, skip it. */
if (!s && *oktext == '|' && (s=strchr (oktext+1,'|')))
oktext = s+1;
}
if (ctx->use_html)
{
int i;
es_fputs (" <tr><td><table><tr><td>", ctx->outstream);
if (color)
es_fprintf (ctx->outstream, "<font color=\"%s\">*</font>", color);
else
es_fputs ("*", ctx->outstream);
for (i=1; i < ctx->indentlevel; i++)
es_fputs ("&nbsp;&nbsp;", ctx->outstream);
es_fputs ("</td><td>", ctx->outstream);
}
else
es_fprintf (ctx->outstream, "* %*s", (ctx->indentlevel-1)*2, "");
if (format)
{
va_start (arg_ptr, format) ;
writeout_v (ctx, format, arg_ptr);
va_end (arg_ptr);
}
if (ctx->use_html)
es_fputs ("</td></tr></table>", ctx->outstream);
if (format && oktext)
{
if (ctx->use_html)
{
es_fputs ("</td><td>", ctx->outstream);
if (color)
es_fprintf (ctx->outstream, "<font color=\"%s\">", color);
}
else
writeout (ctx, ": ");
writeout (ctx, oktext);
if (color)
es_fputs ("</font>", ctx->outstream);
}
if (ctx->use_html)
es_fputs ("</td></tr>\n", ctx->outstream);
else
es_fputc ('\n', ctx->outstream);
}
/* Write a remark line. */
static void
writeout_rem (audit_ctx_t ctx, const char *format, ...)
{
va_list arg_ptr;
if (ctx->use_html)
{
int i;
es_fputs (" <tr><td><table><tr><td>*", ctx->outstream);
for (i=1; i < ctx->indentlevel; i++)
es_fputs ("&nbsp;&nbsp;", ctx->outstream);
es_fputs ("&nbsp;&nbsp;&nbsp;</td><td> (", ctx->outstream);
}
else
es_fprintf (ctx->outstream, "* %*s (", (ctx->indentlevel-1)*2, "");
if (format)
{
va_start (arg_ptr, format) ;
writeout_v (ctx, format, arg_ptr);
va_end (arg_ptr);
}
if (ctx->use_html)
es_fputs (")</td></tr></table></td></tr>\n", ctx->outstream);
else
es_fputs (")\n", ctx->outstream);
}
/* Return the first log item for EVENT. If STOPEVENT is not 0 never
look behind that event in the log. If STARTITEM is not NULL start
search _after_that item. */
static log_item_t
find_next_log_item (audit_ctx_t ctx, log_item_t startitem,
audit_event_t event, audit_event_t stopevent)
{
int idx;
for (idx=0; idx < ctx->logused; idx++)
{
if (startitem)
{
if (ctx->log + idx == startitem)
startitem = NULL;
}
else if (stopevent && ctx->log[idx].event == stopevent)
break;
else if (ctx->log[idx].event == event)
return ctx->log + idx;
}
return NULL;
}
static log_item_t
find_log_item (audit_ctx_t ctx, audit_event_t event, audit_event_t stopevent)
{
return find_next_log_item (ctx, NULL, event, stopevent);
}
/* Helper to a format a serial number. */
static char *
format_serial (ksba_const_sexp_t sn)
{
const char *p = (const char *)sn;
unsigned long n;
char *endp;
if (!p)
return NULL;
if (*p != '(')
BUG (); /* Not a valid S-expression. */
n = strtoul (p+1, &endp, 10);
p = endp;
if (*p != ':')
BUG (); /* Not a valid S-expression. */
return bin2hex (p+1, n, NULL);
}
/* Return a malloced string with the serial number and the issuer DN
of the certificate. */
static char *
get_cert_name (ksba_cert_t cert)
{
char *result;
ksba_sexp_t sn;
char *issuer, *p;
if (!cert)
return xtrystrdup ("[no certificate]");
issuer = ksba_cert_get_issuer (cert, 0);
sn = ksba_cert_get_serial (cert);
if (issuer && sn)
{
p = format_serial (sn);
if (!p)
result = xtrystrdup ("[invalid S/N]");
else
{
result = xtrymalloc (strlen (p) + strlen (issuer) + 2 + 1);
if (result)
{
*result = '#';
strcpy (stpcpy (stpcpy (result+1, p),"/"), issuer);
}
xfree (p);
}
}
else
result = xtrystrdup ("[missing S/N or issuer]");
ksba_free (sn);
xfree (issuer);
return result;
}
/* Return a malloced string with the serial number and the issuer DN
of the certificate. */
static char *
get_cert_subject (ksba_cert_t cert, int idx)
{
char *result;
char *subject;
if (!cert)
return xtrystrdup ("[no certificate]");
subject = ksba_cert_get_subject (cert, idx);
if (subject)
{
result = xtrymalloc (strlen (subject) + 1 + 1);
if (result)
{
*result = '/';
strcpy (result+1, subject);
}
}
else
result = NULL;
xfree (subject);
return result;
}
/* List the given certificiate. If CERT is NULL, this is a NOP. */
static void
list_cert (audit_ctx_t ctx, ksba_cert_t cert, int with_subj)
{
char *name;
int idx;
name = get_cert_name (cert);
writeout_rem (ctx, "%s", name);
xfree (name);
if (with_subj)
{
enter_li (ctx);
for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
/* List the chain of certificates from STARTITEM up to STOPEVENT. The
certificates are written out as comments. */
static void
list_certchain (audit_ctx_t ctx, log_item_t startitem, audit_event_t stopevent)
{
log_item_t item;
startitem = find_next_log_item (ctx, startitem, AUDIT_CHAIN_BEGIN,stopevent);
writeout_li (ctx, startitem? "Yes":"No", _("Certificate chain available"));
if (!startitem)
return;
item = find_next_log_item (ctx, startitem,
AUDIT_CHAIN_ROOTCERT, AUDIT_CHAIN_END);
if (!item)
writeout_rem (ctx, "%s", _("root certificate missing"));
else
{
list_cert (ctx, item->cert, 0);
}
item = startitem;
while ( ((item = find_next_log_item (ctx, item,
AUDIT_CHAIN_CERT, AUDIT_CHAIN_END))))
{
list_cert (ctx, item->cert, 1);
}
}
/* Process an encrypt operation's log. */
static void
proc_type_encrypt (audit_ctx_t ctx)
{
log_item_t loopitem, item;
int recp_no, idx;
char numbuf[35];
int algo;
char *name;
item = find_log_item (ctx, AUDIT_ENCRYPTION_DONE, 0);
writeout_li (ctx, item?"Yes":"No", "%s", _("Data encryption succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
item = find_log_item (ctx, AUDIT_SESSION_KEY, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Session key created"));
if (item)
{
algo = gcry_cipher_map_name (item->string);
if (algo)
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
else if (item->string)
writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
else
writeout_rem (ctx, _("seems to be not encrypted"));
}
item = find_log_item (ctx, AUDIT_GOT_RECIPIENTS, 0);
snprintf (numbuf, sizeof numbuf, "%d",
item && item->have_intvalue? item->intvalue : 0);
writeout_li (ctx, numbuf, "%s", _("Number of recipients"));
/* Loop over all recipients. */
loopitem = NULL;
recp_no = 0;
while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_ENCRYPTED_TO, 0)))
{
recp_no++;
writeout_li (ctx, NULL, _("Recipient %d"), recp_no);
if (loopitem->cert)
{
name = get_cert_name (loopitem->cert);
writeout_rem (ctx, "%s", name);
xfree (name);
enter_li (ctx);
for (idx=0; (name = get_cert_subject (loopitem->cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
leave_li (ctx);
}
/* Process a sign operation's log. */
static void
proc_type_sign (audit_ctx_t ctx)
{
log_item_t item, loopitem;
int signer, idx;
const char *result;
ksba_cert_t cert;
char *name;
int lastalgo;
item = find_log_item (ctx, AUDIT_SIGNING_DONE, 0);
writeout_li (ctx, item?"Yes":"No", "%s", _("Data signing succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
/* Write remarks with the data hash algorithms. We use a very
simple scheme to avoid some duplicates. */
loopitem = NULL;
lastalgo = 0;
while ((loopitem = find_next_log_item
(ctx, loopitem, AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG)))
{
if (loopitem->intvalue && loopitem->intvalue != lastalgo)
writeout_rem (ctx, _("data hash algorithm: %s"),
gcry_md_algo_name (loopitem->intvalue));
lastalgo = loopitem->intvalue;
}
/* Loop over all signer. */
loopitem = NULL;
signer = 0;
while ((loopitem=find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)))
{
signer++;
item = find_next_log_item (ctx, loopitem, AUDIT_SIGNED_BY, AUDIT_NEW_SIG);
if (!item)
result = "error";
else if (!item->err)
result = "okay";
else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
result = "skipped";
else
result = gpg_strerror (item->err);
cert = item? item->cert : NULL;
writeout_li (ctx, result, _("Signer %d"), signer);
item = find_next_log_item (ctx, loopitem,
AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, _("attr hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
if (cert)
{
name = get_cert_name (cert);
writeout_rem (ctx, "%s", name);
xfree (name);
enter_li (ctx);
for (idx=0; (name = get_cert_subject (cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
leave_li (ctx);
}
/* Process a decrypt operation's log. */
static void
proc_type_decrypt (audit_ctx_t ctx)
{
log_item_t loopitem, item;
int algo, recpno;
char *name;
char numbuf[35];
int idx;
item = find_log_item (ctx, AUDIT_DECRYPTION_RESULT, 0);
writeout_li (ctx, item && !item->err?"Yes":"No",
"%s", _("Data decryption succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
item = find_log_item (ctx, AUDIT_DATA_CIPHER_ALGO, 0);
algo = item? item->intvalue : 0;
writeout_li (ctx, algo?"Yes":"No", "%s", _("Encryption algorithm supported"));
if (algo)
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
item = find_log_item (ctx, AUDIT_BAD_DATA_CIPHER_ALGO, 0);
if (item && item->string)
{
algo = gcry_cipher_map_name (item->string);
if (algo)
writeout_rem (ctx, _("algorithm: %s"), gnupg_cipher_algo_name (algo));
else if (item->string && !strcmp (item->string, "1.2.840.113549.3.2"))
writeout_rem (ctx, _("unsupported algorithm: %s"), "RC2");
else if (item->string)
writeout_rem (ctx, _("unsupported algorithm: %s"), item->string);
else
writeout_rem (ctx, _("seems to be not encrypted"));
}
for (recpno = 0, item = NULL;
(item = find_next_log_item (ctx, item, AUDIT_NEW_RECP, 0)); recpno++)
;
snprintf (numbuf, sizeof numbuf, "%d", recpno);
writeout_li (ctx, numbuf, "%s", _("Number of recipients"));
/* Loop over all recipients. */
loopitem = NULL;
while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_RECP, 0)))
{
const char *result;
recpno = loopitem->have_intvalue? loopitem->intvalue : -1;
item = find_next_log_item (ctx, loopitem,
AUDIT_RECP_RESULT, AUDIT_NEW_RECP);
if (!item)
result = "not-used";
else if (!item->err)
result = "okay";
else if (gpg_err_code (item->err) == GPG_ERR_CANCELED)
result = "skipped";
else
result = gpg_strerror (item->err);
item = find_next_log_item (ctx, loopitem,
AUDIT_RECP_NAME, AUDIT_NEW_RECP);
writeout_li (ctx, result, _("Recipient %d"), recpno);
if (item && item->string)
writeout_rem (ctx, "%s", item->string);
/* If we have a certificate write out more infos. */
item = find_next_log_item (ctx, loopitem,
AUDIT_SAVE_CERT, AUDIT_NEW_RECP);
if (item && item->cert)
{
enter_li (ctx);
for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
}
leave_li (ctx);
}
/* Process a verification operation's log. */
static void
proc_type_verify (audit_ctx_t ctx)
{
log_item_t loopitem, item;
int signo, count, idx, n_good, n_bad;
char numbuf[35];
const char *result;
/* If there is at least one signature status we claim that the
verification succeeded. This does not mean that the data has
verified okay. */
item = find_log_item (ctx, AUDIT_SIG_STATUS, 0);
writeout_li (ctx, item?"Yes":"No", "%s", _("Data verification succeeded"));
enter_li (ctx);
item = find_log_item (ctx, AUDIT_GOT_DATA, AUDIT_NEW_SIG);
writeout_li (ctx, item? "Yes":"No", "%s", _("Data available"));
if (!item)
goto leave;
item = find_log_item (ctx, AUDIT_NEW_SIG, 0);
writeout_li (ctx, item? "Yes":"No", "%s", _("Signature available"));
if (!item)
goto leave;
/* Print info about the used data hashing algorithms. */
for (idx=0, n_good=n_bad=0; idx < ctx->logused; idx++)
{
item = ctx->log + idx;
if (item->event == AUDIT_NEW_SIG)
break;
else if (item->event == AUDIT_DATA_HASH_ALGO)
n_good++;
else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
n_bad++;
}
item = find_log_item (ctx, AUDIT_DATA_HASHING, AUDIT_NEW_SIG);
if (!item || item->err || !n_good)
result = "No";
else if (n_good && !n_bad)
result = "Yes";
else
result = "Some";
writeout_li (ctx, result, "%s", _("Parsing data succeeded"));
if (n_good || n_bad)
{
for (idx=0; idx < ctx->logused; idx++)
{
item = ctx->log + idx;
if (item->event == AUDIT_NEW_SIG)
break;
else if (item->event == AUDIT_DATA_HASH_ALGO)
writeout_rem (ctx, _("data hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
else if (item->event == AUDIT_BAD_DATA_HASH_ALGO)
writeout_rem (ctx, _("bad data hash algorithm: %s"),
item->string? item->string:"?");
}
}
/* Loop over all signatures. */
loopitem = find_log_item (ctx, AUDIT_NEW_SIG, 0);
assert (loopitem);
do
{
signo = loopitem->have_intvalue? loopitem->intvalue : -1;
item = find_next_log_item (ctx, loopitem,
AUDIT_SIG_STATUS, AUDIT_NEW_SIG);
writeout_li (ctx, item? item->string:"?", _("Signature %d"), signo);
item = find_next_log_item (ctx, loopitem,
AUDIT_SIG_NAME, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, "%s", item->string);
item = find_next_log_item (ctx, loopitem,
AUDIT_DATA_HASH_ALGO, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, _("data hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
item = find_next_log_item (ctx, loopitem,
AUDIT_ATTR_HASH_ALGO, AUDIT_NEW_SIG);
if (item)
writeout_rem (ctx, _("attr hash algorithm: %s"),
gcry_md_algo_name (item->intvalue));
enter_li (ctx);
/* List the certificate chain. */
list_certchain (ctx, loopitem, AUDIT_NEW_SIG);
/* Show the result of the chain validation. */
item = find_next_log_item (ctx, loopitem,
AUDIT_CHAIN_STATUS, AUDIT_NEW_SIG);
if (item && item->have_err)
{
writeout_li (ctx, item->err? "No":"Yes",
_("Certificate chain valid"));
if (item->err)
writeout_rem (ctx, "%s", gpg_strerror (item->err));
}
/* Show whether the root certificate is fine. */
item = find_next_log_item (ctx, loopitem,
AUDIT_ROOT_TRUSTED, AUDIT_CHAIN_STATUS);
if (item)
{
writeout_li (ctx, item->err?"No":"Yes", "%s",
_("Root certificate trustworthy"));
if (item->err)
{
add_helptag (ctx, "gpgsm.root-cert-not-trusted");
writeout_rem (ctx, "%s", gpg_strerror (item->err));
list_cert (ctx, item->cert, 0);
}
}
/* Show result of the CRL/OCSP check. */
item = find_next_log_item (ctx, loopitem,
AUDIT_CRL_CHECK, AUDIT_NEW_SIG);
if (item)
{
const char *ok;
switch (gpg_err_code (item->err))
{
case 0: ok = "good"; break;
case GPG_ERR_TRUE: ok = "n/a"; break;
case GPG_ERR_CERT_REVOKED: ok = "bad"; break;
case GPG_ERR_NOT_ENABLED: ok = "disabled"; break;
case GPG_ERR_NO_CRL_KNOWN:
case GPG_ERR_INV_CRL_OBJ:
ok = _("no CRL found for certificate");
break;
case GPG_ERR_CRL_TOO_OLD:
ok = _("the available CRL is too old");
break;
default: ok = gpg_strerror (item->err); break;
}
writeout_li (ctx, ok, "%s", _("CRL/OCSP check of certificates"));
if (item->err
&& gpg_err_code (item->err) != GPG_ERR_CERT_REVOKED
&& gpg_err_code (item->err) != GPG_ERR_NOT_ENABLED)
add_helptag (ctx, "gpgsm.crl-problem");
}
leave_li (ctx);
}
while ((loopitem = find_next_log_item (ctx, loopitem, AUDIT_NEW_SIG, 0)));
leave:
/* Always list the certificates stored in the signature. */
item = NULL;
count = 0;
while ( ((item = find_next_log_item (ctx, item,
AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
count++;
snprintf (numbuf, sizeof numbuf, "%d", count);
writeout_li (ctx, numbuf, _("Included certificates"));
item = NULL;
while ( ((item = find_next_log_item (ctx, item,
AUDIT_SAVE_CERT, AUDIT_NEW_SIG))))
{
char *name = get_cert_name (item->cert);
writeout_rem (ctx, "%s", name);
xfree (name);
enter_li (ctx);
for (idx=0; (name = get_cert_subject (item->cert, idx)); idx++)
{
writeout_rem (ctx, "%s", name);
xfree (name);
}
leave_li (ctx);
}
leave_li (ctx);
}
/* Print the formatted audit result. THIS IS WORK IN PROGRESS. */
void
audit_print_result (audit_ctx_t ctx, estream_t out, int use_html)
{
int idx;
size_t n;
log_item_t item;
helptag_t helptag;
const char *s;
int show_raw = 0;
char *orig_codeset;
if (!ctx)
return;
orig_codeset = i18n_switchto_utf8 ();
/* We use an environment variable to include some debug info in the
log. */
if ((s = getenv ("gnupg_debug_audit")))
show_raw = 1;
assert (!ctx->outstream);
ctx->outstream = out;
ctx->use_html = use_html;
ctx->indentlevel = 0;
clear_helptags (ctx);
if (use_html)
es_fputs ("<div class=\"" GNUPG_NAME "AuditLog\">\n", ctx->outstream);
if (!ctx->log || !ctx->logused)
{
writeout_para (ctx, _("No audit log entries."));
goto leave;
}
if (show_raw)
{
int maxlen;
for (idx=0,maxlen=0; idx < DIM (eventstr_msgidx); idx++)
{
n = strlen (eventstr_msgstr + eventstr_msgidx[idx]);
if (n > maxlen)
maxlen = n;
}
if (use_html)
es_fputs ("<pre>\n", out);
for (idx=0; idx < ctx->logused; idx++)
{
es_fprintf (out, "log: %-*s",
maxlen, event2str (ctx->log[idx].event));
if (ctx->log[idx].have_intvalue)
es_fprintf (out, " i=%d", ctx->log[idx].intvalue);
if (ctx->log[idx].string)
{
es_fputs (" s='", out);
writeout (ctx, ctx->log[idx].string);
es_fputs ("'", out);
}
if (ctx->log[idx].cert)
es_fprintf (out, " has_cert");
if (ctx->log[idx].have_err)
{
es_fputs (" err='", out);
writeout (ctx, gpg_strerror (ctx->log[idx].err));
es_fputs ("'", out);
}
es_fputs ("\n", out);
}
if (use_html)
es_fputs ("</pre>\n", out);
else
es_fputs ("\n", out);
}
enter_li (ctx);
switch (ctx->type)
{
case AUDIT_TYPE_NONE:
writeout_li (ctx, NULL, _("Unknown operation"));
break;
case AUDIT_TYPE_ENCRYPT:
proc_type_encrypt (ctx);
break;
case AUDIT_TYPE_SIGN:
proc_type_sign (ctx);
break;
case AUDIT_TYPE_DECRYPT:
proc_type_decrypt (ctx);
break;
case AUDIT_TYPE_VERIFY:
proc_type_verify (ctx);
break;
}
item = find_log_item (ctx, AUDIT_AGENT_READY, 0);
if (item && item->have_err)
{
writeout_li (ctx, item->err? "No":"Yes", "%s", _("Gpg-Agent usable"));
if (item->err)
{
writeout_rem (ctx, "%s", gpg_strerror (item->err));
add_helptag (ctx, "gnupg.agent-problem");
}
}
item = find_log_item (ctx, AUDIT_DIRMNGR_READY, 0);
if (item && item->have_err)
{
writeout_li (ctx, item->err? "No":"Yes", "%s", _("Dirmngr usable"));
if (item->err)
{
writeout_rem (ctx, "%s", gpg_strerror (item->err));
add_helptag (ctx, "gnupg.dirmngr-problem");
}
}
leave_li (ctx);
/* Show the help from the collected help tags. */
if (ctx->helptags)
{
if (use_html)
{
es_fputs ("<hr/>\n", ctx->outstream);
if (ctx->helptags->next)
es_fputs ("<ul>\n", ctx->outstream);
}
else
es_fputs ("\n\n", ctx->outstream);
}
for (helptag = ctx->helptags; helptag; helptag = helptag->next)
{
char *text;
if (use_html && ctx->helptags->next)
es_fputs ("<li>\n", ctx->outstream);
text = gnupg_get_help_string (helptag->name, 0);
if (text)
{
writeout_para (ctx, "%s", text);
xfree (text);
}
else
writeout_para (ctx, _("No help available for '%s'."), helptag->name);
if (use_html && ctx->helptags->next)
es_fputs ("</li>\n", ctx->outstream);
if (helptag->next)
es_fputs ("\n", ctx->outstream);
}
if (use_html && ctx->helptags && ctx->helptags->next)
es_fputs ("</ul>\n", ctx->outstream);
leave:
if (use_html)
es_fputs ("</div>\n", ctx->outstream);
ctx->outstream = NULL;
ctx->use_html = 0;
clear_helptags (ctx);
i18n_switchback (orig_codeset);
}
diff --git a/common/audit.h b/common/audit.h
index 05f39533d..9fadba1b2 100644
--- a/common/audit.h
+++ b/common/audit.h
@@ -1,225 +1,225 @@
/* audit.h - Definitions for the audit subsystem
* Copyright (C) 2007 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_COMMON_AUDIT_H
#define GNUPG_COMMON_AUDIT_H
#include <ksba.h>
struct audit_ctx_s;
typedef struct audit_ctx_s *audit_ctx_t;
/* Constants for the audit type. */
typedef enum
{
AUDIT_TYPE_NONE = 0, /* No type set. */
AUDIT_TYPE_ENCRYPT, /* Data encryption. */
AUDIT_TYPE_SIGN, /* Signature creation. */
AUDIT_TYPE_DECRYPT, /* Data decryption. */
AUDIT_TYPE_VERIFY /* Signature verification. */
}
audit_type_t;
/* The events we support. */
typedef enum
{
AUDIT_NULL_EVENT = 0,
/* No such event. Its value shall be 0 and no other values shall
be assigned to the other enum symbols. This is required so
that the exaudit.awk script comes up with correct values
without running cc. */
AUDIT_SETUP_READY,
/* All preparations done so that the actual processing can start
now. This indicates that all parameters are okay and we can
start to process the actual data. */
AUDIT_AGENT_READY, /* err */
/* Indicates whether the gpg-agent is available. For some
operations the agent is not required and thus no such event
will be logged. */
AUDIT_DIRMNGR_READY, /* err */
/* Indicates whether the Dirmngr is available. For some
operations the Dirmngr is not required and thus no such event
will be logged. */
AUDIT_GPG_READY, /* err */
/* Indicates whether the Gpg engine is available. */
AUDIT_GPGSM_READY, /* err */
/* Indicates whether the Gpgsm engine is available. */
AUDIT_G13_READY, /* err */
/* Indicates whether the G13 engine is available. */
AUDIT_GOT_DATA,
/* Data to be processed has been seen. */
AUDIT_DETACHED_SIGNATURE,
/* The signature is a detached one. */
AUDIT_CERT_ONLY_SIG,
- /* A certifciate only signature has been detected. */
+ /* A certificate only signature has been detected. */
AUDIT_DATA_HASH_ALGO, /* int */
/* The hash algo given as argument is used for the data. This
event will be repeated for all hash algorithms used with the
data. */
AUDIT_ATTR_HASH_ALGO, /* int */
/* The hash algo given as argument is used to hash the message
digest and other signed attributes of this signature. */
AUDIT_DATA_CIPHER_ALGO, /* int */
/* The cipher algo given as argument is used for this data. */
AUDIT_BAD_DATA_HASH_ALGO, /* string */
/* The hash algo as specified by the signature can't be used.
STRING is the description of this algorithm which usually is an
OID string. STRING may be NULL. */
AUDIT_BAD_DATA_CIPHER_ALGO, /* string */
/* The symmetric cipher algorithm is not supported. STRING is the
description of this algorithm which usually is an OID string.
STRING may be NULL. */
AUDIT_DATA_HASHING, /* ok_err */
/* Logs the result of the data hashing. */
AUDIT_READ_ERROR, /* ok_err */
/* A generic read error occurred. */
AUDIT_WRITE_ERROR, /* ok_err */
/* A generic write error occurred. */
AUDIT_USAGE_ERROR,
/* The program was used in an inappropriate way; For example by
passing a data object while the signature does not expect one
or vice versa. */
AUDIT_SAVE_CERT, /* cert, ok_err */
/* Save the certificate received in a message. */
AUDIT_NEW_SIG, /* int */
/* Start the verification of a new signature for the last data
object. The argument is the signature number as used
internally by the program. */
AUDIT_SIG_NAME, /* string */
/* The name of a signer. This is the name or other identification
data as known from the signature and not the name from the
certificate used for verification. An example for STRING when
using CMS is: "#1234/CN=Prostetnic Vogon Jeltz". */
AUDIT_SIG_STATUS, /* string */
/* The signature status of the current signer. This is the last
audit information for one signature. STRING gives the status:
"error" - there was a problem checking this or any signature.
"unsupported" - the signature type is not supported.
"no-cert" - The certificate of the signer was not found (the
S/N+issuer of the signer is already in the log).
"bad" - bad signature
"good" - good signature
*/
AUDIT_NEW_RECP, /* int */
/* A new recipient has been seen during decryption. The argument
is the recipient number as used internally by the program. */
AUDIT_RECP_NAME, /* string */
/* The name of a recipient. This is the name or other identification
data as known from the decryption and not the name from the
certificate used for decryption. An example for STRING when
using CMS is: "#1234/CN=Prostetnic Vogon Jeltz". */
AUDIT_RECP_RESULT, /* ok_err */
/* The status of the session key decryption. This is only written
for recipients tried. */
AUDIT_DECRYPTION_RESULT, /* ok_err */
/* The status of the entire decryption. The decryption was
successful if the error code is 0. */
AUDIT_VALIDATE_CHAIN,
/* Start the validation of a certificate chain. */
AUDIT_CHAIN_BEGIN,
AUDIT_CHAIN_CERT, /* cert */
AUDIT_CHAIN_ROOTCERT,/* cert */
AUDIT_CHAIN_END,
/* These 4 events are used to log the certificates making up a
certificate chain. ROOTCERT is used for the trustanchor and
CERT for all other certificates. */
AUDIT_CHAIN_STATUS, /* err */
/* Tells the final status of the chain validation. */
AUDIT_ROOT_TRUSTED, /* cert, err */
/* Tells whether the root certificate is trusted. This event is
emitted during chain validation. */
AUDIT_CRL_CHECK, /* err */
/* Tells the status of a CRL or OCSP check. */
AUDIT_GOT_RECIPIENTS, /* int */
/* Records the number of recipients to be used for encryption.
This includes the recipients set by --encrypt-to but records 0
if no real recipient has been given. */
AUDIT_SESSION_KEY, /* string */
/* Mark the creation or availability of the session key. The
parameter is the algorithm ID. */
AUDIT_ENCRYPTED_TO, /* cert, err */
/* Records the certificate used for encryption and whether the
session key could be encrypted to it (err==0). */
AUDIT_ENCRYPTION_DONE,
/* Encryption succeeded. */
AUDIT_SIGNED_BY, /* cert, err */
/* Records the certificate used for signed and whether the signure
could be created (if err==0). */
AUDIT_SIGNING_DONE,
/* Signing succeeded. */
AUDIT_LAST_EVENT /* Marker for parsing this list. */
}
audit_event_t;
audit_ctx_t audit_new (void);
void audit_release (audit_ctx_t ctx);
void audit_set_type (audit_ctx_t ctx, audit_type_t type);
void audit_log (audit_ctx_t ctx, audit_event_t event);
void audit_log_ok (audit_ctx_t ctx, audit_event_t event, gpg_error_t err);
void audit_log_i (audit_ctx_t ctx, audit_event_t event, int value);
void audit_log_s (audit_ctx_t ctx, audit_event_t event, const char *value);
void audit_log_cert (audit_ctx_t ctx, audit_event_t event,
ksba_cert_t cert, gpg_error_t err);
void audit_print_result (audit_ctx_t ctx, estream_t stream, int use_html);
#endif /*GNUPG_COMMON_AUDIT_H*/
diff --git a/common/compliance.c b/common/compliance.c
index 2df10d2e2..d421b0371 100644
--- a/common/compliance.c
+++ b/common/compliance.c
@@ -1,731 +1,731 @@
/* compliance.c - Functions for compliance modi
* Copyright (C) 2017 g10 Code GmbH
* Copyright (C) 2017 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <gcrypt.h>
#include "openpgpdefs.h"
#include "logging.h"
#include "util.h"
#include "i18n.h"
#include "compliance.h"
static int initialized;
static int module;
/* This value is used by DSA and RSA checks in addition to the hard
* coded length checks. It allows one to increase the required key length
- * using a confue file. */
+ * using a config file. */
static unsigned int min_compliant_rsa_length;
/* Return the address of a compliance cache variable for COMPLIANCE.
* If no such variable exists NULL is returned. FOR_RNG returns the
* cache variable for the RNG compliance check. */
static int *
get_compliance_cache (enum gnupg_compliance_mode compliance, int for_rng)
{
static int r_gnupg = -1, s_gnupg = -1;
static int r_rfc4880 = -1, s_rfc4880 = -1;
static int r_rfc2440 = -1, s_rfc2440 = -1;
static int r_pgp7 = -1, s_pgp7 = -1;
static int r_pgp8 = -1, s_pgp8 = -1;
static int r_de_vs = -1, s_de_vs = -1;
int *ptr = NULL;
switch (compliance)
{
case CO_GNUPG: ptr = for_rng? &r_gnupg : &s_gnupg ; break;
case CO_RFC4880: ptr = for_rng? &r_rfc4880 : &s_rfc4880; break;
case CO_RFC2440: ptr = for_rng? &r_rfc2440 : &s_rfc2440; break;
case CO_PGP7: ptr = for_rng? &r_pgp7 : &s_pgp7 ; break;
case CO_PGP8: ptr = for_rng? &r_pgp8 : &s_pgp8 ; break;
case CO_DE_VS: ptr = for_rng? &r_de_vs : &s_de_vs ; break;
}
return ptr;
}
/* Initializes the module. Must be called with the current
* GNUPG_MODULE_NAME. Checks a few invariants, and tunes the policies
* for the given module. */
void
gnupg_initialize_compliance (int gnupg_module_name)
{
log_assert (! initialized);
/* We accept both OpenPGP-style and gcrypt-style algorithm ids.
* Assert that they are compatible. At some places gcrypt ids are
* used which can't be encoded in an OpenPGP algo octet; we also
* assert this. */
log_assert ((int) GCRY_PK_RSA == (int) PUBKEY_ALGO_RSA);
log_assert ((int) GCRY_PK_RSA_E == (int) PUBKEY_ALGO_RSA_E);
log_assert ((int) GCRY_PK_RSA_S == (int) PUBKEY_ALGO_RSA_S);
log_assert ((int) GCRY_PK_ELG_E == (int) PUBKEY_ALGO_ELGAMAL_E);
log_assert ((int) GCRY_PK_DSA == (int) PUBKEY_ALGO_DSA);
log_assert ((int) GCRY_PK_ECC == (int) PUBKEY_ALGO_ECDH);
log_assert ((int) GCRY_PK_ELG == (int) PUBKEY_ALGO_ELGAMAL);
log_assert ((int) GCRY_PK_ECDSA > 255);
log_assert ((int) GCRY_PK_ECDH > 255);
log_assert ((int) GCRY_PK_EDDSA > 255);
log_assert ((int) GCRY_CIPHER_NONE == (int) CIPHER_ALGO_NONE);
log_assert ((int) GCRY_CIPHER_IDEA == (int) CIPHER_ALGO_IDEA);
log_assert ((int) GCRY_CIPHER_3DES == (int) CIPHER_ALGO_3DES);
log_assert ((int) GCRY_CIPHER_CAST5 == (int) CIPHER_ALGO_CAST5);
log_assert ((int) GCRY_CIPHER_BLOWFISH == (int) CIPHER_ALGO_BLOWFISH);
log_assert ((int) GCRY_CIPHER_AES == (int) CIPHER_ALGO_AES);
log_assert ((int) GCRY_CIPHER_AES192 == (int) CIPHER_ALGO_AES192);
log_assert ((int) GCRY_CIPHER_AES256 == (int) CIPHER_ALGO_AES256);
log_assert ((int) GCRY_CIPHER_TWOFISH == (int) CIPHER_ALGO_TWOFISH);
log_assert ((int) GCRY_MD_MD5 == (int) DIGEST_ALGO_MD5);
log_assert ((int) GCRY_MD_SHA1 == (int) DIGEST_ALGO_SHA1);
log_assert ((int) GCRY_MD_RMD160 == (int) DIGEST_ALGO_RMD160);
log_assert ((int) GCRY_MD_SHA256 == (int) DIGEST_ALGO_SHA256);
log_assert ((int) GCRY_MD_SHA384 == (int) DIGEST_ALGO_SHA384);
log_assert ((int) GCRY_MD_SHA512 == (int) DIGEST_ALGO_SHA512);
log_assert ((int) GCRY_MD_SHA224 == (int) DIGEST_ALGO_SHA224);
switch (gnupg_module_name)
{
case GNUPG_MODULE_NAME_GPGSM:
case GNUPG_MODULE_NAME_GPG:
break;
default:
log_assert (!"no policies for this module");
}
module = gnupg_module_name;
initialized = 1;
}
/* Return true if ALGO with a key of KEYLENGTH is compliant to the
* given COMPLIANCE mode. If KEY is not NULL, various bits of
* information will be extracted from it. If CURVENAME is not NULL, it
* is assumed to be the already computed. ALGO may be either an
* OpenPGP-style pubkey_algo_t, or a gcrypt-style enum gcry_pk_algos,
* both are compatible from the point of view of this function. */
int
gnupg_pk_is_compliant (enum gnupg_compliance_mode compliance, int algo,
unsigned int algo_flags,
gcry_mpi_t key[], unsigned int keylength,
const char *curvename)
{
enum { is_rsa, is_dsa, is_elg, is_ecc, is_kem } algotype;
int result = 0;
if (! initialized)
return 0;
switch (algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S:
algotype = is_rsa;
break;
case PUBKEY_ALGO_DSA:
algotype = is_dsa;
break;
case PUBKEY_ALGO_ELGAMAL_E:
algotype = is_elg;
break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_EDDSA:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH:
case GCRY_PK_EDDSA:
algotype = is_ecc;
break;
case PUBKEY_ALGO_ELGAMAL:
return 0; /* Signing with Elgamal is not at all supported. */
case PUBKEY_ALGO_KYBER:
algotype = is_kem;
break;
default: /* Unknown. */
return 0;
}
if (compliance == CO_DE_VS)
{
char *curve = NULL;
switch (algotype)
{
case is_elg:
result = 0;
break;
case is_rsa:
result = ((keylength == 2048
|| keylength == 3072
|| keylength == 4096)
&& keylength >= min_compliant_rsa_length);
/* Although rsaPSS was not part of the original evaluation
* we got word that we can claim compliance. */
(void)algo_flags;
break;
case is_dsa:
if (key)
{
size_t P = gcry_mpi_get_nbits (key[0]);
size_t Q = gcry_mpi_get_nbits (key[1]);
result = (Q == 256
&& (P == 2048 || P == 3072)
&& P >= min_compliant_rsa_length);
}
break;
case is_ecc:
if (!curvename && key)
{
curve = openpgp_oid_to_str (key[0]);
curvename = openpgp_oid_to_curve (curve, 0);
if (!curvename)
curvename = curve;
}
result = (curvename
&& (algo == PUBKEY_ALGO_ECDH
|| algo == PUBKEY_ALGO_ECDSA
|| algo == GCRY_PK_ECDH
|| algo == GCRY_PK_ECDSA)
&& (!strcmp (curvename, "brainpoolP256r1")
|| !strcmp (curvename, "brainpoolP384r1")
|| !strcmp (curvename, "brainpoolP512r1")));
break;
case is_kem:
result = 0;
break;
default:
result = 0;
}
xfree (curve);
}
else
{
result = 1; /* Assume compliance. */
}
return result;
}
/* Return true if ALGO with the given KEYLENGTH is allowed in the
* given COMPLIANCE mode. USE specifies for which use case the
* predicate is evaluated. This way policies can be strict in what
* they produce, and liberal in what they accept. */
int
gnupg_pk_is_allowed (enum gnupg_compliance_mode compliance,
enum pk_use_case use, int algo,
unsigned int algo_flags, gcry_mpi_t key[],
unsigned int keylength, const char *curvename)
{
int result = 0;
if (! initialized)
return 1;
/* Map the the generic ECC algo to ECDSA if requested. */
if ((algo_flags & PK_ALGO_FLAG_ECC18)
&& algo == GCRY_PK_ECC
&& (use == PK_USE_VERIFICATION
|| use == PK_USE_SIGNING))
algo = GCRY_PK_ECDSA;
switch (compliance)
{
case CO_DE_VS:
switch (algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S:
switch (use)
{
case PK_USE_DECRYPTION:
case PK_USE_VERIFICATION:
result = 1;
break;
case PK_USE_ENCRYPTION:
case PK_USE_SIGNING:
result = ((keylength == 2048
|| keylength == 3072
|| keylength == 4096)
&& keylength >= min_compliant_rsa_length);
break;
default:
log_assert (!"reached");
}
break;
case PUBKEY_ALGO_DSA:
if (use == PK_USE_VERIFICATION)
result = 1;
else if (use == PK_USE_SIGNING && key)
{
size_t P = gcry_mpi_get_nbits (key[0]);
size_t Q = gcry_mpi_get_nbits (key[1]);
result = (Q == 256
&& (P == 2048 || P == 3072)
&& keylength >= min_compliant_rsa_length);
}
break;
case PUBKEY_ALGO_ELGAMAL:
case PUBKEY_ALGO_ELGAMAL_E:
result = (use == PK_USE_DECRYPTION);
break;
case PUBKEY_ALGO_ECDH: /* Same value as GCRY_PK_ECC, i.e. 18 */
case GCRY_PK_ECDH:
if (use == PK_USE_DECRYPTION)
result = 1;
else if (use == PK_USE_ENCRYPTION)
{
char *curve = NULL;
if (!curvename && key)
{
curve = openpgp_oid_to_str (key[0]);
curvename = openpgp_oid_to_curve (curve, 0);
if (!curvename)
curvename = curve;
}
result = (curvename
&& (!strcmp (curvename, "brainpoolP256r1")
|| !strcmp (curvename, "brainpoolP384r1")
|| !strcmp (curvename, "brainpoolP512r1")));
xfree (curve);
}
break;
case PUBKEY_ALGO_ECDSA:
case GCRY_PK_ECDSA:
if (use == PK_USE_VERIFICATION)
result = 1;
else
{
char *curve = NULL;
if (! curvename && key)
{
curve = openpgp_oid_to_str (key[0]);
curvename = openpgp_oid_to_curve (curve, 0);
if (!curvename)
curvename = curve;
}
result = (use == PK_USE_SIGNING
&& curvename
&& (!strcmp (curvename, "brainpoolP256r1")
|| !strcmp (curvename, "brainpoolP384r1")
|| !strcmp (curvename, "brainpoolP512r1")));
xfree (curve);
}
break;
case PUBKEY_ALGO_EDDSA:
if (use == PK_USE_VERIFICATION)
result = 1;
else /* We may not create such signatures in de-vs mode. */
result = 0;
break;
default:
break;
}
break;
default:
/* The default policy is to allow all algorithms. */
result = 1;
}
return result;
}
/* Return true if (CIPHER, MODE) is compliant to the given COMPLIANCE mode. */
int
gnupg_cipher_is_compliant (enum gnupg_compliance_mode compliance,
cipher_algo_t cipher,
enum gcry_cipher_modes mode)
{
if (! initialized)
return 0;
switch (compliance)
{
case CO_DE_VS:
switch (cipher)
{
case CIPHER_ALGO_AES:
case CIPHER_ALGO_AES192:
case CIPHER_ALGO_AES256:
case CIPHER_ALGO_3DES:
switch (module)
{
case GNUPG_MODULE_NAME_GPG:
return mode == GCRY_CIPHER_MODE_CFB;
case GNUPG_MODULE_NAME_GPGSM:
return mode == GCRY_CIPHER_MODE_CBC;
}
log_assert (!"reached");
default:
return 0;
}
log_assert (!"reached");
default:
return 0;
}
log_assert (!"reached");
}
/* Return true if CIPHER is allowed in the given COMPLIANCE mode. If
* PRODUCER is true, the predicate is evaluated for the producer, if
* false for the consumer. This way policies can be strict in what
* they produce, and liberal in what they accept. */
int
gnupg_cipher_is_allowed (enum gnupg_compliance_mode compliance, int producer,
cipher_algo_t cipher,
enum gcry_cipher_modes mode)
{
if (! initialized)
return 1;
switch (compliance)
{
case CO_DE_VS:
switch (cipher)
{
case CIPHER_ALGO_AES:
case CIPHER_ALGO_AES192:
case CIPHER_ALGO_AES256:
case CIPHER_ALGO_3DES:
switch (module)
{
case GNUPG_MODULE_NAME_GPG:
return (mode == GCRY_CIPHER_MODE_NONE
|| mode == GCRY_CIPHER_MODE_CFB);
case GNUPG_MODULE_NAME_GPGSM:
return (mode == GCRY_CIPHER_MODE_NONE
|| mode == GCRY_CIPHER_MODE_CBC
|| (mode == GCRY_CIPHER_MODE_GCM && !producer));
}
log_assert (!"reached");
case CIPHER_ALGO_BLOWFISH:
case CIPHER_ALGO_CAMELLIA128:
case CIPHER_ALGO_CAMELLIA192:
case CIPHER_ALGO_CAMELLIA256:
case CIPHER_ALGO_CAST5:
case CIPHER_ALGO_IDEA:
case CIPHER_ALGO_TWOFISH:
return (module == GNUPG_MODULE_NAME_GPG
&& (mode == GCRY_CIPHER_MODE_NONE
|| mode == GCRY_CIPHER_MODE_CFB)
&& ! producer);
default:
return 0;
}
log_assert (!"reached");
default:
/* The default policy is to allow all algorithms. */
return 1;
}
log_assert (!"reached");
}
/* Return true if DIGEST is compliant to the given COMPLIANCE mode. */
int
gnupg_digest_is_compliant (enum gnupg_compliance_mode compliance,
digest_algo_t digest)
{
if (! initialized)
return 0;
switch (compliance)
{
case CO_DE_VS:
switch (digest)
{
case DIGEST_ALGO_SHA256:
case DIGEST_ALGO_SHA384:
case DIGEST_ALGO_SHA512:
return 1;
default:
return 0;
}
log_assert (!"reached");
default:
return 0;
}
log_assert (!"reached");
}
/* Return true if DIGEST is allowed in the given COMPLIANCE mode. If
* PRODUCER is true, the predicate is evaluated for the producer, if
* false for the consumer. This way policies can be strict in what
* they produce, and liberal in what they accept. */
int
gnupg_digest_is_allowed (enum gnupg_compliance_mode compliance, int producer,
digest_algo_t digest)
{
if (! initialized)
return 1;
switch (compliance)
{
case CO_DE_VS:
switch (digest)
{
case DIGEST_ALGO_SHA256:
case DIGEST_ALGO_SHA384:
case DIGEST_ALGO_SHA512:
return 1;
case DIGEST_ALGO_SHA1:
case DIGEST_ALGO_SHA224:
case DIGEST_ALGO_RMD160:
return ! producer;
case DIGEST_ALGO_MD5:
return ! producer && module == GNUPG_MODULE_NAME_GPGSM;
default:
return 0;
}
log_assert (!"reached");
default:
/* The default policy is to allow all algorithms. */
return 1;
}
log_assert (!"reached");
}
/* Return True if the random number generator is compliant in
* COMPLIANCE mode. */
int
gnupg_rng_is_compliant (enum gnupg_compliance_mode compliance)
{
int *result;
int res;
/* #warning debug code ahead */
/* return 1; */
result = get_compliance_cache (compliance, 1);
if (result && *result != -1)
res = *result; /* Use cached result. */
else if (compliance == CO_DE_VS)
{
/* We also check whether the library is at all compliant. */
res = gnupg_gcrypt_is_compliant (compliance);
/* In DE_VS mode under Windows we also require that the JENT RNG
* is active. Check it here. */
#ifdef HAVE_W32_SYSTEM
if (res == 1)
{
char *buf;
const char *fields[5];
buf = gcry_get_config (0, "rng-type");
if (buf
&& split_fields_colon (buf, fields, DIM (fields)) >= 5
&& atoi (fields[4]) > 0)
; /* Field 5 > 0 := Jent is active. */
else
result = 0; /* Force non-compliance. */
gcry_free (buf);
}
#endif /*HAVE_W32_SYSTEM*/
}
else
res = 1;
if (result)
*result = res;
return res;
}
/* Return true if the used Libgcrypt is compliant in COMPLIANCE
* mode. */
int
gnupg_gcrypt_is_compliant (enum gnupg_compliance_mode compliance)
{
int *result;
int res;
result = get_compliance_cache (compliance, 0);
if (result && *result != -1)
res = *result; /* Use cached result. */
else if (compliance == CO_DE_VS)
{
int is19orlater = !!gcry_check_version ("1.9.0");
/* A compliant version of GnuPG requires Libgcrypt >= 1.8.1 and
* less than 1.9.0. Version 1.9.0 requires a re-evaluation and
* can thus not be used for de-vs. */
if (gcry_check_version ("1.8.1") && !is19orlater)
res = 1; /* Compliant version of Libgcrypt. */
else if (is19orlater)
{
/* Libgcrypt might be nice enough to tell us whether it is
* compliant. */
char *buf;
const char *fields[3];
buf = gcry_get_config (0, "compliance");
if (buf
&& split_fields_colon (buf, fields, DIM (fields)) >= 2
&& strstr (fields[1], "de-vs"))
res = 1; /* Compliant. */
else
res = 0; /* Non-compliant. */
gcry_free (buf);
}
else
res = 0; /* Non-compliant version of Libgcrypt. */
}
else
res = 1;
if (result)
*result = res;
return res;
}
const char *
gnupg_status_compliance_flag (enum gnupg_compliance_mode compliance)
{
switch (compliance)
{
case CO_GNUPG:
return "8";
case CO_RFC4880:
case CO_RFC2440:
case CO_PGP7:
case CO_PGP8:
log_assert (!"no status code assigned for this compliance mode");
case CO_DE_VS:
return "23";
}
log_assert (!"invalid compliance mode");
}
/* Parse the value of --compliance. Returns the value corresponding
* to the given STRING according to OPTIONS of size LENGTH, or -1
* indicating that the lookup was unsuccessful, or the list of options
* was printed. If quiet is false, an additional hint to use 'help'
* is printed on unsuccessful lookups. */
int
gnupg_parse_compliance_option (const char *string,
struct gnupg_compliance_option options[],
size_t length,
int quiet)
{
size_t i;
if (! ascii_strcasecmp (string, "help"))
{
log_info (_("valid values for option '%s':\n"), "--compliance");
for (i = 0; i < length; i++)
log_info (" %s\n", options[i].keyword);
return -1;
}
for (i = 0; i < length; i++)
if (! ascii_strcasecmp (string, options[i].keyword))
return options[i].value;
log_error (_("invalid value for option '%s'\n"), "--compliance");
if (! quiet)
log_info (_("(use \"help\" to list choices)\n"));
return -1;
}
/* Return the command line option for the given COMPLIANCE mode. */
const char *
gnupg_compliance_option_string (enum gnupg_compliance_mode compliance)
{
switch (compliance)
{
case CO_GNUPG: return "--compliance=gnupg";
case CO_RFC4880: return "--compliance=openpgp";
case CO_RFC2440: return "--compliance=rfc2440";
case CO_PGP7: return "--compliance=pgp7";
case CO_PGP8: return "--compliance=pgp8";
case CO_DE_VS: return "--compliance=de-vs";
}
log_assert (!"invalid compliance mode");
}
/* Set additional infos for example taken from config files at startup. */
void
gnupg_set_compliance_extra_info (unsigned int min_rsa)
{
min_compliant_rsa_length = min_rsa;
}
diff --git a/common/dotlock.c b/common/dotlock.c
index fe6d7fe71..ae77fad8a 100644
--- a/common/dotlock.c
+++ b/common/dotlock.c
@@ -1,1718 +1,1718 @@
/* dotlock.c - dotfile locking
* Copyright (C) 1998, 2000, 2001, 2003, 2004,
* 2005, 2006, 2008, 2010, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute and/or modify this
* part of GnuPG under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* 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 copies of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, see <https://www.gnu.org/licenses/>.
*
* ALTERNATIVELY, this file may be distributed under the terms of the
* following license, in which case the provisions of this license are
* required INSTEAD OF the GNU Lesser General License or the GNU
* General Public License. If you wish to allow use of your version of
* this file only under the terms of the GNU Lesser General License or
* the GNU General Public License, and not to allow others to use your
* version of this file under the terms of the following license,
* indicate your decision by deleting this paragraph and the license
* below.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, and the entire permission notice in its entirety,
* including the disclaimer of warranties.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/*
Overview:
=========
This module implements advisory file locking in a portable way.
Due to the problems with POSIX fcntl locking a separate lock file
is used. It would be possible to use fcntl locking on this lock
file and thus avoid the weird auto unlock bug of POSIX while still
having an unproved better performance of fcntl locking. However
there are still problems left, thus we resort to use a hardlink
which has the well defined property that a link call will fail if
the target file already exists.
Given that hardlinks are also available on NTFS file systems since
Windows XP; it will be possible to enhance this module to use
hardlinks even on Windows and thus allow Windows and Posix clients
to use locking on the same directory. This is not yet implemented;
instead we use a lockfile on Windows along with W32 style file
locking.
On FAT file systems hardlinks are not supported. Thus this method
does not work. Our solution is to use a O_EXCL locking instead.
Querying the type of the file system is not easy to do in a
portable way (e.g. Linux has a statfs, BSDs have a the same call
but using different structures and constants). What we do instead
is to check at runtime whether link(2) works for a specific lock
file.
How to use:
===========
At program initialization time, the module should be explicitly
initialized:
dotlock_create (NULL, 0);
This installs an atexit handler and may also initialize mutex etc.
It is optional for non-threaded applications. Only the first call
has an effect. This needs to be done before any extra threads are
started.
To create a lock file (which prepares it but does not take the
lock) you do:
dotlock_t h
h = dotlock_create (fname, 0);
if (!h)
error ("error creating lock file: %s\n", strerror (errno));
It is important to handle the error. For example on a read-only
file system a lock can't be created (but is usually not needed).
FNAME is the file you want to lock; the actual lockfile is that
name with the suffix ".lock" appended. On success a handle to be
used with the other functions is returned or NULL on error. Note
that the handle shall only be used by one thread at a time. This
function creates a unique file temporary file (".#lk*") in the same
directory as FNAME and returns a handle for further operations.
The module keeps track of these unique files so that they will be
unlinked using the atexit handler. If you don't need the lock file
anymore, you may also explicitly remove it with a call to:
dotlock_destroy (h);
To actually lock the file, you use:
if (dotlock_take (h, -1))
error ("error taking lock: %s\n", strerror (errno));
This function will wait until the lock is acquired. If an
unexpected error occurs if will return non-zero and set ERRNO. If
you pass (0) instead of (-1) the function does not wait in case the
file is already locked but returns -1 and sets ERRNO to EACCES.
Any other positive value for the second parameter is considered a
timeout value in milliseconds.
To release the lock you call:
if (dotlock_release (h))
error ("error releasing lock: %s\n", strerror (errno));
or, if the lock file is not anymore needed, you may just call
dotlock_destroy. However dotlock_release does some extra checks
before releasing the lock and prints diagnostics to help detecting
bugs.
If you want to explicitly destroy all lock files you may call
dotlock_remove_lockfiles ();
which is the core of the installed atexit handler. In case your
application wants to disable locking completely it may call
disable_locking ()
before any locks are created.
There are two convenience functions to store an integer (e.g. a
file descriptor) value with the handle:
void dotlock_set_fd (dotlock_t h, int fd);
int dotlock_get_fd (dotlock_t h);
If nothing has been stored dotlock_get_fd returns -1.
How to build:
=============
This module was originally developed for GnuPG but later changed to
allow its use without any GnuPG dependency. If you want to use it
with you application you may simply use it and it should figure out
most things automagically.
You may use the common config.h file to pass macros, but take care
to pass -DHAVE_CONFIG_H to the compiler. Macros used by this
module are:
DOTLOCK_USE_PTHREAD - Define if POSIX threads are in use.
DOTLOCK_GLIB_LOGGING - Define this to use Glib logging functions.
DOTLOCK_EXT_SYM_PREFIX - Prefix all external symbols with the
string to which this macro evaluates.
GNUPG_MAJOR_VERSION - Defined when used by GnuPG.
HAVE_DOSISH_SYSTEM - Defined for Windows etc. Will be
automatically defined if a the target is
Windows.
HAVE_POSIX_SYSTEM - Internally defined to !HAVE_DOSISH_SYSTEM.
HAVE_SIGNAL_H - Should be defined on Posix systems. If config.h
is not used defaults to defined.
DIRSEP_C - Separation character for file name parts.
Usually not redefined.
EXTSEP_S - Separation string for file name suffixes.
Usually not redefined.
Note that there is a test program t-dotlock which has compile
instructions at its end. At least for SMBFS and CIFS it is
important that 64 bit versions of stat are used; most programming
environments do this these days, just in case you want to compile
it on the command line, remember to pass -D_FILE_OFFSET_BITS=64
Bugs:
=====
On Windows this module is not yet thread-safe.
Miscellaneous notes:
====================
On hardlinks:
- Hardlinks are supported under Windows with NTFS since XP/Server2003.
- In Linux 2.6.33 both SMBFS and CIFS seem to support hardlinks.
- NFS supports hard links. But there are solvable problems.
- FAT does not support links
On the file locking API:
- CIFS on Linux 2.6.33 supports several locking methods.
SMBFS seems not to support locking. No closer checks done.
- NFS supports Posix locks. flock is emulated in the server.
However there are a couple of problems; see below.
- FAT does not support locks.
- An advantage of fcntl locking is that R/W locks can be
implemented which is not easy with a straight lock file.
On O_EXCL:
- Does not work reliable on NFS
- Should work on CIFS and SMBFS but how can we delete lockfiles?
On NFS problems:
- Locks vanish if the server crashes and reboots.
- Client crashes keep the lock in the server until the client
re-connects.
- Communication problems may return unreliable error codes. The
MUA Postfix's workaround is to compare the link count after
seeing an error for link. However that gives a race. If using a
unique file to link to a lockfile and using stat to check the
link count instead of looking at the error return of link(2) is
the best solution.
- O_EXCL seems to have a race and may re-create a file anyway.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
/* Some quick replacements for stuff we usually expect to be defined
in config.h. Define HAVE_POSIX_SYSTEM for better readability. */
#if !defined (HAVE_DOSISH_SYSTEM) && defined(_WIN32)
# define HAVE_DOSISH_SYSTEM 1
#endif
#if !defined (HAVE_DOSISH_SYSTEM) && !defined (HAVE_POSIX_SYSTEM)
# define HAVE_POSIX_SYSTEM 1
#endif
/* With no config.h assume that we have sitgnal.h. */
#if !defined (HAVE_CONFIG_H) && defined (HAVE_POSIX_SYSTEM)
# define HAVE_SIGNAL_H 1
#endif
/* Standard headers. */
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_DOSISH_SYSTEM
# define WIN32_LEAN_AND_MEAN /* We only need the OS core stuff. */
# include <windows.h>
#else
# include <sys/types.h>
# include <sys/stat.h>
# include <sys/utsname.h>
# include <dirent.h>
#endif
#include <sys/types.h>
#include <sys/time.h>
#include <sys/stat.h>
#include <fcntl.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#ifdef DOTLOCK_USE_PTHREAD
# include <pthread.h>
#endif
#ifdef DOTLOCK_GLIB_LOGGING
# include <glib.h>
#endif
#ifdef GNUPG_MAJOR_VERSION
# include "util.h"
# include "common-defs.h"
# include "stringhelp.h" /* For stpcpy and w32_strerror. */
#endif
#include "dotlock.h"
/* Define constants for file name construction. */
#if !defined(DIRSEP_C) && !defined(EXTSEP_S)
# ifdef HAVE_DOSISH_SYSTEM
# define DIRSEP_C '\\'
# define EXTSEP_S "."
#else
# define DIRSEP_C '/'
# define EXTSEP_S "."
# endif
#endif
/* In GnuPG we use wrappers around the malloc functions. If they are
not defined we assume that this code is used outside of GnuPG and
fall back to the regular malloc functions. */
#ifndef xtrymalloc
# define xtrymalloc(a) malloc ((a))
# define xtrycalloc(a,b) calloc ((a), (b))
# define xfree(a) free ((a))
#endif
/* Wrapper to set ERRNO (required for W32CE). */
#ifdef GPG_ERROR_VERSION
# define my_set_errno(e) gpg_err_set_errno ((e))
#else
# define my_set_errno(e) do { errno = (e); } while (0)
#endif
/* Gettext macro replacement. */
#ifndef _
# define _(a) (a)
#endif
#ifdef GNUPG_MAJOR_VERSION
# define my_info_0(a) log_info ((a))
# define my_info_1(a,b) log_info ((a), (b))
# define my_info_2(a,b,c) log_info ((a), (b), (c))
# define my_info_3(a,b,c,d) log_info ((a), (b), (c), (d))
# define my_error_0(a) log_error ((a))
# define my_error_1(a,b) log_error ((a), (b))
# define my_error_2(a,b,c) log_error ((a), (b), (c))
# define my_debug_1(a,b) log_debug ((a), (b))
# define my_fatal_0(a) log_fatal ((a))
#elif defined (DOTLOCK_GLIB_LOGGING)
# define my_info_0(a) g_message ((a))
# define my_info_1(a,b) g_message ((a), (b))
# define my_info_2(a,b,c) g_message ((a), (b), (c))
# define my_info_3(a,b,c,d) g_message ((a), (b), (c), (d))
# define my_error_0(a) g_warning ((a))
# define my_error_1(a,b) g_warning ((a), (b))
# define my_error_2(a,b,c) g_warning ((a), (b), (c))
# define my_debug_1(a,b) g_debug ((a), (b))
# define my_fatal_0(a) g_error ((a))
#else
# define my_info_0(a) fprintf (stderr, (a))
# define my_info_1(a,b) fprintf (stderr, (a), (b))
# define my_info_2(a,b,c) fprintf (stderr, (a), (b), (c))
# define my_info_3(a,b,c,d) fprintf (stderr, (a), (b), (c), (d))
# define my_error_0(a) fprintf (stderr, (a))
# define my_error_1(a,b) fprintf (stderr, (a), (b))
# define my_error_2(a,b,c) fprintf (stderr, (a), (b), (c))
# define my_debug_1(a,b) fprintf (stderr, (a), (b))
# define my_fatal_0(a) do { fprintf (stderr,(a)); fflush (stderr); \
abort (); } while (0)
#endif
/* The object describing a lock. */
struct dotlock_handle
{
struct dotlock_handle *next;
char *lockname; /* Name of the actual lockfile. */
unsigned int locked:1; /* Lock status. */
unsigned int disable:1; /* If true, locking is disabled. */
unsigned int use_o_excl:1; /* Use open (O_EXCL) for locking. */
unsigned int by_parent:1; /* Parent does the locking. */
unsigned int no_write:1; /* No write to the lockfile. */
int extra_fd; /* A place for the caller to store an FD. */
/* An optional info callback - see dotlock_set_info_cb. */
int (*info_cb)(dotlock_t, void *,
enum dotlock_reasons reason,
const char *,...);
void *info_cb_value;
#ifdef HAVE_DOSISH_SYSTEM
HANDLE lockhd; /* The W32 handle of the lock file. */
#else /*!HAVE_DOSISH_SYSTEM */
char *tname; /* Name of the lockfile template. */
size_t nodename_off; /* Offset in TNAME of the nodename part. */
size_t nodename_len; /* Length of the nodename part. */
#endif /*!HAVE_DOSISH_SYSTEM */
};
/* A list of all lock handles. The volatile attribute might help
if used in an atexit handler. Note that [UN]LOCK_all_lockfiles
must not change ERRNO. */
static volatile dotlock_t all_lockfiles;
#ifdef DOTLOCK_USE_PTHREAD
static pthread_mutex_t all_lockfiles_mutex = PTHREAD_MUTEX_INITIALIZER;
# define LOCK_all_lockfiles() do { \
if (pthread_mutex_lock (&all_lockfiles_mutex)) \
my_fatal_0 ("locking all_lockfiles_mutex failed\n"); \
} while (0)
# define UNLOCK_all_lockfiles() do { \
if (pthread_mutex_unlock (&all_lockfiles_mutex)) \
my_fatal_0 ("unlocking all_lockfiles_mutex failed\n"); \
} while (0)
#else /*!DOTLOCK_USE_PTHREAD*/
# define LOCK_all_lockfiles() do { } while (0)
# define UNLOCK_all_lockfiles() do { } while (0)
#endif /*!DOTLOCK_USE_PTHREAD*/
/* If this has the value true all locking is disabled. */
static int never_lock;
#ifdef HAVE_DOSISH_SYSTEM
/* FIXME: For use in GnuPG this can be replaced by
* gnupg_w32_set_errno. */
static int
map_w32_to_errno (DWORD w32_err)
{
switch (w32_err)
{
case 0:
return 0;
case ERROR_FILE_NOT_FOUND:
return ENOENT;
case ERROR_PATH_NOT_FOUND:
return ENOENT;
case ERROR_ACCESS_DENIED:
return EPERM;
case ERROR_INVALID_HANDLE:
case ERROR_INVALID_BLOCK:
return EINVAL;
case ERROR_NOT_ENOUGH_MEMORY:
return ENOMEM;
case ERROR_NO_DATA:
case ERROR_BROKEN_PIPE:
return EPIPE;
default:
return EIO;
}
}
#endif /*HAVE_DOSISH_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
static int
any8bitchar (const char *string)
{
if (string)
for ( ; *string; string++)
if ((*string & 0x80))
return 1;
return 0;
}
#endif /*HAVE_W32_SYSTEM*/
/* Entirely disable all locking. This function should be called
before any locking is done. It may be called right at startup of
the process as it only sets a global value. */
void
dotlock_disable (void)
{
never_lock = 1;
}
#ifdef HAVE_POSIX_SYSTEM
static int
maybe_deadlock (dotlock_t h)
{
dotlock_t r;
int res = 0;
LOCK_all_lockfiles ();
for (r=all_lockfiles; r; r = r->next)
{
if ( r != h && r->locked )
{
res = 1;
break;
}
}
UNLOCK_all_lockfiles ();
return res;
}
#endif /*HAVE_POSIX_SYSTEM*/
/* Read the lock file and return the pid, returns -1 on error. True
will be stored in the integer at address SAME_NODE if the lock file
has been created on the same node. */
#ifdef HAVE_POSIX_SYSTEM
static int
read_lockfile (dotlock_t h, int *same_node, int *r_fd)
{
char buffer_space[10+1+70+1]; /* 70 is just an estimated value; node
names are usually shorter. */
int fd;
int pid = -1;
char *buffer, *p;
size_t expected_len;
int res, nread;
*same_node = 0;
expected_len = 10 + 1 + h->nodename_len + 1;
if ( expected_len >= sizeof buffer_space)
{
buffer = xtrymalloc (expected_len);
if (!buffer)
return -1;
}
else
buffer = buffer_space;
if ( (fd = open (h->lockname, O_RDONLY)) == -1 )
{
int e = errno;
if (errno != ENOENT)
{
my_info_2 ("error opening lockfile '%s': %s\n",
h->lockname, strerror(errno) );
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"error opening lockfile '%s': %s\n",
h->lockname, strerror (errno) );
}
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (e); /* Need to return ERRNO here. */
return -1;
}
p = buffer;
nread = 0;
do
{
res = read (fd, p, expected_len - nread);
if (res == -1 && errno == EINTR)
continue;
if (res < 0)
{
int e = errno;
my_info_1 ("error reading lockfile '%s'\n", h->lockname );
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"error reading lockfile '%s': %s\n",
h->lockname, strerror (errno) );
close (fd);
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (e);
return -1;
}
p += res;
nread += res;
}
while (res && nread != expected_len);
if (r_fd)
*r_fd = fd;
else
close(fd);
if (nread < 11)
{
my_info_1 ("invalid size of lockfile '%s'\n", h->lockname);
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_INV_FILE,
"invalid size of lockfile '%s'\n", h->lockname);
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (EINVAL);
return -1;
}
if (buffer[10] != '\n'
|| (buffer[10] = 0, pid = atoi (buffer)) == -1
|| !pid )
{
my_error_2 ("invalid pid %d in lockfile '%s'\n", pid, h->lockname);
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_INV_FILE,
"invalid pid %d in lockfile '%s'\n", pid, h->lockname);
if (buffer != buffer_space)
xfree (buffer);
my_set_errno (EINVAL);
return -1;
}
if (nread == expected_len
&& !memcmp (h->tname+h->nodename_off, buffer+11, h->nodename_len)
&& buffer[11+h->nodename_len] == '\n')
*same_node = 1;
if (buffer != buffer_space)
xfree (buffer);
return pid;
}
#endif /*HAVE_POSIX_SYSTEM */
/* Check whether the file system which stores TNAME supports
hardlinks. Instead of using the non-portable statsfs call which
differs between various Unix versions, we do a runtime test.
Returns: 0 supports hardlinks; 1 no hardlink support, -1 unknown
(test error). */
#ifdef HAVE_POSIX_SYSTEM
static int
use_hardlinks_p (const char *tname)
{
char *lname;
struct stat sb;
unsigned int nlink;
int res;
if (stat (tname, &sb))
return -1;
nlink = (unsigned int)sb.st_nlink;
lname = xtrymalloc (strlen (tname) + 1 + 1);
if (!lname)
return -1;
strcpy (lname, tname);
strcat (lname, "x");
/* We ignore the return value of link() because it is unreliable. */
(void) link (tname, lname);
if (stat (tname, &sb))
res = -1; /* Ooops. */
else if (sb.st_nlink == nlink + 1)
res = 0; /* Yeah, hardlinks are supported. */
else
res = 1; /* No hardlink support. */
unlink (lname);
xfree (lname);
return res;
}
#endif /*HAVE_POSIX_SYSTEM */
#ifdef HAVE_POSIX_SYSTEM
static int
dotlock_get_process_id (dotlock_t h)
{
return h->by_parent? (int)getppid(): (int)getpid();
}
static int
dotlock_detect_tname (dotlock_t h)
{
struct stat sb;
DIR *dir;
char *dirname;
char *basename;
struct dirent *d;
int r;
if (stat (h->lockname, &sb))
return -1;
basename = make_basename (h->lockname, NULL);
dirname = make_dirname (h->lockname);
dir = opendir (dirname);
if (dir == NULL)
{
xfree (basename);
xfree (dirname);
return -1;
}
while ((d = readdir (dir)))
if (sb.st_ino == d->d_ino && strcmp (d->d_name, basename))
break;
if (d)
{
int len = strlen (h->tname);
int dlen = strlen (d->d_name);
const char *tname_path;
if (dlen > len)
{
xfree (basename);
xfree (dirname);
return -1;
}
strcpy (stpcpy (stpcpy (h->tname, dirname), DIRSEP_S), d->d_name);
h->use_o_excl = 0;
tname_path = strchr (h->tname + strlen (dirname) + 2, '.');
if (!tname_path)
{
xfree (basename);
xfree (dirname);
return -1;
}
h->nodename_off = tname_path - h->tname + 1;
}
else
h->use_o_excl = 1;
r = closedir (dir);
if (r)
{
xfree (basename);
xfree (dirname);
return r;
}
xfree (basename);
xfree (dirname);
return 0;
}
/* Locking core for Unix. It used a temporary file and the link
system call to make locking an atomic operation. */
static dotlock_t
dotlock_create_unix (dotlock_t h, const char *file_to_lock)
{
int fd = -1;
char pidstr[16];
const char *nodename;
const char *dirpart;
int dirpartlen;
struct utsname utsbuf;
size_t tnamelen;
int pid;
pid = dotlock_get_process_id (h);
snprintf (pidstr, sizeof pidstr, "%10d\n", pid);
/* Create a temporary file. */
if ( uname ( &utsbuf ) )
nodename = "unknown";
else
nodename = utsbuf.nodename;
if ( !(dirpart = strrchr (file_to_lock, DIRSEP_C)) )
{
dirpart = EXTSEP_S;
dirpartlen = 1;
}
else
{
dirpartlen = dirpart - file_to_lock;
dirpart = file_to_lock;
}
LOCK_all_lockfiles ();
h->next = all_lockfiles;
all_lockfiles = h;
tnamelen = dirpartlen + 6 + 30 + strlen(nodename) + 10 + 1;
h->tname = xtrymalloc (tnamelen + 1);
if (!h->tname)
{
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
xfree (h);
return NULL;
}
h->nodename_len = strlen (nodename);
if (h->no_write)
{
memset (h->tname, '_', tnamelen);
h->tname[tnamelen] = 0;
goto skip_write;
}
snprintf (h->tname, tnamelen, "%.*s/.#lk%p.", dirpartlen, dirpart, h );
h->nodename_off = strlen (h->tname);
snprintf (h->tname+h->nodename_off, tnamelen - h->nodename_off,
"%s.%d", nodename, pid);
do
{
my_set_errno (0);
fd = open (h->tname, O_WRONLY|O_CREAT|O_EXCL,
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
}
while (fd == -1 && errno == EINTR);
if ( fd == -1 )
{
int saveerrno = errno;
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
my_error_2 (_("failed to create temporary file '%s': %s\n"),
h->tname, strerror (errno));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING,
_("failed to create temporary file '%s': %s\n"),
h->tname, strerror (errno));
xfree (h->tname);
xfree (h);
my_set_errno (saveerrno);
return NULL;
}
if ( write (fd, pidstr, 11 ) != 11 )
goto write_failed;
if ( write (fd, nodename, strlen (nodename) ) != strlen (nodename) )
goto write_failed;
if ( write (fd, "\n", 1 ) != 1 )
goto write_failed;
if ( close (fd) )
{
if ( errno == EINTR )
fd = -1;
goto write_failed;
}
fd = -1;
/* Check whether we support hard links. */
switch (use_hardlinks_p (h->tname))
{
case 0: /* Yes. */
break;
case 1: /* No. */
unlink (h->tname);
h->use_o_excl = 1;
break;
default:
{
int saveerrno = errno;
my_error_2 ("can't check whether hardlinks are supported for '%s': %s\n"
, h->tname, strerror (saveerrno));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_CONFIG_TEST,
"can't check whether hardlinks are supported for '%s': %s\n"
, h->tname, strerror (saveerrno));
my_set_errno (saveerrno);
}
goto write_failed;
}
skip_write:
h->lockname = xtrymalloc (strlen (file_to_lock) + 6 );
if (!h->lockname)
{
int saveerrno = errno;
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
unlink (h->tname);
xfree (h->tname);
xfree (h);
my_set_errno (saveerrno);
return NULL;
}
strcpy (stpcpy (h->lockname, file_to_lock), EXTSEP_S "lock");
UNLOCK_all_lockfiles ();
if (h->no_write)
{
if (dotlock_detect_tname (h) < 0)
{
xfree (h->lockname);
xfree (h->tname);
xfree (h);
my_set_errno (EACCES);
return NULL;
}
h->locked = 1;
}
return h;
write_failed:
{
int saveerrno = errno;
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
my_error_2 (_("error writing to '%s': %s\n"), h->tname, strerror (errno));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
_("error writing to '%s': %s\n"),
h->tname, strerror (errno));
if ( fd != -1 )
close (fd);
unlink (h->tname);
xfree (h->tname);
xfree (h);
my_set_errno (saveerrno);
}
return NULL;
}
#endif /*HAVE_POSIX_SYSTEM*/
#ifdef HAVE_DOSISH_SYSTEM
/* Locking core for Windows. This version does not need a temporary
file but uses the plain lock file along with record locking. We
create this file here so that we later only need to do the file
locking. For error reporting it is useful to keep the name of the
file in the handle. */
static dotlock_t
dotlock_create_w32 (dotlock_t h, const char *file_to_lock)
{
LOCK_all_lockfiles ();
h->next = all_lockfiles;
all_lockfiles = h;
h->lockname = strconcat (file_to_lock, EXTSEP_S "lock", NULL);
if (!h->lockname)
{
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
xfree (h);
return NULL;
}
/* If would be nice if we would use the FILE_FLAG_DELETE_ON_CLOSE
along with FILE_SHARE_DELETE but that does not work due to a race
condition: Despite the OPEN_ALWAYS flag CreateFile may return an
error and we can't reliable create/open the lock file unless we
would wait here until it works - however there are other valid
reasons why a lock file can't be created and thus the process
would not stop as expected but spin until Windows crashes. Our
solution is to keep the lock file open; that does not harm. */
if (any8bitchar (h->lockname))
{
wchar_t *wname = utf8_to_wchar (h->lockname);
if (wname)
h->lockhd = CreateFileW (wname,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, 0, NULL);
else
h->lockhd = INVALID_HANDLE_VALUE;
xfree (wname);
}
else
h->lockhd = CreateFileA (h->lockname,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL, OPEN_ALWAYS, 0, NULL);
if (h->lockhd == INVALID_HANDLE_VALUE)
{
int saveerrno = map_w32_to_errno (GetLastError ());
all_lockfiles = h->next;
UNLOCK_all_lockfiles ();
my_error_2 (_("can't create '%s': %s\n"), h->lockname, w32_strerror (-1));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
_("can't create '%s': %s\n"),
h->lockname, w32_strerror (-1));
xfree (h->lockname);
xfree (h);
my_set_errno (saveerrno);
return NULL;
}
return h;
}
#endif /*HAVE_DOSISH_SYSTEM*/
/* Create a lockfile for a file name FILE_TO_LOCK and returns an
object of type dotlock_t which may be used later to actually acquire
the lock. A cleanup routine gets installed to cleanup left over
locks or other files used internally by the lock mechanism.
Calling this function with NULL does only install the atexit
handler and may thus be used to assure that the cleanup is called
after all other atexit handlers.
This function creates a lock file in the same directory as
FILE_TO_LOCK using that name and a suffix of ".lock". Note that on
POSIX systems a temporary file ".#lk.<hostname>.pid[.threadid] is
used.
FLAGS may include DOTLOCK_PREPARE_CREATE bit, which only allocates
the handle and requires a further call to dotlock_finish_create.
This can be used to set a callback between these calls.
FLAGS may include DOTLOCK_LOCK_BY_PARENT bit, when it's the parent
process controlling the lock. This is used by dotlock util.
FLAGS may include DOTLOCK_LOCKED bit, when it should not create the
lockfile, but to unlock. This is used by dotlock util.
The function returns an new handle which needs to be released using
destroy_dotlock but gets also released at the termination of the
process. On error NULL is returned.
*/
dotlock_t
dotlock_create (const char *file_to_lock, unsigned int flags)
{
static int initialized;
dotlock_t h;
#ifndef HAVE_DOSISH_SYSTEM
int by_parent = 0;
int no_write = 0;
#endif
if ( !(flags & DOTLOCK_LOCK_BY_PARENT)
&& !initialized )
{
atexit (dotlock_remove_lockfiles);
initialized = 1;
}
if ( !file_to_lock )
return NULL; /* Only initialization was requested. */
#ifndef HAVE_DOSISH_SYSTEM
if ((flags & DOTLOCK_LOCK_BY_PARENT) || (flags & DOTLOCK_LOCKED))
{
by_parent = !!(flags & DOTLOCK_LOCK_BY_PARENT);
no_write = !!(flags & DOTLOCK_LOCKED);
flags &= ~(DOTLOCK_LOCK_BY_PARENT | DOTLOCK_LOCKED);
}
#endif
if ((flags & ~DOTLOCK_PREPARE_CREATE))
{
my_set_errno (EINVAL);
return NULL;
}
h = xtrycalloc (1, sizeof *h);
if (!h)
return NULL;
h->extra_fd = -1;
#ifndef HAVE_DOSISH_SYSTEM
h->by_parent = by_parent;
h->no_write = no_write;
#endif
if (never_lock)
{
h->disable = 1;
LOCK_all_lockfiles ();
h->next = all_lockfiles;
all_lockfiles = h;
UNLOCK_all_lockfiles ();
return h;
}
if ((flags & DOTLOCK_PREPARE_CREATE))
return h;
else
return dotlock_finish_create (h, file_to_lock);
}
/* This function may be used along with dotlock_create (file_name,
* DOTLOCK_PREPARE_CREATE) to finish the creation call. The given
* filename shall be the same as passed to dotlock_create. On success
* the same handle H is returned, on error NULL is returned and H is
* released. */
dotlock_t
dotlock_finish_create (dotlock_t h, const char *file_to_lock)
{
if (!h || !file_to_lock)
return NULL;
#ifdef HAVE_DOSISH_SYSTEM
return dotlock_create_w32 (h, file_to_lock);
#else /*!HAVE_DOSISH_SYSTEM */
return dotlock_create_unix (h, file_to_lock);
#endif /*!HAVE_DOSISH_SYSTEM*/
}
/* Convenience function to store a file descriptor (or any other
integer value) in the context of handle H. */
void
dotlock_set_fd (dotlock_t h, int fd)
{
h->extra_fd = fd;
}
/* Convenience function to retrieve a file descriptor (or any other
integer value) stored in the context of handle H. */
int
dotlock_get_fd (dotlock_t h)
{
return h->extra_fd;
}
/* Set a callback function for info diagnostics. The callback
* function CB is called with the handle, the opaque value OPAQUE, a
* reason code, and a format string with its arguments. The callback
* shall return 0 to continue operation or true in which case the
* current function will be terminated with an error. */
void
dotlock_set_info_cb (dotlock_t h,
int (*cb)(dotlock_t, void *,
enum dotlock_reasons reason,
const char *,...),
void *opaque)
{
h->info_cb = cb;
h->info_cb_value = opaque;
}
#ifdef HAVE_POSIX_SYSTEM
/* Unix specific code of destroy_dotlock. */
static void
dotlock_destroy_unix (dotlock_t h)
{
if (h->locked && h->lockname)
unlink (h->lockname);
if (h->tname && !h->use_o_excl)
unlink (h->tname);
}
#endif /*HAVE_POSIX_SYSTEM*/
#ifdef HAVE_DOSISH_SYSTEM
/* Windows specific code of destroy_dotlock. */
static void
dotlock_destroy_w32 (dotlock_t h)
{
if (h->locked)
{
OVERLAPPED ovl;
memset (&ovl, 0, sizeof ovl);
UnlockFileEx (h->lockhd, 0, 1, 0, &ovl);
}
CloseHandle (h->lockhd);
}
#endif /*HAVE_DOSISH_SYSTEM*/
/* Destroy the lock handle H and release the lock. */
void
dotlock_destroy (dotlock_t h)
{
dotlock_t hprev, htmp;
if ( !h )
return;
/* First remove the handle from our global list of all locks. */
LOCK_all_lockfiles ();
for (hprev=NULL, htmp=all_lockfiles; htmp; hprev=htmp, htmp=htmp->next)
if (htmp == h)
{
if (hprev)
hprev->next = htmp->next;
else
all_lockfiles = htmp->next;
h->next = NULL;
break;
}
UNLOCK_all_lockfiles ();
/* Then destroy the lock. */
if (!h->disable
&& (!h->by_parent || h->no_write))
{
/* NOTE: under the condition of (by_parent && !no_write),
it doesn't come here. So, the lock file remains. */
#ifdef HAVE_DOSISH_SYSTEM
dotlock_destroy_w32 (h);
#else /* !HAVE_DOSISH_SYSTEM */
dotlock_destroy_unix (h);
#endif /* HAVE_DOSISH_SYSTEM */
}
#ifdef HAVE_POSIX_SYSTEM
/* When DOTLOCK_LOCK_BY_PARENT and lock fails,
the temporary file created should be removed. */
if (h->by_parent && !h->no_write && !h->locked)
if (h->tname && !h->use_o_excl)
unlink (h->tname);
xfree (h->tname);
#endif
xfree (h->lockname);
xfree(h);
}
/* Return true if H has been taken. */
int
dotlock_is_locked (dotlock_t h)
{
return h && !!h->locked;
}
/* Return the next interval to wait. WTIME and TIMEOUT are pointers
* to the current state and are updated by this function. The
* returned value might be different from the value of WTIME. */
static int
next_wait_interval (int *wtime, long *timeout)
{
int result;
/* Wait until lock has been released. We use retry intervals of 4,
* 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 512, 1024, 2048ms, and
* so on. If wait-forever was requested we add a small random value
* to have different timeouts per process. */
if (!*wtime)
*wtime = 4;
else if (*wtime < 2048)
*wtime *= 2;
else
*wtime = 512;
result = *wtime;
if (*wtime > 8 && *timeout < 0)
result += ((unsigned int)getpid() % 37);
if (*timeout > 0)
{
if (result > *timeout)
result = *timeout;
*timeout -= result;
}
return result;
}
#ifdef HAVE_POSIX_SYSTEM
/* Unix specific code of make_dotlock. Returns 0 on success and -1 on
error. */
static int
dotlock_take_unix (dotlock_t h, long timeout)
{
int wtime = 0;
int timedout = 0;
int sumtime = 0;
int pid;
int lastpid = -1;
int ownerchanged;
const char *maybe_dead="";
int same_node;
int saveerrno;
int fd;
again:
if (h->use_o_excl)
{
/* No hardlink support - use open(O_EXCL). */
do
{
my_set_errno (0);
fd = open (h->lockname, O_WRONLY|O_CREAT|O_EXCL,
S_IRUSR|S_IRGRP|S_IROTH|S_IWUSR );
}
while (fd == -1 && errno == EINTR);
if (fd == -1 && errno == EEXIST)
; /* Lock held by another process. */
else if (fd == -1)
{
saveerrno = errno;
my_error_2 ("lock not made: open(O_EXCL) of '%s' failed: %s\n",
h->lockname, strerror (saveerrno));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"lock not made: open(O_EXCL) of '%s' failed: %s\n",
h->lockname, strerror (saveerrno));
my_set_errno (saveerrno);
return -1;
}
else
{
char pidstr[16];
snprintf (pidstr, sizeof pidstr, "%10d\n",
dotlock_get_process_id (h));
if (write (fd, pidstr, 11 ) == 11
&& write (fd, h->tname + h->nodename_off,h->nodename_len)
== h->nodename_len
&& write (fd, "\n", 1) == 1
&& !close (fd))
{
h->locked = 1;
return 0;
}
/* Write error. */
saveerrno = errno;
my_error_2 ("lock not made: writing to '%s' failed: %s\n",
h->lockname, strerror (errno));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"lock not made: writing to '%s' failed: %s\n",
h->lockname, strerror (errno));
close (fd);
unlink (h->lockname);
my_set_errno (saveerrno);
return -1;
}
}
else /* Standard method: Use hardlinks. */
{
struct stat sb;
/* We ignore the return value of link() because it is unreliable. */
(void) link (h->tname, h->lockname);
if (stat (h->tname, &sb))
{
saveerrno = errno;
my_error_1 ("lock not made: Oops: stat of tmp file failed: %s\n",
strerror (errno));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"lock not made: Oops: stat of tmp file failed: %s\n",
strerror (errno));
/* In theory this might be a severe error: It is possible
that link succeeded but stat failed due to changed
permissions. We can't do anything about it, though. */
my_set_errno (saveerrno);
return -1;
}
if (sb.st_nlink == 2)
{
h->locked = 1;
return 0; /* Okay. */
}
}
/* Check for stale lock files. */
if ( (pid = read_lockfile (h, &same_node, &fd)) == -1 )
{
if ( errno != ENOENT )
{
saveerrno = errno;
my_info_0 ("cannot read lockfile\n");
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"cannot read lockfile\n");
my_set_errno (saveerrno);
return -1;
}
my_info_0 ("lockfile disappeared\n");
goto again;
}
else if ( (pid == dotlock_get_process_id (h) && same_node && !h->by_parent)
|| (same_node && kill (pid, 0) && errno == ESRCH) )
{
/* Stale lockfile is detected. */
struct stat sb;
/* Check if it's unlocked during examining the lockfile. */
if (fstat (fd, &sb) || sb.st_nlink == 0)
{
/* It's gone already by another process. */
close (fd);
goto again;
}
/*
* Here, although it's quite _rare_, we have a race condition.
*
* When multiple processes race on a stale lockfile, detecting
* AND removing should be done atomically. That is, to work
* correctly, the file to be removed should be the one which is
* examined for detection.
*
* But, when it's not atomic, consider the case for us where it
* takes some time between the detection and the removal of the
* lockfile.
*
* In this situation, it is possible that the file which was
* detected as stale is already removed by another process and
* then new lockfile is created (by that process or other one).
*
* And it is newly created valid lockfile which is going to be
* removed by us.
*
* Consider this long comment as it expresses possible (long)
* time between fstat above and unlink below; Meanwhile, the
* lockfile in question may be removed and there may be new
* valid one.
*
* In short, when you see the message of removing stale lockfile
* when there are multiple processes for the work, there is
* (very) little possibility something went wrong.
*/
unlink (h->lockname);
my_info_1 (_("removing stale lockfile (created by %d)\n"), pid);
close (fd);
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_STALE_REMOVED,
_("removing stale lockfile (created by %d)\n"), pid);
goto again;
}
close (fd);
if (lastpid == -1)
lastpid = pid;
ownerchanged = (pid != lastpid);
if (timeout)
{
struct timeval tv;
int wtimereal;
if (ownerchanged)
- wtime = 0; /* Reset because owner chnaged. */
+ wtime = 0; /* Reset because owner changed. */
wtimereal = next_wait_interval (&wtime, &timeout);
if (!timeout)
timedout = 1; /* remember. */
sumtime += wtimereal;
if (sumtime >= 1500)
{
sumtime = 0;
my_info_3 (_("waiting for lock (held by %d%s) %s...\n"),
pid, maybe_dead, maybe_deadlock(h)? _("(deadlock?) "):"");
if (h->info_cb
&& h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING,
_("waiting for lock (held by %d%s) %s...\n"),
pid, maybe_dead,
maybe_deadlock(h)? _("(deadlock?) "):""))
{
my_set_errno (ECANCELED);
return -1;
}
}
tv.tv_sec = wtimereal / 1000;
tv.tv_usec = (wtimereal % 1000) * 1000;
select (0, NULL, NULL, NULL, &tv);
goto again;
}
my_set_errno (timedout? ETIMEDOUT : EACCES);
return -1;
}
#endif /*HAVE_POSIX_SYSTEM*/
#ifdef HAVE_DOSISH_SYSTEM
/* Windows specific code of make_dotlock. Returns 0 on success and -1 on
error. */
static int
dotlock_take_w32 (dotlock_t h, long timeout)
{
int wtime = 0;
int timedout = 0;
int w32err;
OVERLAPPED ovl;
again:
/* Lock one byte at offset 0. The offset is given by OVL. */
memset (&ovl, 0, sizeof ovl);
if (LockFileEx (h->lockhd, (LOCKFILE_EXCLUSIVE_LOCK
| LOCKFILE_FAIL_IMMEDIATELY), 0, 1, 0, &ovl))
{
h->locked = 1;
return 0; /* okay */
}
w32err = GetLastError ();
if (w32err != ERROR_LOCK_VIOLATION)
{
my_error_2 (_("lock '%s' not made: %s\n"),
h->lockname, w32_strerror (w32err));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
_("lock '%s' not made: %s\n"),
h->lockname, w32_strerror (w32err));
_set_errno (map_w32_to_errno (w32err));
return -1;
}
if (timeout)
{
int wtimereal;
wtimereal = next_wait_interval (&wtime, &timeout);
if (!timeout)
timedout = 1; /* remember. */
if (wtime >= 800)
{
my_info_1 (_("waiting for lock %s...\n"), h->lockname);
if (h->info_cb
&& h->info_cb (h, h->info_cb_value, DOTLOCK_WAITING,
_("waiting for lock %s...\n"), h->lockname))
{
my_set_errno (ECANCELED);
return -1;
}
}
Sleep (wtimereal);
goto again;
}
my_set_errno (timedout? ETIMEDOUT : EACCES);
return -1;
}
#endif /*HAVE_DOSISH_SYSTEM*/
/* Take a lock on H. A value of 0 for TIMEOUT returns immediately if
the lock can't be taken, -1 waits forever (hopefully not), other
values wait for TIMEOUT milliseconds. Returns: 0 on success */
int
dotlock_take (dotlock_t h, long timeout)
{
int ret;
if ( h->disable )
return 0; /* Locks are completely disabled. Return success. */
if ( h->locked )
{
my_debug_1 ("Oops, '%s' is already locked\n", h->lockname);
return 0;
}
#ifdef HAVE_DOSISH_SYSTEM
ret = dotlock_take_w32 (h, timeout);
#else /*!HAVE_DOSISH_SYSTEM*/
ret = dotlock_take_unix (h, timeout);
#endif /*!HAVE_DOSISH_SYSTEM*/
return ret;
}
#ifdef HAVE_POSIX_SYSTEM
/* Unix specific code of release_dotlock. */
static int
dotlock_release_unix (dotlock_t h)
{
int pid, same_node;
int saveerrno;
pid = read_lockfile (h, &same_node, NULL);
if ( pid == -1 )
{
saveerrno = errno;
my_error_0 ("release_dotlock: lockfile error\n");
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"release_dotlock: lockfile error\n");
my_set_errno (saveerrno);
return -1;
}
if ( pid != dotlock_get_process_id (h) || !same_node )
{
my_error_1 ("release_dotlock: not our lock (pid=%d)\n", pid);
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_CONFLICT,
"release_dotlock: not our lock (pid=%d)\n", pid);
my_set_errno (EACCES);
return -1;
}
if ( unlink( h->lockname ) )
{
saveerrno = errno;
my_error_1 ("release_dotlock: error removing lockfile '%s'\n",
h->lockname);
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"release_dotlock: error removing lockfile '%s'\n",
h->lockname);
my_set_errno (saveerrno);
return -1;
}
/* Fixme: As an extra check we could check whether the link count is
now really at 1. */
return 0;
}
#endif /*HAVE_POSIX_SYSTEM */
#ifdef HAVE_DOSISH_SYSTEM
/* Windows specific code of release_dotlock. */
static int
dotlock_release_w32 (dotlock_t h)
{
OVERLAPPED ovl;
memset (&ovl, 0, sizeof ovl);
if (!UnlockFileEx (h->lockhd, 0, 1, 0, &ovl))
{
int ec = (int)GetLastError ();
my_error_2 ("release_dotlock: error removing lockfile '%s': %s\n",
h->lockname, w32_strerror (ec));
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_FILE_ERROR,
"release_dotlock: error removing lockfile '%s': %s\n",
h->lockname, w32_strerror (ec));
my_set_errno (map_w32_to_errno (ec));
return -1;
}
return 0;
}
#endif /*HAVE_DOSISH_SYSTEM */
/* Release a lock. Returns 0 on success. */
int
dotlock_release (dotlock_t h)
{
int ret;
/* To avoid atexit race conditions we first check whether there are
any locks left. It might happen that another atexit handler
tries to release the lock while the atexit handler of this module
already ran and thus H is undefined. */
LOCK_all_lockfiles ();
ret = !all_lockfiles;
UNLOCK_all_lockfiles ();
if (ret)
return 0;
if ( h->disable )
return 0;
if ( !h->locked )
{
my_debug_1 ("Oops, '%s' is not locked\n", h->lockname);
if (h->info_cb)
h->info_cb (h, h->info_cb_value, DOTLOCK_NOT_LOCKED,
"Oops, '%s' is not locked\n", h->lockname);
return 0;
}
#ifdef HAVE_DOSISH_SYSTEM
ret = dotlock_release_w32 (h);
#else
ret = dotlock_release_unix (h);
#endif
if (!ret)
h->locked = 0;
return ret;
}
/* Remove all lockfiles. This is called by the atexit handler
installed by this module but may also be called by other
termination handlers. */
void
dotlock_remove_lockfiles (void)
{
dotlock_t h, h2;
/* First set the lockfiles list to NULL so that for example
dotlock_release is aware that this function is currently
running. */
LOCK_all_lockfiles ();
h = all_lockfiles;
all_lockfiles = NULL;
UNLOCK_all_lockfiles ();
while ( h )
{
h2 = h->next;
dotlock_destroy (h);
h = h2;
}
}
diff --git a/common/gettime.c b/common/gettime.c
index 136c47ca7..180f388bb 100644
--- a/common/gettime.c
+++ b/common/gettime.c
@@ -1,1146 +1,1146 @@
/* gettime.c - Wrapper for time functions
* Copyright (C) 1998, 2002, 2007, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <time.h>
#include <ctype.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef HAVE_LANGINFO_H
#include <langinfo.h>
#endif
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <stdint.h> /* We use uint64_t. */
#include "util.h"
#include "i18n.h"
#include "gettime.h"
#ifdef HAVE_W32_SYSTEM
#include <windows.h>
#endif
#ifdef HAVE_UNSIGNED_TIME_T
# define IS_INVALID_TIME_T(a) ((a) == (time_t)(-1))
#else
/* Error or 32 bit time_t and value after 2038-01-19. */
# define IS_INVALID_TIME_T(a) ((a) < 0)
#endif
static unsigned long timewarp;
static enum { NORMAL = 0, FROZEN, FUTURE, PAST } timemode;
/* Correction used to map to real Julian days. */
#define JD_DIFF 1721060L
/*
timegm() is a GNU function that might not be available everywhere.
It's basically the inverse of gmtime() - you give it a struct tm,
and get back a time_t. It differs from mktime() in that it handles
the case where the struct tm is UTC and the local environment isn't.
Note, that this replacement implementation might not be thread-safe!
Some BSDs don't handle the putenv("foo") case properly, so we use
unsetenv if the platform has it to remove environment variables.
*/
#ifndef HAVE_TIMEGM
time_t
timegm (struct tm *tm)
{
#ifdef HAVE_W32_SYSTEM
uint64_t val = timegm_u64 (tm);
if (val == (uint64_t)(-1))
return (time_t)(-1);
return (time_t)val;
#else /* (Non thread safe implementation!) */
time_t answer;
char *zone;
zone=getenv("TZ");
putenv("TZ=UTC");
tzset();
answer=mktime(tm);
if(zone)
{
static char *old_zone;
if (!old_zone)
{
old_zone = malloc(3+strlen(zone)+1);
if (old_zone)
{
strcpy(old_zone,"TZ=");
strcat(old_zone,zone);
}
}
if (old_zone)
putenv (old_zone);
}
else
gnupg_unsetenv("TZ");
tzset();
return answer;
#endif
}
#endif /*!HAVE_TIMEGM*/
/* Version of the GNU timegm which returns an unsigned 64 bit integer
* instead of the usually signed time_t. On error (uint64_t)(-1) is
- * returned. This function is mostly here becuase on 32 bit Windows
+ * returned. This function is mostly here because on 32 bit Windows
* we have an internal API to get the system time even after
* 2023-01-19. For 32 bit Unix we need to suffer from the too short
* time_t and no system function to construct the time from a tm. */
uint64_t
timegm_u64 (struct tm *tm)
{
#ifdef HAVE_W32_SYSTEM
/* This one is thread safe. */
SYSTEMTIME st;
FILETIME ft;
unsigned long long cnsecs;
st.wYear = tm->tm_year + 1900;
st.wMonth = tm->tm_mon + 1;
st.wDay = tm->tm_mday;
st.wHour = tm->tm_hour;
st.wMinute = tm->tm_min;
st.wSecond = tm->tm_sec;
st.wMilliseconds = 0; /* Not available. */
st.wDayOfWeek = 0; /* Ignored. */
/* System time is UTC thus the conversion is pretty easy. */
if (!SystemTimeToFileTime (&st, &ft))
{
gpg_err_set_errno (EINVAL);
return (uint64_t)(-1);
}
cnsecs = (((unsigned long long)ft.dwHighDateTime << 32)
| ft.dwLowDateTime);
cnsecs -= 116444736000000000ULL; /* The filetime epoch is 1601-01-01. */
return (uint64_t)(cnsecs / 10000000ULL);
#else /*Unix*/
time_t t = timegm (tm);
if (t == (time_t)(-1))
return (uint64_t)(-1);
if ((int64_t)t < 0)
return (uint64_t)(-1);
return (uint64_t)t;
#endif /*Unix*/
}
/* Wrapper for the time(3). We use this here so we can fake the time
for tests */
time_t
gnupg_get_time (void)
{
time_t current = time (NULL);
if (current == (time_t)(-1))
log_fatal ("time() failed\n");
if (timemode == NORMAL)
return current;
else if (timemode == FROZEN)
return timewarp;
else if (timemode == FUTURE)
return current + timewarp;
else
return current - timewarp;
}
/* Wrapper around gmtime_r.
On systems without gmtime_r this implementation works within gnupg
because we use only one thread a time. FIXME: An independent
library may use gmtime in one of its own thread (or via
npth_enter/npth_leave) - in this case we run into a problem. The
solution would be to use a mutex here. */
struct tm *
gnupg_gmtime (const time_t *timep, struct tm *result)
{
#ifdef HAVE_GMTIME_R
return gmtime_r (timep, result);
#else
struct tm *tp;
tp = gmtime (timep);
if (tp)
memcpy (result, tp, sizeof *result);
return tp;
#endif
}
/* Return the current time (possibly faked) in ISO format. */
void
gnupg_get_isotime (gnupg_isotime_t timebuf)
{
time_t atime = gnupg_get_time ();
struct tm *tp;
struct tm tmbuf;
tp = gnupg_gmtime (&atime, &tmbuf);
if (!tp)
*timebuf = 0;
else
snprintf (timebuf, 16, "%04d%02d%02dT%02d%02d%02d",
1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
/* Set the time to NEWTIME so that gnupg_get_time returns a time
starting with this one. With FREEZE set to 1 the returned time
will never change. Just for completeness, a value of (time_t)-1
for NEWTIME gets you back to reality. Note that this is obviously
not thread-safe but this is not required. */
void
gnupg_set_time (time_t newtime, int freeze)
{
time_t current = time (NULL);
if ( newtime == (time_t)-1 || current == newtime)
{
timemode = NORMAL;
timewarp = 0;
}
else if (freeze)
{
timemode = FROZEN;
timewarp = newtime == (time_t)-1 ? current : newtime;
}
else if (newtime > current)
{
timemode = FUTURE;
timewarp = newtime - current;
}
else
{
timemode = PAST;
timewarp = current - newtime;
}
}
/* Returns true when we are in timewarp mode */
int
gnupg_faked_time_p (void)
{
return timemode;
}
/* This function is used by gpg because OpenPGP defines the timestamp
as an unsigned 32 bit value. */
u32
make_timestamp (void)
{
time_t t = gnupg_get_time ();
return (u32)t;
}
/* Specialized version of atoi which returns an u32 instead of an int
* and caps the result at 2^32-2. Leading white space is skipped,
* scanning stops at at the first non-convertable byte. Note that we
* do not cap at 2^32-1 because that value is often used as error
* return. */
u32
scan_secondsstr (const char *string)
{
uint64_t value = 0;
while (*string == ' ' || *string == '\t')
string++;
for (; *string >= '0' && *string <= '9'; string++)
{
value *= 10;
value += atoi_1 (string);
if (value >= (u32)(-1))
return (u32)(-1) - 1;
}
return (u32)value;
}
/****************
* Scan a date string and return a timestamp.
* The only supported format is "yyyy-mm-dd"
* Returns 0 for an invalid date.
*/
u32
scan_isodatestr( const char *string )
{
int year, month, day;
struct tm tmbuf;
time_t stamp;
int i;
if( strlen(string) != 10 || string[4] != '-' || string[7] != '-' )
return 0;
for( i=0; i < 4; i++ )
if( !digitp (string+i) )
return 0;
if( !digitp (string+5) || !digitp(string+6) )
return 0;
if( !digitp(string+8) || !digitp(string+9) )
return 0;
year = atoi(string);
month = atoi(string+5);
day = atoi(string+8);
/* some basic checks */
if( year < 1970 || month < 1 || month > 12 || day < 1 || day > 31 )
return 0;
memset( &tmbuf, 0, sizeof tmbuf );
tmbuf.tm_mday = day;
tmbuf.tm_mon = month-1;
tmbuf.tm_year = year - 1900;
tmbuf.tm_isdst = -1;
stamp = mktime( &tmbuf );
if( stamp == (time_t)-1 )
{
/* mktime did not work. Construct an ISO timestring for noon
* of the given day instead. We keep the use of mktime for 64
* bit system to limit the risk of regressions. */
gnupg_isotime_t isobuf;
uint64_t tmp64;
snprintf (isobuf, 16, "%04d%02d%02dT120000", year, month, day);
tmp64 = isotime2epoch_u64 (isobuf);
if (tmp64 == (uint64_t)(-1))
return 0; /* Error. */
if (tmp64 >= (u32)(-1))
return 0; /* Error. */
return (u32)tmp64;
}
return stamp;
}
int
isotime_p (const char *string)
{
const char *s;
int i;
if (!*string)
return 0;
for (s=string, i=0; i < 8; i++, s++)
if (!digitp (s))
return 0;
if (*s != 'T')
return 0;
for (s++, i=9; i < 15; i++, s++)
if (!digitp (s))
return 0;
if (*s == 'Z')
s++;
if ( !(!*s || (isascii (*s) && isspace(*s)) || *s == ':' || *s == ','))
return 0; /* Wrong delimiter. */
return 1;
}
/* Scan a string and return true if the string represents the human
readable format of an ISO time. This format is:
yyyy-mm-dd[ hh[:mm[:ss]]]
Scanning stops at the second space or at a comma. If DATE_ONLY is
true the time part is not expected and the scanning stops at the
first space or at a comma. */
int
isotime_human_p (const char *string, int date_only)
{
const char *s;
int i;
if (!*string)
return 0;
for (s=string, i=0; i < 4; i++, s++)
if (!digitp (s))
return 0;
if (*s != '-')
return 0;
s++;
if (!digitp (s) || !digitp (s+1) || s[2] != '-')
return 0;
i = atoi_2 (s);
if (i < 1 || i > 12)
return 0;
s += 3;
if (!digitp (s) || !digitp (s+1))
return 0;
i = atoi_2 (s);
if (i < 1 || i > 31)
return 0;
s += 2;
if (!*s || *s == ',')
return 1; /* Okay; only date given. */
if (!spacep (s))
return 0;
if (date_only)
return 1; /* Okay; only date was requested. */
s++;
if (spacep (s))
return 1; /* Okay, second space stops scanning. */
if (!digitp (s) || !digitp (s+1))
return 0;
i = atoi_2 (s);
if (i < 0 || i > 23)
return 0;
s += 2;
if (!*s || *s == ',')
return 1; /* Okay; only date and hour given. */
if (*s != ':')
return 0;
s++;
if (!digitp (s) || !digitp (s+1))
return 0;
i = atoi_2 (s);
if (i < 0 || i > 59)
return 0;
s += 2;
if (!*s || *s == ',')
return 1; /* Okay; only date, hour and minute given. */
if (*s != ':')
return 0;
s++;
if (!digitp (s) || !digitp (s+1))
return 0;
i = atoi_2 (s);
if (i < 0 || i > 60)
return 0;
s += 2;
if (!*s || *s == ',' || spacep (s))
return 1; /* Okay; date, hour and minute and second given. */
return 0; /* Unexpected delimiter. */
}
/* Convert a standard isotime or a human readable variant into an
isotime structure. The allowed formats are those described by
isotime_p and isotime_human_p. The function returns 0 on failure
or the length of the scanned string on success. */
size_t
string2isotime (gnupg_isotime_t atime, const char *string)
{
gnupg_isotime_t dummyatime;
if (!atime)
atime = dummyatime;
atime[0] = 0;
if (isotime_p (string))
{
memcpy (atime, string, 15);
atime[15] = 0;
return 15;
}
if (!isotime_human_p (string, 0))
return 0;
atime[0] = string[0];
atime[1] = string[1];
atime[2] = string[2];
atime[3] = string[3];
atime[4] = string[5];
atime[5] = string[6];
atime[6] = string[8];
atime[7] = string[9];
atime[8] = 'T';
memset (atime+9, '0', 6);
atime[15] = 0;
if (!spacep (string+10))
return 10;
if (spacep (string+11))
return 11; /* As per def, second space stops scanning. */
atime[9] = string[11];
atime[10] = string[12];
if (string[13] != ':')
return 13;
atime[11] = string[14];
atime[12] = string[15];
if (string[16] != ':')
return 16;
atime[13] = string[17];
atime[14] = string[18];
return 19;
}
/* Helper for isotime2epoch. Returns 0 on success. */
static int
isotime_make_tm (const char *string, struct tm *tmbuf)
{
int year, month, day, hour, minu, sec;
if (!isotime_p (string))
return -1;
year = atoi_4 (string);
month = atoi_2 (string + 4);
day = atoi_2 (string + 6);
hour = atoi_2 (string + 9);
minu = atoi_2 (string + 11);
sec = atoi_2 (string + 13);
/* Basic checks. */
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31
|| hour > 23 || minu > 59 || sec > 61 )
return -1;
memset (tmbuf, 0, sizeof *tmbuf);
tmbuf->tm_sec = sec;
tmbuf->tm_min = minu;
tmbuf->tm_hour = hour;
tmbuf->tm_mday = day;
tmbuf->tm_mon = month-1;
tmbuf->tm_year = year - 1900;
tmbuf->tm_isdst = -1;
return 0;
}
/* Scan an ISO timestamp and return an Epoch based timestamp. The
only supported format is "yyyymmddThhmmss[Z]" delimited by white
space, nul, a colon or a comma. Returns (time_t)(-1) for an
invalid string. */
time_t
isotime2epoch (const char *string)
{
struct tm tmbuf;
if (isotime_make_tm (string, &tmbuf))
return (time_t)(-1);
return timegm (&tmbuf);
}
uint64_t
isotime2epoch_u64 (const char *string)
{
struct tm tmbuf;
if (isotime_make_tm (string, &tmbuf))
return (uint64_t)(-1);
return timegm_u64 (&tmbuf);
}
/* Convert an Epoch time to an iso time stamp. */
void
epoch2isotime (gnupg_isotime_t timebuf, time_t atime)
{
if (atime == (time_t)(-1))
*timebuf = 0;
else
{
struct tm *tp;
#ifdef HAVE_GMTIME_R
struct tm tmbuf;
tp = gmtime_r (&atime, &tmbuf);
#else
tp = gmtime (&atime);
#endif
snprintf (timebuf, 16, "%04d%02d%02dT%02d%02d%02d",
1900 + tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
}
/* Parse a short ISO date string (YYYY-MM-DD) into a TM structure.
Returns 0 on success. */
int
isodate_human_to_tm (const char *string, struct tm *t)
{
int year, month, day;
if (!isotime_human_p (string, 1))
return -1;
year = atoi_4 (string);
month = atoi_2 (string + 5);
day = atoi_2 (string + 8);
/* Basic checks. */
if (year < 1970 || month < 1 || month > 12 || day < 1 || day > 31)
return -1;
memset (t, 0, sizeof *t);
t->tm_sec = 0;
t->tm_min = 0;
t->tm_hour = 0;
t->tm_mday = day;
t->tm_mon = month-1;
t->tm_year = year - 1900;
t->tm_isdst = -1;
return 0;
}
/* Parse the string TIMESTAMP into a time_t. The string may either be
seconds since Epoch or in the ISO 8601 format like
"20390815T143012". Returns 0 for an empty string or seconds since
Epoch. Leading spaces are skipped. If ENDP is not NULL, it will
point to the next non-parsed character in TIMESTRING.
This function is a copy of
gpgme/src/conversion.c:_gpgme_parse_timestamp. If you change it,
then update the other one too.
FIXME: Replace users of this function by one of the more modern
functions or change the return type to u64.
*/
time_t
parse_timestamp (const char *timestamp, char **endp)
{
/* Need to skip leading spaces, because that is what strtoul does
but not our ISO 8601 checking code. */
while (*timestamp && *timestamp== ' ')
timestamp++;
if (!*timestamp)
return 0;
if (strlen (timestamp) >= 15 && timestamp[8] == 'T')
{
struct tm buf;
int year;
year = atoi_4 (timestamp);
if (year < 1900)
return (time_t)(-1);
if (endp)
*endp = (char*)(timestamp + 15);
/* Fixme: We would better use a configure test to see whether
mktime can handle dates beyond 2038. */
if (sizeof (time_t) <= 4 && year >= 2038)
return (time_t)2145914603; /* 2037-12-31 23:23:23 */
memset (&buf, 0, sizeof buf);
buf.tm_year = year - 1900;
buf.tm_mon = atoi_2 (timestamp+4) - 1;
buf.tm_mday = atoi_2 (timestamp+6);
buf.tm_hour = atoi_2 (timestamp+9);
buf.tm_min = atoi_2 (timestamp+11);
buf.tm_sec = atoi_2 (timestamp+13);
return timegm (&buf);
}
else
return (time_t)strtoul (timestamp, endp, 10);
}
u32
add_days_to_timestamp( u32 stamp, u16 days )
{
return stamp + days*86400L;
}
/****************
* Return a string with a time value in the form: x Y, n D, n H
*/
const char *
strtimevalue( u32 value )
{
static char buffer[30];
unsigned int years, days, hours, minutes;
value /= 60;
minutes = value % 60;
value /= 60;
hours = value % 24;
value /= 24;
days = value % 365;
value /= 365;
years = value;
sprintf(buffer,"%uy%ud%uh%um", years, days, hours, minutes );
if( years )
return buffer;
if( days )
return strchr( buffer, 'y' ) + 1;
return strchr( buffer, 'd' ) + 1;
}
/* Return a malloced string with the time elapsed between NOW and
SINCE. May return NULL on error. */
char *
elapsed_time_string (time_t since, time_t now)
{
char *result;
double diff;
unsigned long value;
unsigned int days, hours, minutes, seconds;
if (!now)
now = gnupg_get_time ();
diff = difftime (now, since);
if (diff < 0)
return xtrystrdup ("time-warp");
seconds = (unsigned long)diff % 60;
value = (unsigned long)(diff / 60);
minutes = value % 60;
value /= 60;
hours = value % 24;
value /= 24;
days = value % 365;
if (days)
result = xtryasprintf ("%ud%uh%um%us", days, hours, minutes, seconds);
else if (hours)
result = xtryasprintf ("%uh%um%us", hours, minutes, seconds);
else if (minutes)
result = xtryasprintf ("%um%us", minutes, seconds);
else
result = xtryasprintf ("%us", seconds);
return result;
}
/*
* Note: this function returns GMT
*/
const char *
strtimestamp (u32 stamp)
{
static char buffer[11+5];
struct tm *tp;
time_t atime = stamp;
if (IS_INVALID_TIME_T (atime))
{
strcpy (buffer, "????" "-??" "-??");
}
else
{
tp = gmtime( &atime );
snprintf (buffer, sizeof buffer, "%04d-%02d-%02d",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
}
return buffer;
}
/*
* Note: this function returns GMT
*/
const char *
isotimestamp (u32 stamp)
{
static char buffer[25+5];
struct tm *tp;
time_t atime = stamp;
if (IS_INVALID_TIME_T (atime))
{
strcpy (buffer, "????" "-??" "-??" " " "??" ":" "??" ":" "??");
}
else
{
tp = gmtime ( &atime );
snprintf (buffer, sizeof buffer, "%04d-%02d-%02d %02d:%02d:%02d",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday,
tp->tm_hour, tp->tm_min, tp->tm_sec);
}
return buffer;
}
/****************
* Note: this function returns local time
*/
const char *
asctimestamp (u32 stamp)
{
static char buffer[80];
#if defined (HAVE_STRFTIME) && defined (HAVE_NL_LANGINFO)
static char fmt[80];
#endif
struct tm *tp;
time_t atime = stamp;
if (IS_INVALID_TIME_T (atime))
{
strcpy (buffer, "????" "-??" "-??");
return buffer;
}
tp = localtime( &atime );
#ifdef HAVE_STRFTIME
# if defined(HAVE_NL_LANGINFO)
mem2str( fmt, nl_langinfo(D_T_FMT), DIM(fmt)-3 );
if (!strstr( fmt, "%Z" ))
strcat( fmt, " %Z");
/* NOTE: gcc -Wformat-noliteral will complain here. I have found no
way to suppress this warning. */
strftime (buffer, DIM(buffer)-1, fmt, tp);
# else
# if HAVE_W32_SYSTEM
{
static int done;
if (!done)
{
/* The locale names as used by Windows are in the form
* "German_Germany.1252" or "German_Austria.1252" with
* alternate names similar to Unix, e.g. "de-DE". However
* that is the theory. On current Windows and Mingw the
* alternate names do not work. We would need a table to map
* them from the short names as provided by gpgrt to the long
* names and append some code page. For now we use "" and
* take the locale from the user's system settings. Thus the
* standard Unix envvars don't work for time and may mismatch
* with the string translations. The new UCRT available since
* 2018 has a lot of additional support but that will for sure
* break other things. We should move to ISO strings to get
* rid of such problems. */
setlocale (LC_TIME, ".UTF8");
done = 1;
/* log_debug ("LC_ALL now '%s'\n", setlocale (LC_ALL, NULL)); */
/* log_debug ("LC_TIME now '%s'\n", setlocale (LC_TIME, NULL)); */
}
}
# endif
/* FIXME: we should check whether the locale appends a " %Z" These
* locales from glibc don't put the " %Z": fi_FI hr_HR ja_JP lt_LT
* lv_LV POSIX ru_RU ru_SU sv_FI sv_SE zh_CN. */
strftime (buffer, DIM(buffer)-1, "%c %Z", tp);
# endif
buffer[DIM(buffer)-1] = 0;
#else
mem2str( buffer, asctime(tp), DIM(buffer) );
#endif
return buffer;
}
/* Return the timestamp STAMP in RFC-2822 format. This is always done
* in the C locale. We return the gmtime to avoid computing the
* timezone. The caller must release the returned string.
*
* Example: "Mon, 27 Jun 2016 1:42:00 +0000".
*/
char *
rfctimestamp (u32 stamp)
{
time_t atime = stamp;
struct tm tmbuf, *tp;
if (IS_INVALID_TIME_T (atime))
{
gpg_err_set_errno (EINVAL);
return NULL;
}
tp = gnupg_gmtime (&atime, &tmbuf);
if (!tp)
return NULL;
return xtryasprintf ("%.3s, %02d %.3s %04d %02d:%02d:%02d +0000",
&"SunMonTueWedThuFriSat"[(tp->tm_wday%7)*3],
tp->tm_mday,
&"JanFebMarAprMayJunJulAugSepOctNovDec"
[(tp->tm_mon%12)*3],
tp->tm_year + 1900,
tp->tm_hour,
tp->tm_min,
tp->tm_sec);
}
static int
days_per_year (int y)
{
int s ;
s = !(y % 4);
if ( !(y % 100))
if ((y%400))
s = 0;
return s ? 366 : 365;
}
static int
days_per_month (int y, int m)
{
int s;
switch(m)
{
case 1: case 3: case 5: case 7: case 8: case 10: case 12:
return 31 ;
case 2:
s = !(y % 4);
if (!(y % 100))
if ((y % 400))
s = 0;
return s? 29 : 28 ;
case 4: case 6: case 9: case 11:
return 30;
}
BUG();
}
/* Convert YEAR, MONTH and DAY into the Julian date. We assume that
it is already noon. We do not support dates before 1582-10-15. */
static unsigned long
date2jd (int year, int month, int day)
{
unsigned long jd;
jd = 365L * year + 31 * (month-1) + day + JD_DIFF;
if (month < 3)
year-- ;
else
jd -= (4 * month + 23) / 10;
jd += year / 4 - ((year / 100 + 1) *3) / 4;
return jd ;
}
/* Convert a Julian date back to YEAR, MONTH and DAY. Return day of
the year or 0 on error. This function uses some more or less
arbitrary limits, most important is that days before 1582 are not
supported. */
static int
jd2date (unsigned long jd, int *year, int *month, int *day)
{
int y, m, d;
long delta;
if (!jd)
return 0 ;
if (jd < 1721425 || jd > 2843085)
return 0;
y = (jd - JD_DIFF) / 366;
d = m = 1;
while ((delta = jd - date2jd (y, m, d)) > days_per_year (y))
y++;
m = (delta / 31) + 1;
while( (delta = jd - date2jd (y, m, d)) > days_per_month (y,m))
if (++m > 12)
{
m = 1;
y++;
}
d = delta + 1 ;
if (d > days_per_month (y, m))
{
d = 1;
m++;
}
if (m > 12)
{
m = 1;
y++;
}
if (year)
*year = y;
if (month)
*month = m;
if (day)
*day = d ;
return (jd - date2jd (y, 1, 1)) + 1;
}
/* Check that the 15 bytes in ATIME represent a valid ISO time. Note
that this function does not expect a string but a plain 15 byte
isotime buffer. */
gpg_error_t
check_isotime (const gnupg_isotime_t atime)
{
int i;
const char *s;
if (!*atime)
return gpg_error (GPG_ERR_NO_VALUE);
for (s=atime, i=0; i < 8; i++, s++)
if (!digitp (s))
return gpg_error (GPG_ERR_INV_TIME);
if (*s != 'T')
return gpg_error (GPG_ERR_INV_TIME);
for (s++, i=9; i < 15; i++, s++)
if (!digitp (s))
return gpg_error (GPG_ERR_INV_TIME);
return 0;
}
/* Dump the ISO time T to the log stream without a LF. */
void
dump_isotime (const gnupg_isotime_t t)
{
if (!t || !*t)
log_printf ("%s", _("[none]"));
else
log_printf ("%.4s-%.2s-%.2s %.2s:%.2s:%s",
t, t+4, t+6, t+9, t+11, t+13);
}
/* Copy one ISO date to another, this is inline so that we can do a
minimal sanity check. A null date (empty string) is allowed. */
void
gnupg_copy_time (gnupg_isotime_t d, const gnupg_isotime_t s)
{
if (*s)
{
if ((strlen (s) != 15 || s[8] != 'T'))
BUG();
memcpy (d, s, 15);
d[15] = 0;
}
else
*d = 0;
}
/* Add SECONDS to ATIME. SECONDS may not be negative and is limited
to about the equivalent of 62 years which should be more then
enough for our purposes. */
gpg_error_t
add_seconds_to_isotime (gnupg_isotime_t atime, int nseconds)
{
gpg_error_t err;
int year, month, day, hour, minute, sec, ndays;
unsigned long jd;
err = check_isotime (atime);
if (err)
return err;
if (nseconds < 0 || nseconds >= (0x7fffffff - 61) )
return gpg_error (GPG_ERR_INV_VALUE);
year = atoi_4 (atime+0);
month = atoi_2 (atime+4);
day = atoi_2 (atime+6);
hour = atoi_2 (atime+9);
minute= atoi_2 (atime+11);
sec = atoi_2 (atime+13);
if (year <= 1582) /* The julian date functions don't support this. */
return gpg_error (GPG_ERR_INV_VALUE);
sec += nseconds;
minute += sec/60;
sec %= 60;
hour += minute/60;
minute %= 60;
ndays = hour/24;
hour %= 24;
jd = date2jd (year, month, day) + ndays;
jd2date (jd, &year, &month, &day);
if (year > 9999 || month > 12 || day > 31
|| year < 0 || month < 1 || day < 1)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (atime, 16, "%04d%02d%02dT%02d%02d%02d",
year, month, day, hour, minute, sec);
return 0;
}
gpg_error_t
add_days_to_isotime (gnupg_isotime_t atime, int ndays)
{
gpg_error_t err;
int year, month, day, hour, minute, sec;
unsigned long jd;
err = check_isotime (atime);
if (err)
return err;
if (ndays < 0 || ndays >= 9999*366 )
return gpg_error (GPG_ERR_INV_VALUE);
year = atoi_4 (atime+0);
month = atoi_2 (atime+4);
day = atoi_2 (atime+6);
hour = atoi_2 (atime+9);
minute= atoi_2 (atime+11);
sec = atoi_2 (atime+13);
if (year <= 1582) /* The julian date functions don't support this. */
return gpg_error (GPG_ERR_INV_VALUE);
jd = date2jd (year, month, day) + ndays;
jd2date (jd, &year, &month, &day);
if (year > 9999 || month > 12 || day > 31
|| year < 0 || month < 1 || day < 1)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (atime, 16, "%04d%02d%02dT%02d%02d%02d",
year, month, day, hour, minute, sec);
return 0;
}
diff --git a/common/homedir.c b/common/homedir.c
index deb6f3616..392910867 100644
--- a/common/homedir.c
+++ b/common/homedir.c
@@ -1,1907 +1,1907 @@
/* homedir.c - Setup the home directory.
* Copyright (C) 2004, 2006, 2007, 2010 Free Software Foundation, Inc.
* Copyright (C) 2013, 2016 Werner Koch
* Copyright (C) 2021, 2024 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later)
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
#include <winsock2.h> /* Due to the stupid mingw64 requirement to
include this header before windows.h which
is often implicitly included. */
#include <shlobj.h>
#ifndef CSIDL_APPDATA
#define CSIDL_APPDATA 0x001a
#endif
#ifndef CSIDL_LOCAL_APPDATA
#define CSIDL_LOCAL_APPDATA 0x001c
#endif
#ifndef CSIDL_COMMON_APPDATA
#define CSIDL_COMMON_APPDATA 0x0023
#endif
#ifndef CSIDL_FLAG_CREATE
#define CSIDL_FLAG_CREATE 0x8000
#endif
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_STAT
#include <sys/stat.h> /* for stat() */
#endif
#include "util.h"
#include "sysutils.h"
#include "i18n.h"
#include "zb32.h"
/* The name of the symbolic link to the file from which the process
* text was read. */
#if __linux__
# define MYPROC_SELF_EXE "/proc/self/exe"
#elif defined(__NetBSD__)
# define MYPROC_SELF_EXE "/proc/curproc/exe"
#elif defined(__illumos__) || defined(__sun)
# define MYPROC_SELF_EXE "/proc/self/path/a.out"
#else /* Assume other BSDs */
# define MYPROC_SELF_EXE "/proc/curproc/file"
#endif
/* Mode flags for unix_rootdir. */
enum wantdir_values {
WANTDIR_ROOT = 0,
WANTDIR_SYSCONF,
WANTDIR_SOCKET
};
/* The GnuPG homedir. This is only accessed by the functions
* gnupg_homedir and gnupg_set_homedir. Malloced. */
static char *the_gnupg_homedir;
/* Flag indicating that home directory is not the default one. */
static byte non_default_homedir;
/* An object to store information taken from a gpgconf.ctl file. This
* is parsed early at startup time and never changed later. */
static struct
{
unsigned int checked:1; /* True if we have checked for a gpgconf.ctl. */
unsigned int found:1; /* True if a gpgconf.ctl was found. */
unsigned int empty:1; /* The file is empty except for comments. */
unsigned int valid:1; /* The entries in gpgconf.ctl are valid. */
unsigned int portable:1;/* Windows portable installation. */
char *gnupg; /* The "gnupg" directory part. */
char *rootdir; /* rootdir or NULL */
char *sysconfdir; /* sysconfdir or NULL */
char *socketdir; /* socketdir or NULL */
} gpgconf_ctl;
#ifdef HAVE_W32_SYSTEM
/* A flag used to indicate that a control file for gpgconf has been
* detected. Under Windows the presence of this file indicates a
* portable installations and triggers several changes:
*
* - The GNUGHOME directory is fixed relative to installation
* directory. All other means to set the home directory are ignored.
*
* - All registry variables will be ignored.
*
* This flag is not used on Unix systems.
*/
static byte w32_portable_app;
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
/* This flag is true if this process's binary has been installed under
bin and not in the root directory as often used before GnuPG 2.1. */
static byte w32_bin_is_bin;
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
static const char *w32_rootdir (void);
#endif
/* Return the name of the gnupg dir. This is usually "gnupg". */
static const char *
my_gnupg_dirname (void)
{
if (gpgconf_ctl.valid && gpgconf_ctl.gnupg)
return gpgconf_ctl.gnupg;
return "gnupg";
}
/* Return the hardwired home directory which is not anymore so
* hardwired because it may now be modified using the gpgconf.ctl
* "gnupg" keyword. */
static const char *
my_fixed_default_homedir (void)
{
if (gpgconf_ctl.valid && gpgconf_ctl.gnupg)
{
static char *name;
char *p;
if (!name)
{
name = xmalloc (strlen (GNUPG_DEFAULT_HOMEDIR)
+ strlen (gpgconf_ctl.gnupg) + 1);
strcpy (name, GNUPG_DEFAULT_HOMEDIR);
p = strrchr (name, '/');
if (p)
p++;
else
p = name;
if (*p == '.')
p++; /* Keep a leading dot. */
strcpy (p, gpgconf_ctl.gnupg);
gpgrt_annotate_leaked_object (name);
}
return name;
}
return GNUPG_DEFAULT_HOMEDIR;
}
/* Under Windows we need to modify the standard registry key with the
* "gnupg" keyword from a gpgconf.ctl. */
#ifdef HAVE_W32_SYSTEM
const char *
gnupg_registry_dir (void)
{
if (gpgconf_ctl.valid && gpgconf_ctl.gnupg)
{
static char *name;
char *p;
if (!name)
{
name = xmalloc (strlen (GNUPG_REGISTRY_DIR)
+ strlen (gpgconf_ctl.gnupg) + 1);
strcpy (name, GNUPG_REGISTRY_DIR);
p = strrchr (name, '\\');
if (p)
p++;
else
p = name;
strcpy (p, gpgconf_ctl.gnupg);
if (!strncmp (p, "gnupg", 5))
{
/* Registry keys are case-insensitive and we use a
* capitalized version of gnupg by default. So, if the
* new value starts with "gnupg" we apply the usual
* capitalization for this first part. */
p[0] = 'G';
p[3] = 'P';
p[4] = 'G';
}
gpgrt_annotate_leaked_object (name);
}
return name;
}
return GNUPG_REGISTRY_DIR;
}
#endif /*HAVE_W32_SYSTEM*/
/* This is a helper function to load and call a Windows function from
* either of one DLLs. On success an UTF-8 file name is returned.
* ERRNO is _not_ set on error. */
#ifdef HAVE_W32_SYSTEM
static char *
w32_shgetfolderpath (HWND a, int b, HANDLE c, DWORD d)
{
static int initialized;
static HRESULT (WINAPI * func)(HWND,int,HANDLE,DWORD,LPWSTR);
wchar_t wfname[MAX_PATH];
if (!initialized)
{
static char *dllnames[] = { "shell32.dll", "shfolder.dll", NULL };
void *handle;
int i;
initialized = 1;
for (i=0, handle = NULL; !handle && dllnames[i]; i++)
{
handle = dlopen (dllnames[i], RTLD_LAZY);
if (handle)
{
func = dlsym (handle, "SHGetFolderPathW");
if (!func)
{
dlclose (handle);
handle = NULL;
}
}
}
}
if (func && func (a,b,c,d,wfname) >= 0)
return wchar_to_utf8 (wfname);
else
return NULL;
}
#endif /*HAVE_W32_SYSTEM*/
/* Given the directory name DNAME try to create a common.conf and
* enable the keyboxd. This should only be called for the standard
* home directory and only if that directory has just been created. */
static void
create_common_conf (const char *dname)
{
#ifdef BUILD_WITH_KEYBOXD
estream_t fp;
char *fcommon;
fcommon = make_filename (dname, "common.conf", NULL);
fp = es_fopen (fcommon, "wx,mode=-rw-r");
if (!fp)
{
log_info (_("error creating '%s': %s\n"), fcommon,
gpg_strerror (gpg_error_from_syserror ()));
}
else
{
if (es_fputs ("use-keyboxd\n", fp) == EOF)
{
log_info (_("error writing to '%s': %s\n"), fcommon,
gpg_strerror (es_ferror (fp)
? gpg_error_from_syserror ()
: gpg_error (GPG_ERR_EOF)));
es_fclose (fp);
}
else if (es_fclose (fp))
{
log_info (_("error closing '%s': %s\n"), fcommon,
gpg_strerror (gpg_error_from_syserror ()));
}
}
#endif /* BUILD_WITH_KEYBOXD */
}
/* Check whether DIR is the default homedir. */
static int
is_gnupg_default_homedir (const char *dir)
{
int result;
char *a = make_absfilename (dir, NULL);
char *b = make_absfilename (standard_homedir (), NULL);
result = !compare_filenames (a, b);
xfree (b);
xfree (a);
return result;
}
/* Helper to remove trailing slashes from NEWDIR. Return a new
* allocated string if that has been done or NULL if there are no
* slashes to remove. Also inserts a missing slash after a Windows
* drive letter. */
static char *
copy_dir_with_fixup (const char *newdir)
{
char *result = NULL;
char *p;
#ifdef HAVE_W32_SYSTEM
char *p0;
const char *s;
#endif
if (!*newdir)
return NULL;
#ifdef HAVE_W32_SYSTEM
if (newdir[0] && newdir[1] == ':'
&& !(newdir[2] == '/' || newdir[2] == '\\'))
{
/* Drive letter with missing leading slash. */
p = result = xmalloc (strlen (newdir) + 1 + 1);
*p++ = newdir[0];
*p++ = newdir[1];
*p++ = '\\';
strcpy (p, newdir+2);
/* Remove trailing slashes. */
p = result + strlen (result) - 1;
while (p > result+2 && (*p == '/' || *p == '\\'))
*p-- = 0;
}
else if (newdir[strlen (newdir)-1] == '/'
|| newdir[strlen (newdir)-1] == '\\' )
{
result = xstrdup (newdir);
p = result + strlen (result) - 1;
while (p > result
&& (*p == '/' || *p == '\\')
&& (p-1 > result && p[-1] != ':')) /* We keep "c:/". */
*p-- = 0;
}
/* Hack to mitigate badly doubled backslashes. */
s = result? result : newdir;
if (s[0] == '\\' && s[1] == '\\' && s[2] != '\\')
{
/* UNC (\\Servername\file) or Long UNC (\\?\Servername\file)
* Does not seem to be double quoted. */
}
else if (strstr (s, "\\\\"))
{
/* Double quotes detected. Fold them into one because that is
* what what Windows does. This way we get a unique hash
* regardless of the number of doubled backslashes. */
if (!result)
result = xstrdup (newdir);
for (p0=p=result; *p; p++)
{
*p0++ = *p;
while (*p == '\\' && p[1] == '\\')
p++;
}
*p0 = 0;
}
#else /*!HAVE_W32_SYSTEM*/
if (newdir[strlen (newdir)-1] == '/')
{
result = xstrdup (newdir);
p = result + strlen (result) - 1;
while (p > result && *p == '/')
*p-- = 0;
}
#endif /*!HAVE_W32_SYSTEM*/
return result;
}
/* Get the standard home directory. In general this function should
not be used as it does not consider a registry value (under W32) or
the GNUPGHOME environment variable. It is better to use
gnupg_homedir(). */
const char *
standard_homedir (void)
{
#ifdef HAVE_W32_SYSTEM
static const char *dir;
if (!dir)
{
const char *rdir;
rdir = w32_rootdir ();
if (w32_portable_app)
{
dir = xstrconcat (rdir, DIRSEP_S "home", NULL);
gpgrt_annotate_leaked_object (dir);
}
else
{
char *path;
path = w32_shgetfolderpath (NULL, CSIDL_APPDATA|CSIDL_FLAG_CREATE,
NULL, 0);
if (path)
{
dir = xstrconcat (path, "\\", my_gnupg_dirname (), NULL);
xfree (path);
gpgrt_annotate_leaked_object (dir);
/* Try to create the directory if it does not yet exists. */
if (gnupg_access (dir, F_OK))
if (!gnupg_mkdir (dir, "-rwx"))
create_common_conf (dir);
}
else
dir = my_fixed_default_homedir ();
}
}
return dir;
#else/*!HAVE_W32_SYSTEM*/
return my_fixed_default_homedir ();
#endif /*!HAVE_W32_SYSTEM*/
}
/* Set up the default home directory. The usual --homedir option
should be parsed later. */
static const char *
default_homedir (void)
{
const char *dir;
#ifdef HAVE_W32_SYSTEM
/* For a portable application we only use the standard homedir. */
w32_rootdir ();
if (w32_portable_app)
return standard_homedir ();
#endif /*HAVE_W32_SYSTEM*/
dir = getenv ("GNUPGHOME");
#ifdef HAVE_W32_SYSTEM
if (!dir || !*dir)
{
static const char *saved_dir;
if (!saved_dir)
{
if (!dir || !*dir)
{
char *tmp, *p;
/* This is deprecated; gpgconf --list-dirs prints a
* warning if the homedir has been taken from the
* registry. */
tmp = read_w32_registry_string (NULL,
gnupg_registry_dir (),
"HomeDir");
if (tmp && !*tmp)
{
xfree (tmp);
tmp = NULL;
}
if (tmp)
{
/* Strip trailing backslashes. */
p = tmp + strlen (tmp) - 1;
while (p > tmp && *p == '\\')
*p-- = 0;
saved_dir = tmp;
}
}
if (!saved_dir)
saved_dir = standard_homedir ();
}
dir = saved_dir;
}
#endif /*HAVE_W32_SYSTEM*/
if (!dir || !*dir)
dir = my_fixed_default_homedir ();
else
{
char *p;
p = copy_dir_with_fixup (dir);
if (p)
{
/* A new buffer has been allocated with proper semantics.
* Assign this to DIR. If DIR is passed again to
* copy_dir_with_fixup there will be no need for a fix up
* and the function returns NULL. Thus we leak only once.
* Setting the homedir is usually a one-off task but might
* be called a second time. We also ignore such extra leaks
* because we don't know who still references the former
* string. */
gpgrt_annotate_leaked_object (p);
dir = p;
}
if (!is_gnupg_default_homedir (dir))
non_default_homedir = 1;
}
return dir;
}
/* Return true if S can be inteprtated as true. This is uised for
* keywords in gpgconf.ctl. Spaces must have been trimmed. */
static int
string_is_true (const char *s)
{
return (atoi (s)
|| !ascii_strcasecmp (s, "yes")
|| !ascii_strcasecmp (s, "true")
|| !ascii_strcasecmp (s, "fact"));
}
/* This function is used to parse the gpgconf.ctl file and set the
* information ito the gpgconf_ctl structure. This is called once
* with the full filename of gpgconf.ctl. There are two callers: One
* used on Windows and one on Unix. No error return but diagnostics
* are printed. */
static void
parse_gpgconf_ctl (const char *fname)
{
gpg_error_t err;
char *p;
char *line;
size_t linelen;
ssize_t length;
estream_t fp;
const char *name;
int anyitem = 0;
int ignoreall = 0;
char *gnupgval = NULL;
char *rootdir = NULL;
char *sysconfdir = NULL;
char *socketdir = NULL;
if (gpgconf_ctl.checked)
return; /* Just in case this is called a second time. */
gpgconf_ctl.checked = 1;
gpgconf_ctl.found = 0;
gpgconf_ctl.valid = 0;
gpgconf_ctl.empty = 0;
if (gnupg_access (fname, F_OK))
return; /* No gpgconf.ctl file. */
/* log_info ("detected '%s'\n", buffer); */
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_info ("error opening '%s': %s\n", fname, gpg_strerror (err));
return;
}
gpgconf_ctl.found = 1;
line = NULL;
linelen = 0;
while ((length = es_read_line (fp, &line, &linelen, NULL)) > 0)
{
static const char *names[] =
{
"gnupg",
"rootdir",
"sysconfdir",
"socketdir",
"portable",
".enable"
};
int i;
size_t n;
/* Strip NL and CR, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = 0;
trim_spaces (line);
if (*line == '#' || !*line)
continue;
anyitem = 1;
/* Find the keyword. */
name = NULL;
p = NULL;
for (i=0; i < DIM (names); i++)
{
n = strlen (names[i]);
if (!strncmp (line, names[i], n))
{
while (line[n] == ' ' || line[n] == '\t')
n++;
if (line[n] == '=')
{
name = names[i];
p = line + n + 1;
break;
}
}
}
if (!name)
continue; /* Keyword not known. */
trim_spaces (p);
p = substitute_envvars (p);
if (!p)
{
err = gpg_error_from_syserror ();
log_info ("error getting %s from gpgconf.ctl: %s\n",
name, gpg_strerror (err));
}
else if (!strcmp (name, ".enable"))
{
if (string_is_true (p))
; /* Yes, this file shall be used. */
else
ignoreall = 1; /* No, this file shall be ignored. */
xfree (p);
}
else if (!strcmp (name, "gnupg"))
{
xfree (gnupgval);
gnupgval = p;
}
else if (!strcmp (name, "sysconfdir"))
{
xfree (sysconfdir);
sysconfdir = p;
}
else if (!strcmp (name, "socketdir"))
{
xfree (socketdir);
socketdir = p;
}
else if (!strcmp (name, "rootdir"))
{
xfree (rootdir);
rootdir = p;
}
else if (!strcmp (name, "portable"))
{
gpgconf_ctl.portable = string_is_true (p);
xfree (p);
}
else /* Unknown keyword. */
xfree (p);
}
if (es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_info ("error reading '%s': %s\n", fname, gpg_strerror (err));
ignoreall = 1; /* Force all entries to invalid. */
}
es_fclose (fp);
xfree (line);
if (ignoreall)
; /* Forced error. Note that .found is still set. */
else if (gnupgval && (!*gnupgval || strpbrk (gnupgval, "/\\")))
{
/* We don't allow a slash or backslash in the value because our
* code assumes this is a single directory name. */
log_info ("invalid %s '%s' specified in gpgconf.ctl\n",
"gnupg", gnupgval);
}
else if (rootdir && (!*rootdir || *rootdir != '/'))
{
log_info ("invalid %s '%s' specified in gpgconf.ctl\n",
"rootdir", rootdir);
}
else if (sysconfdir && (!*sysconfdir || *sysconfdir != '/'))
{
log_info ("invalid %s '%s' specified in gpgconf.ctl\n",
"sysconfdir", sysconfdir);
}
else if (socketdir && (!*socketdir || *socketdir != '/'))
{
log_info ("invalid %s '%s' specified in gpgconf.ctl\n",
"socketdir", socketdir);
}
else
{
if (gnupgval)
{
gpgconf_ctl.gnupg = gnupgval;
gpgrt_annotate_leaked_object (gpgconf_ctl.gnupg);
/* log_info ("want gnupg '%s'\n", dir); */
}
if (rootdir)
{
while (*rootdir && rootdir[strlen (rootdir)-1] == '/')
rootdir[strlen (rootdir)-1] = 0;
gpgconf_ctl.rootdir = rootdir;
gpgrt_annotate_leaked_object (gpgconf_ctl.rootdir);
/* log_info ("want rootdir '%s'\n", dir); */
}
if (sysconfdir)
{
while (*sysconfdir && sysconfdir[strlen (sysconfdir)-1] == '/')
sysconfdir[strlen (sysconfdir)-1] = 0;
gpgconf_ctl.sysconfdir = sysconfdir;
gpgrt_annotate_leaked_object (gpgconf_ctl.sysconfdir);
/* log_info ("want sysconfdir '%s'\n", sdir); */
}
if (socketdir)
{
while (*socketdir && socketdir[strlen (socketdir)-1] == '/')
socketdir[strlen (socketdir)-1] = 0;
gpgconf_ctl.socketdir = socketdir;
gpgrt_annotate_leaked_object (gpgconf_ctl.socketdir);
/* log_info ("want socketdir '%s'\n", s2dir); */
}
gpgconf_ctl.valid = 1;
}
gpgconf_ctl.empty = !anyitem;
if (!gpgconf_ctl.valid)
{
/* Error reading some entries - clear them all. */
xfree (gnupgval);
xfree (rootdir);
xfree (sysconfdir);
xfree (socketdir);
gpgconf_ctl.gnupg = NULL;
gpgconf_ctl.rootdir = NULL;
gpgconf_ctl.sysconfdir = NULL;
gpgconf_ctl.socketdir = NULL;
}
}
#ifdef HAVE_W32_SYSTEM
/* Check whether gpgconf is installed and if so read the gpgconf.ctl
file. */
static void
check_portable_app (const char *dir)
{
char *fname;
fname = xstrconcat (dir, DIRSEP_S "gpgconf.exe", NULL);
if (!gnupg_access (fname, F_OK))
{
strcpy (fname + strlen (fname) - 3, "ctl");
parse_gpgconf_ctl (fname);
if ((gpgconf_ctl.found && gpgconf_ctl.empty)
|| (gpgconf_ctl.valid && gpgconf_ctl.portable))
{
unsigned int flags;
/* Classic gpgconf.ctl file found. This is a portable
* application. Note that if there are any items in that
* file we don't consider this a portable application unless
* the (later added) ".portable" keyword has also been
* seen. */
w32_portable_app = 1;
log_get_prefix (&flags);
log_set_prefix (NULL, (flags | GPGRT_LOG_NO_REGISTRY));
}
}
xfree (fname);
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HAVE_W32_SYSTEM
/* Determine the root directory of the gnupg installation on Windows. */
static const char *
w32_rootdir (void)
{
static int got_dir;
static char dir[MAX_PATH+5];
if (!got_dir)
{
char *p;
int rc;
wchar_t wdir [MAX_PATH+5];
rc = GetModuleFileNameW (NULL, wdir, MAX_PATH);
if (rc && WideCharToMultiByte (CP_UTF8, 0, wdir, -1, dir, MAX_PATH-4,
NULL, NULL) < 0)
rc = 0;
if (!rc)
{
log_debug ("GetModuleFileName failed: %s\n", w32_strerror (-1));
*dir = 0;
}
got_dir = 1;
p = strrchr (dir, DIRSEP_C);
if (p)
{
*p = 0;
check_portable_app (dir);
/* If we are installed below "bin" we strip that and use
the top directory instead. */
p = strrchr (dir, DIRSEP_C);
if (p && !strcmp (p+1, "bin"))
{
*p = 0;
w32_bin_is_bin = 1;
}
}
if (!p)
{
log_debug ("bad filename '%s' returned for this process\n", dir);
*dir = 0;
}
}
if (*dir)
return dir;
/* Fallback to the hardwired value. */
return GNUPG_LIBEXECDIR;
}
#endif /*HAVE_W32_SYSTEM*/
#ifndef HAVE_W32_SYSTEM /* Unix */
/* Determine the root directory of the gnupg installation on Unix.
* The standard case is that this function returns NULL so that the
* root directory as configured at build time is used. However, it
* may return a static string with a different root directory, similar
* to what we do on Windows. That second mode is triggered by the
* existence of a file gpgconf.ctl installed side-by-side to gpgconf.
* This file is parsed for keywords describing the actually to be used
* root directory. There is no solid standard on Unix to locate the
* binary used to create the process, thus we support this currently
* only on Linux and BSD where we can look this info up using the proc
* file system. If WANT_SYSCONFDIR is true the optional sysconfdir
* entry is returned. */
static const char *
unix_rootdir (enum wantdir_values wantdir)
{
if (!gpgconf_ctl.checked)
{
char *p;
char *buffer;
size_t bufsize = 256-1;
int nread;
gpg_error_t err;
const char *name;
for (;;)
{
buffer = xmalloc (bufsize+1);
nread = readlink (MYPROC_SELF_EXE, buffer, bufsize);
if (nread < 0)
{
err = gpg_error_from_syserror ();
buffer[0] = 0;
if ((name = getenv ("GNUPG_BUILD_ROOT")) && *name == '/')
{
/* Try a fallback for systems w/o a supported /proc
* file system if we are running a regression test. */
log_info ("error reading symlink '%s': %s\n",
MYPROC_SELF_EXE, gpg_strerror (err));
xfree (buffer);
buffer = xstrconcat (name, "/bin/gpgconf", NULL);
log_info ("trying fallback '%s'\n", buffer);
}
break;
}
else if (nread < bufsize)
{
buffer[nread] = 0;
break; /* Got it. */
}
else if (bufsize >= 4095)
{
buffer[0] = 0;
log_info ("error reading symlink '%s': %s\n",
MYPROC_SELF_EXE, "value too large");
break;
}
xfree (buffer);
bufsize += 256;
}
if (!*buffer)
{
xfree (buffer);
gpgconf_ctl.checked = 1;
return NULL; /* Error - assume no gpgconf.ctl. */
}
p = strrchr (buffer, '/');
if (!p)
{
xfree (buffer);
gpgconf_ctl.checked = 1;
return NULL; /* Erroneous /proc - assume no gpgconf.ctl. */
}
*p = 0; /* BUFFER has the directory. */
if (!(p = strrchr (buffer, '/')))
{
/* Installed in the root which is not a good idea. Assume
* no gpgconf.ctl. */
xfree (buffer);
gpgconf_ctl.checked = 1;
return NULL;
}
/* Strip one part and expect the file below a bin dir. */
*p = 0;
p = xstrconcat (buffer, "/bin/gpgconf.ctl", NULL);
xfree (buffer);
buffer = p;
parse_gpgconf_ctl (buffer);
xfree (buffer);
}
if (!gpgconf_ctl.valid)
return NULL; /* No valid entries in gpgconf.ctl */
switch (wantdir)
{
case WANTDIR_ROOT: return gpgconf_ctl.rootdir;
case WANTDIR_SYSCONF: return gpgconf_ctl.sysconfdir;
case WANTDIR_SOCKET: return gpgconf_ctl.socketdir;
}
return NULL; /* Not reached. */
}
#endif /* Unix */
#ifdef HAVE_W32_SYSTEM
static const char *
w32_commondir (void)
{
static char *dir;
if (!dir)
{
const char *rdir;
char *path;
/* Make sure that w32_rootdir has been called so that we are
able to check the portable application flag. The common dir
is the identical to the rootdir. In that case there is also
no need to strdup its value. */
rdir = w32_rootdir ();
if (w32_portable_app)
return rdir;
path = w32_shgetfolderpath (NULL, CSIDL_COMMON_APPDATA, NULL, 0);
if (path)
{
dir = xstrconcat (path, "\\GNU", NULL);
/* No auto create of the directory. Either the installer or
* the admin has to create these directories. */
}
else
{
/* Folder not found or defined - probably an old Windows
* version. Use the installation directory instead. */
dir = xstrdup (rdir);
}
gpgrt_annotate_leaked_object (dir);
}
return dir;
}
#endif /*HAVE_W32_SYSTEM*/
/* Change the homedir. Some care must be taken to set this early
* enough because previous calls to gnupg_homedir may else return a
* different string. */
void
gnupg_set_homedir (const char *newdir)
{
char *tmp = NULL;
if (!newdir || !*newdir)
newdir = default_homedir ();
else
{
tmp = copy_dir_with_fixup (newdir);
if (tmp)
newdir = tmp;
if (!is_gnupg_default_homedir (newdir))
non_default_homedir = 1;
}
xfree (the_gnupg_homedir);
the_gnupg_homedir = make_absfilename (newdir, NULL);;
xfree (tmp);
/* Fixme: Should we use
* gpgrt_annotate_leaked_object(the_gnupg_homedir)
* despite that we may free and allocate a new one in some
* cases? */
}
/* Create the homedir directory only if the supplied directory name is
* the same as the default one. This way we avoid to create arbitrary
* directories when a non-default home directory is used. To cope
* with HOME, we do compare only the suffix if we see that the default
* homedir does start with a tilde. If the mkdir fails the function
* terminates the process. If QUIET is set not diagnostic is printed
* on homedir creation. */
void
gnupg_maybe_make_homedir (const char *fname, int quiet)
{
const char *defhome = standard_homedir ();
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (fname, defhome) )
#else
( *defhome == '~'
&& (strlen(fname) >= strlen (defhome+1)
&& !strcmp(fname+strlen(fname)-strlen(defhome+1), defhome+1 ) ))
|| (*defhome != '~' && !compare_filenames( fname, defhome ) )
#endif
)
{
if (gnupg_mkdir (fname, "-rwx"))
log_fatal ( _("can't create directory '%s': %s\n"),
fname, strerror(errno) );
else
{
if (!quiet )
log_info ( _("directory '%s' created\n"), fname );
create_common_conf (fname);
}
}
}
/* Return the homedir. The returned string is valid until another
* gnupg-set-homedir call. This is always an absolute directory name.
* The function replaces the former global var opt.homedir. */
const char *
gnupg_homedir (void)
{
/* If a homedir has not been set, set it to the default. */
if (!the_gnupg_homedir)
the_gnupg_homedir = make_absfilename (default_homedir (), NULL);
return the_gnupg_homedir;
}
/* Return whether the home dir is the default one. */
int
gnupg_default_homedir_p (void)
{
return !non_default_homedir;
}
/* Return the directory name used by daemons for their current working
* directory. */
const char *
gnupg_daemon_rootdir (void)
{
#ifdef HAVE_W32_SYSTEM
static char *name;
if (!name)
{
char path[MAX_PATH];
size_t n;
n = GetSystemDirectoryA (path, sizeof path);
if (!n || n >= sizeof path)
- name = xstrdup ("/"); /* Error - use the curret top dir instead. */
+ name = xstrdup ("/"); /* Error - use the current top dir instead. */
else
name = xstrdup (path);
gpgrt_annotate_leaked_object (name);
}
return name;
#else /*!HAVE_W32_SYSTEM*/
return "/";
#endif /*!HAVE_W32_SYSTEM*/
}
/* Helper for gnupg-socketdir. This is a global function, so that
* gpgconf can use it for its --create-socketdir command. If
* SKIP_CHECKS is set permission checks etc. are not done. The
* function always returns a malloced directory name and stores these
* bit flags at R_INFO:
*
* 1 := Internal error, stat failed, out of core, etc.
* 2 := No /run/user directory.
* 4 := Directory not owned by the user, not a directory
* or wrong permissions.
* 8 := Same as 4 but for the subdir.
* 16 := mkdir failed
* 32 := Non default homedir; checking subdir.
* 64 := Subdir does not exist.
* 128 := Using homedir as fallback.
*/
char *
_gnupg_socketdir_internal (int skip_checks, unsigned *r_info)
{
#if defined(HAVE_W32_SYSTEM)
char *name;
(void)skip_checks;
*r_info = 0;
/* First make sure that non_default_homedir and w32_portable_app can
* be set. */
gnupg_homedir ();
if (w32_portable_app)
{
name = xstrconcat (w32_rootdir (), DIRSEP_S, my_gnupg_dirname (), NULL);
}
else
{
char *path;
path = w32_shgetfolderpath (NULL,
CSIDL_LOCAL_APPDATA|CSIDL_FLAG_CREATE,
NULL, 0);
if (path)
{
name = xstrconcat (path, "\\", my_gnupg_dirname (), NULL);
xfree (path);
if (gnupg_access (name, F_OK))
gnupg_mkdir (name, "-rwx");
}
else
{
name = xstrdup (gnupg_homedir ());
}
}
/* If a non default homedir is used, we check whether an
* corresponding sub directory below the socket dir is available
* and use that. We hash the non default homedir to keep the new
* subdir short enough. */
if (non_default_homedir)
{
char sha1buf[20];
struct stat sb;
char *suffix;
char *p;
*r_info |= 32; /* Testing subdir. */
/* Canonicalize the name to avoid problems with mixed case
* names. Note that we use only 10 bytes of the hash because on
* Windows the account name is also part of the name. */
suffix = ascii_strlwr (xstrdup (gnupg_homedir ()));
for (p=suffix; *p; p++)
if ( *p == '\\')
*p = '/';
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, suffix, strlen (suffix));
xfree (suffix);
suffix = zb32_encode (sha1buf, 8*10);
if (!suffix)
{
*r_info |= 1; /* Out of core etc. */
goto leave_w32;
}
p = xstrconcat (name, "\\d.", suffix, NULL);
xfree (suffix);
xfree (name);
name = p;
/* Stat that directory and check constraints.
* The command
* gpgconf --remove-socketdir
* can be used to remove that directory. */
if (gnupg_stat (name, &sb))
{
if (errno != ENOENT)
*r_info |= 1; /* stat failed. */
else if (!skip_checks)
{
/* Try to create the directory and check again. */
if (gnupg_mkdir (name, "-rwx"))
*r_info |= 16; /* mkdir failed. */
else if (gnupg_stat (name, &sb))
{
if (errno != ENOENT)
*r_info |= 1; /* stat failed. */
else
*r_info |= 64; /* Subdir does not exist. */
}
else
goto leave_w32; /* Success! */
}
else
*r_info |= 64; /* Subdir does not exist. */
if (!skip_checks)
{
xfree (name);
name = NULL;
goto leave_w32;
}
}
}
leave_w32:
/* If nothing works - fall back to the homedir. */
if (!name)
{
*r_info |= 128; /* Fallback. */
name = xstrdup (gnupg_homedir ());
}
#elif !defined(HAVE_STAT)
char *name;
(void)skip_checks;
*r_info = 0;
name = xstrdup (gnupg_homedir ());
#else /* Unix and stat(2) available. */
static const char * const bases[] = {
#ifdef USE_RUN_GNUPG_USER_SOCKET
"/run/gnupg",
#endif
"/run",
#ifdef USE_RUN_GNUPG_USER_SOCKET
"/var/run/gnupg",
#endif
"/var/run",
NULL
};
int i;
struct stat sb;
char prefixbuffer[256];
const char *prefix;
const char *s;
char *name = NULL;
const char *gnupgname = my_gnupg_dirname ();
*r_info = 0;
/* First make sure that non_default_homedir can be set. */
gnupg_homedir ();
/* It has been suggested to first check XDG_RUNTIME_DIR envvar.
* However, the specs state that the lifetime of the directory MUST
* be bound to the user being logged in. Now GnuPG may also be run
* as a background process with no (desktop) user logged in. Thus
* we better don't do that. */
prefix = unix_rootdir (WANTDIR_SOCKET);
if (!prefix)
{
/* gpgconf.ctl does not specify a directory. Check whether we
* have the usual /run/[gnupg/]user dir. */
for (i=0; bases[i]; i++)
{
snprintf (prefixbuffer, sizeof prefixbuffer, "%s/user/%u",
bases[i], (unsigned int)getuid ());
prefix = prefixbuffer;
if (!stat (prefix, &sb) && S_ISDIR(sb.st_mode))
break;
}
if (!bases[i])
{
*r_info |= 2; /* No /run/user directory. */
goto leave;
}
if (sb.st_uid != getuid ())
{
*r_info |= 4; /* Not owned by the user. */
if (!skip_checks)
goto leave;
}
if (strlen (prefix) + strlen (gnupgname) + 2 >= sizeof prefixbuffer)
{
*r_info |= 1; /* Ooops: Buffer too short to append "/gnupg". */
goto leave;
}
strcat (prefixbuffer, "/");
strcat (prefixbuffer, gnupgname);
}
- /* Check whether the gnupg sub directory (or the specified diretory)
+ /* Check whether the gnupg sub directory (or the specified directory)
* has proper permissions. */
if (stat (prefix, &sb))
{
if (errno != ENOENT)
{
*r_info |= 1; /* stat failed. */
goto leave;
}
/* Try to create the directory and check again. */
if (gnupg_mkdir (prefix, "-rwx"))
{
*r_info |= 16; /* mkdir failed. */
goto leave;
}
if (stat (prefix, &sb))
{
*r_info |= 1; /* stat failed. */
goto leave;
}
}
/* Check that it is a directory, owned by the user, and only the
* user has permissions to use it. */
if (!S_ISDIR(sb.st_mode)
|| sb.st_uid != getuid ()
|| (sb.st_mode & (S_IRWXG|S_IRWXO)))
{
*r_info |= 4; /* Bad permissions or not a directory. */
if (!skip_checks)
goto leave;
}
/* If a non default homedir is used, we check whether an
* corresponding sub directory below the socket dir is available
* and use that. We hash the non default homedir to keep the new
* subdir short enough. */
if (non_default_homedir)
{
char sha1buf[20];
char *suffix;
*r_info |= 32; /* Testing subdir. */
s = gnupg_homedir ();
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, s, strlen (s));
suffix = zb32_encode (sha1buf, 8*15);
if (!suffix)
{
*r_info |= 1; /* Out of core etc. */
goto leave;
}
name = strconcat (prefix, "/d.", suffix, NULL);
xfree (suffix);
if (!name)
{
*r_info |= 1; /* Out of core etc. */
goto leave;
}
/* Stat that directory and check constraints.
* The command
* gpgconf --remove-socketdir
* can be used to remove that directory. */
if (stat (name, &sb))
{
if (errno != ENOENT)
*r_info |= 1; /* stat failed. */
else if (!skip_checks)
{
/* Try to create the directory and check again. */
if (gnupg_mkdir (name, "-rwx"))
*r_info |= 16; /* mkdir failed. */
else if (stat (prefix, &sb))
{
if (errno != ENOENT)
*r_info |= 1; /* stat failed. */
else
*r_info |= 64; /* Subdir does not exist. */
}
else
goto leave; /* Success! */
}
else
*r_info |= 64; /* Subdir does not exist. */
if (!skip_checks)
{
xfree (name);
name = NULL;
goto leave;
}
}
else if (!S_ISDIR(sb.st_mode)
|| sb.st_uid != getuid ()
|| (sb.st_mode & (S_IRWXG|S_IRWXO)))
{
*r_info |= 8; /* Bad permissions or subdir is not a directory. */
if (!skip_checks)
{
xfree (name);
name = NULL;
goto leave;
}
}
}
else
name = xstrdup (prefix);
leave:
/* If nothing works fall back to the homedir. */
if (!name)
{
*r_info |= 128; /* Fallback. */
name = xstrdup (gnupg_homedir ());
}
#endif /* Unix */
return name;
}
/*
* Return the name of the socket dir. That is the directory used for
* the IPC local sockets. This is an absolute directory name.
*/
const char *
gnupg_socketdir (void)
{
static char *name;
if (!name)
{
unsigned int dummy;
name = _gnupg_socketdir_internal (0, &dummy);
gpgrt_annotate_leaked_object (name);
}
return name;
}
/* Return the name of the sysconfdir. This is a static string. This
function is required because under Windows we can't simply compile
it in. */
const char *
gnupg_sysconfdir (void)
{
#ifdef HAVE_W32_SYSTEM
static char *name;
if (!name)
{
name = xstrconcat (w32_commondir (), DIRSEP_S, "etc", DIRSEP_S,
my_gnupg_dirname (), NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
#else /*!HAVE_W32_SYSTEM*/
const char *dir = unix_rootdir (WANTDIR_SYSCONF);
if (dir)
return dir;
else
return GNUPG_SYSCONFDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_bindir (void)
{
static char *name;
const char *rdir;
#if defined(HAVE_W32_SYSTEM)
rdir = w32_rootdir ();
if (w32_bin_is_bin)
{
if (!name)
{
name = xstrconcat (rdir, DIRSEP_S "bin", NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
else
return rdir;
#else /*!HAVE_W32_SYSTEM*/
rdir = unix_rootdir (WANTDIR_ROOT);
if (rdir)
{
if (!name)
{
name = xstrconcat (rdir, DIRSEP_S "bin", NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
else
return GNUPG_BINDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Return the name of the libexec directory. The name is allocated in
a static area on the first use. This function won't fail. */
const char *
gnupg_libexecdir (void)
{
#ifdef HAVE_W32_SYSTEM
return gnupg_bindir ();
#else /*!HAVE_W32_SYSTEM*/
static char *name;
const char *rdir;
rdir = unix_rootdir (WANTDIR_ROOT);
if (rdir)
{
if (!name)
{
name = xstrconcat (rdir, DIRSEP_S "libexec", NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
else
return GNUPG_LIBEXECDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_libdir (void)
{
static char *name;
#ifdef HAVE_W32_SYSTEM
if (!name)
{
name = xstrconcat (w32_rootdir (), DIRSEP_S "lib" DIRSEP_S "gnupg", NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
#else /*!HAVE_W32_SYSTEM*/
const char *rdir;
rdir = unix_rootdir (WANTDIR_ROOT);
if (rdir)
{
if (!name)
{
name = xstrconcat (rdir, DIRSEP_S "lib", DIRSEP_S, "gnupg", NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
else
return GNUPG_LIBDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_datadir (void)
{
static char *name;
#ifdef HAVE_W32_SYSTEM
if (!name)
{
name = xstrconcat (w32_rootdir (), DIRSEP_S "share" DIRSEP_S "gnupg",
NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
#else /*!HAVE_W32_SYSTEM*/
const char *rdir;
rdir = unix_rootdir (WANTDIR_ROOT);
if (rdir)
{
if (!name)
{
name = xstrconcat (rdir, DIRSEP_S "share" DIRSEP_S "gnupg", NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
else
return GNUPG_DATADIR;
#endif /*!HAVE_W32_SYSTEM*/
}
const char *
gnupg_localedir (void)
{
static char *name;
#ifdef HAVE_W32_SYSTEM
if (!name)
{
name = xstrconcat (w32_rootdir (), DIRSEP_S "share" DIRSEP_S "locale",
NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
#else /*!HAVE_W32_SYSTEM*/
const char *rdir;
rdir = unix_rootdir (WANTDIR_ROOT);
if (rdir)
{
if (!name)
{
name = xstrconcat (rdir, DIRSEP_S "share" DIRSEP_S "locale", NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
else
return LOCALEDIR;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Return the standard socket name used by gpg-agent. */
const char *
gpg_agent_socket_name (void)
{
static char *name;
if (!name)
{
name = make_filename (gnupg_socketdir (), GPG_AGENT_SOCK_NAME, NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
/* Return the user socket name used by DirMngr. */
const char *
dirmngr_socket_name (void)
{
static char *name;
if (!name)
{
name = make_filename (gnupg_socketdir (), DIRMNGR_SOCK_NAME, NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
/* Return the user socket name used by Keyboxd. */
const char *
keyboxd_socket_name (void)
{
static char *name;
if (!name)
{
name = make_filename (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
gpgrt_annotate_leaked_object (name);
}
return name;
}
/* Return the default pinentry name. If RESET is true the internal
cache is first flushed. */
static const char *
get_default_pinentry_name (int reset)
{
static struct {
const char *(*rfnc)(void);
const char *name;
} names[] = {
/* The first entry is what we return in case we found no
other pinentry. */
{ gnupg_bindir, DIRSEP_S "pinentry" EXEEXT_S },
#ifdef HAVE_W32_SYSTEM
/* Try Gpg4win directory (with bin and without.) */
{ w32_rootdir, "\\..\\Gpg4win\\bin\\pinentry.exe" },
{ w32_rootdir, "\\..\\Gpg4win\\pinentry.exe" },
/* Try a pinentry in a dir above us */
{ w32_rootdir, "\\..\\bin\\pinentry.exe" },
/* Try old Gpgwin directory. */
{ w32_rootdir, "\\..\\GNU\\GnuPG\\pinentry.exe" },
/* Try a Pinentry from the common GNU dir. */
{ w32_rootdir, "\\..\\GNU\\bin\\pinentry.exe" },
#endif
/* Last chance is a pinentry-basic (which comes with the
GnuPG 2.1 Windows installer). */
{ gnupg_bindir, DIRSEP_S "pinentry-basic" EXEEXT_S }
};
static char *name;
if (reset)
{
xfree (name);
name = NULL;
}
if (!name)
{
int i;
for (i=0; i < DIM(names); i++)
{
char *name2;
name2 = xstrconcat (names[i].rfnc (), names[i].name, NULL);
if (!gnupg_access (name2, F_OK))
{
/* Use that pinentry. */
xfree (name);
name = name2;
break;
}
if (!i) /* Store the first as fallback return. */
name = name2;
else
xfree (name2);
}
if (name)
gpgrt_annotate_leaked_object (name);
}
return name;
}
/* If set, 'gnupg_module_name' returns modules from that build
* directory. */
static char *gnupg_build_directory;
/* For sanity checks. */
static int gnupg_module_name_called;
/* Set NEWDIR as the new build directory. This will make
* 'gnupg_module_name' return modules from that build directory. Must
* be called before any invocation of 'gnupg_module_name', and must
* not be called twice. It can be used by test suites to make sure
* the components from the build directory are used instead of
* potentially outdated installed ones.
* Fixme: It might be better to make use of the newer gpgconf.ctl feature
* for regression testing.
*/
void
gnupg_set_builddir (const char *newdir)
{
log_assert (! gnupg_module_name_called);
log_assert (! gnupg_build_directory);
gnupg_build_directory = xtrystrdup (newdir);
}
/* If no build directory has been configured, try to set it from the
* environment. We only do this in development builds to avoid
* increasing the set of influential environment variables and hence
* the attack surface of production builds. */
static void
gnupg_set_builddir_from_env (void)
{
#if defined(IS_DEVELOPMENT_VERSION) || defined(ENABLE_GNUPG_BUILDDIR_ENVVAR)
if (gnupg_build_directory)
return;
gnupg_build_directory = getenv ("GNUPG_BUILDDIR");
#endif
}
/* Return the file name of a helper tool. WHICH is one of the
GNUPG_MODULE_NAME_foo constants. */
const char *
gnupg_module_name (int which)
{
gnupg_set_builddir_from_env ();
gnupg_module_name_called = 1;
#define X(a,b,c) do { \
static char *name; \
if (!name) { \
name = gnupg_build_directory \
? xstrconcat (gnupg_build_directory, \
DIRSEP_S b DIRSEP_S c EXEEXT_S, NULL) \
: xstrconcat (gnupg_ ## a (), DIRSEP_S c EXEEXT_S, NULL); \
gpgrt_annotate_leaked_object (name); \
} \
return name; \
} while (0)
switch (which)
{
case GNUPG_MODULE_NAME_AGENT:
#ifdef GNUPG_DEFAULT_AGENT
return GNUPG_DEFAULT_AGENT;
#else
X(bindir, "agent", "gpg-agent");
#endif
case GNUPG_MODULE_NAME_PINENTRY:
#ifdef GNUPG_DEFAULT_PINENTRY
return GNUPG_DEFAULT_PINENTRY; /* (Set by a configure option) */
#else
return get_default_pinentry_name (0);
#endif
case GNUPG_MODULE_NAME_SCDAEMON:
#ifdef GNUPG_DEFAULT_SCDAEMON
return GNUPG_DEFAULT_SCDAEMON;
#else
X(libexecdir, "scd", "scdaemon");
#endif
case GNUPG_MODULE_NAME_TPM2DAEMON:
#ifdef GNUPG_DEFAULT_TPM2DAEMON
return GNUPG_DEFAULT_TPM2DAEMON;
#else
X(libexecdir, "tpm2d", TPM2DAEMON_NAME);
#endif
case GNUPG_MODULE_NAME_DIRMNGR:
#ifdef GNUPG_DEFAULT_DIRMNGR
return GNUPG_DEFAULT_DIRMNGR;
#else
X(bindir, "dirmngr", DIRMNGR_NAME);
#endif
case GNUPG_MODULE_NAME_KEYBOXD:
#ifdef GNUPG_DEFAULT_KEYBOXD
return GNUPG_DEFAULT_KEYBOXD;
#else
X(libexecdir, "kbx", KEYBOXD_NAME);
#endif
case GNUPG_MODULE_NAME_PROTECT_TOOL:
#ifdef GNUPG_DEFAULT_PROTECT_TOOL
return GNUPG_DEFAULT_PROTECT_TOOL;
#else
X(libexecdir, "agent", "gpg-protect-tool");
#endif
case GNUPG_MODULE_NAME_DIRMNGR_LDAP:
#ifdef GNUPG_DEFAULT_DIRMNGR_LDAP
return GNUPG_DEFAULT_DIRMNGR_LDAP;
#else
X(libexecdir, "dirmngr", "dirmngr_ldap");
#endif
case GNUPG_MODULE_NAME_CHECK_PATTERN:
X(libexecdir, "tools", "gpg-check-pattern");
case GNUPG_MODULE_NAME_GPGSM:
X(bindir, "sm", "gpgsm");
case GNUPG_MODULE_NAME_GPG:
#if USE_GPG2_HACK
if (! gnupg_build_directory)
X(bindir, "g10", GPG_NAME "2");
else
#endif
X(bindir, "g10", GPG_NAME);
case GNUPG_MODULE_NAME_GPGV:
#if USE_GPG2_HACK
if (! gnupg_build_directory)
X(bindir, "g10", GPG_NAME "v2");
else
#endif
X(bindir, "g10", GPG_NAME "v");
case GNUPG_MODULE_NAME_CONNECT_AGENT:
X(bindir, "tools", "gpg-connect-agent");
case GNUPG_MODULE_NAME_GPGCONF:
X(bindir, "tools", "gpgconf");
case GNUPG_MODULE_NAME_CARD:
X(bindir, "tools", "gpg-card");
case GNUPG_MODULE_NAME_GPGTAR:
X(bindir, "tools", "gpgtar");
default:
BUG ();
}
#undef X
}
/* Flush some of the cached module names. This is for example used by
gpg-agent to allow configuring a different pinentry. */
void
gnupg_module_name_flush_some (void)
{
(void)get_default_pinentry_name (1);
}
diff --git a/common/iobuf.c b/common/iobuf.c
index e46eeac95..7aaf3a878 100644
--- a/common/iobuf.c
+++ b/common/iobuf.c
@@ -1,3118 +1,3118 @@
/* iobuf.c - File Handling for OpenPGP.
* Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2006, 2007, 2008,
* 2009, 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later)
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#endif
#ifdef __riscos__
# include <kernel.h>
# include <swis.h>
#endif /* __riscos__ */
#include <assuan.h>
#include "util.h"
#include "sysutils.h"
#include "iobuf.h"
/*-- Begin configurable part. --*/
/* The standard size of the internal buffers. */
#define DEFAULT_IOBUF_BUFFER_SIZE (64*1024)
/* To avoid a potential DoS with compression packets we better limit
the number of filters in a chain. */
#define MAX_NESTING_FILTER 64
/* The threshold for switching to use external buffers directly
instead of the internal buffers. */
#define IOBUF_ZEROCOPY_THRESHOLD_SIZE 1024
/*-- End configurable part. --*/
/* The size of the iobuffers. This can be changed using the
* iobuf_set_buffer_size function. */
static unsigned int iobuf_buffer_size = DEFAULT_IOBUF_BUFFER_SIZE;
#ifdef HAVE_W32_SYSTEM
# define FD_FOR_STDIN (GetStdHandle (STD_INPUT_HANDLE))
# define FD_FOR_STDOUT (GetStdHandle (STD_OUTPUT_HANDLE))
#else /*!HAVE_W32_SYSTEM*/
# define FD_FOR_STDIN (0)
# define FD_FOR_STDOUT (1)
#endif /*!HAVE_W32_SYSTEM*/
/* The context used by the file filter. */
typedef struct
{
gnupg_fd_t fp; /* Open file pointer or handle. */
int keep_open;
int no_cache;
int eof_seen;
int delayed_rc;
int print_only_name; /* Flags indicating that fname is not a real file. */
char peeked[32]; /* Read ahead buffer. */
byte npeeked; /* Number of bytes valid in peeked. */
byte upeeked; /* Number of bytes used from peeked. */
char fname[1]; /* Name of the file. */
} file_filter_ctx_t;
/* The context used by the estream filter. */
typedef struct
{
estream_t fp; /* Open estream handle. */
int keep_open;
int no_cache;
int eof_seen;
int use_readlimit; /* Take care of the readlimit. */
size_t readlimit; /* Number of bytes left to read. */
int print_only_name; /* Flags indicating that fname is not a real file. */
char fname[1]; /* Name of the file. */
} file_es_filter_ctx_t;
/* Object to control the "close cache". */
struct close_cache_s
{
struct close_cache_s *next;
gnupg_fd_t fp;
char fname[1];
};
typedef struct close_cache_s *close_cache_t;
static close_cache_t close_cache;
int iobuf_debug_mode;
#ifdef HAVE_W32_SYSTEM
typedef struct
{
int sock;
int keep_open;
int no_cache;
int eof_seen;
int print_only_name; /* Flag indicating that fname is not a real file. */
char fname[1]; /* Name of the file */
} sock_filter_ctx_t;
#endif /*HAVE_W32_SYSTEM*/
/* The first partial length header block must be of size 512 to make
* it easier (and more efficient) we use a min. block size of 512 for
* all chunks (but the last one) */
#define OP_MIN_PARTIAL_CHUNK 512
#define OP_MIN_PARTIAL_CHUNK_2POW 9
/* The context we use for the block filter (used to handle OpenPGP
length information header). */
typedef struct
{
int use;
size_t size;
size_t count;
int partial; /* 1 = partial header, 2 in last partial packet. */
char *buffer; /* Used for partial header. */
size_t buflen; /* Used size of buffer. */
int first_c; /* First character of a partial header (which is > 0). */
int eof;
}
block_filter_ctx_t;
/* Local prototypes. */
static int underflow (iobuf_t a, int clear_pending_eof);
static int underflow_target (iobuf_t a, int clear_pending_eof, size_t target);
static iobuf_t do_iobuf_fdopen (gnupg_fd_t fp, const char *mode, int keep_open);
/* Sends any pending data to the filter's FILTER function. Note: this
works on the filter and not on the whole pipeline. That is,
iobuf_flush doesn't necessarily cause data to be written to any
underlying file; it just causes any data buffered at the filter A
to be sent to A's filter function.
If A is a IOBUF_OUTPUT_TEMP filter, then this also enlarges the
buffer by iobuf_buffer_size.
May only be called on an IOBUF_OUTPUT or IOBUF_OUTPUT_TEMP filters. */
static int filter_flush (iobuf_t a);
/* This is a replacement for strcmp. Under W32 it does not
distinguish between backslash and slash. */
static int
fd_cache_strcmp (const char *a, const char *b)
{
#ifdef HAVE_DOSISH_SYSTEM
for (; *a && *b; a++, b++)
{
if (*a != *b && !((*a == '/' && *b == '\\')
|| (*a == '\\' && *b == '/')) )
break;
}
return *(const unsigned char *)a - *(const unsigned char *)b;
#else
return strcmp (a, b);
#endif
}
/*
* Invalidate (i.e. close) a cached iobuf
*/
static int
fd_cache_invalidate (const char *fname)
{
close_cache_t cc;
int rc = 0;
log_assert (fname);
if (DBG_IOBUF)
log_debug ("fd_cache_invalidate (%s)\n", fname);
for (cc = close_cache; cc; cc = cc->next)
{
if (cc->fp != GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname))
{
if (DBG_IOBUF)
log_debug (" did (%s)\n", cc->fname);
#ifdef HAVE_W32_SYSTEM
if (!CloseHandle (cc->fp))
rc = -1;
#else
rc = close (cc->fp);
#endif
cc->fp = GNUPG_INVALID_FD;
}
}
return rc;
}
/* Try to sync changes to the disk. This is to avoid data loss during
a system crash in write/close/rename cycle on some file
systems. */
static int
fd_cache_synchronize (const char *fname)
{
int err = 0;
#ifdef HAVE_FSYNC
close_cache_t cc;
if (DBG_IOBUF)
log_debug ("fd_cache_synchronize (%s)\n", fname);
for (cc=close_cache; cc; cc = cc->next )
{
if (cc->fp != GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname))
{
if (DBG_IOBUF)
log_debug (" did (%s)\n", cc->fname);
err = fsync (cc->fp);
}
}
#else
(void)fname;
#endif /*HAVE_FSYNC*/
return err;
}
static gnupg_fd_t
direct_open (const char *fname, const char *mode, int mode700)
{
#ifdef HAVE_W32_SYSTEM
unsigned long da, cd, sm;
HANDLE hfile;
(void)mode700;
/* Note, that we do not handle all mode combinations */
/* According to the ReactOS source it seems that open() of the
* standard MSW32 crt does open the file in shared mode which is
* something new for MS applications ;-)
*/
if (strchr (mode, '+'))
{
if (fd_cache_invalidate (fname))
return GNUPG_INVALID_FD;
da = GENERIC_READ | GENERIC_WRITE;
cd = OPEN_EXISTING;
sm = FILE_SHARE_READ | FILE_SHARE_WRITE;
}
else if (strchr (mode, 'w'))
{
if (fd_cache_invalidate (fname))
return GNUPG_INVALID_FD;
da = GENERIC_WRITE;
cd = CREATE_ALWAYS;
sm = FILE_SHARE_WRITE;
}
else
{
da = GENERIC_READ;
cd = OPEN_EXISTING;
sm = FILE_SHARE_READ;
}
/* We always use the Unicode version because it supports file names
* longer than MAX_PATH. (requires gpgrt 1.45) */
if (1)
{
wchar_t *wfname = gpgrt_fname_to_wchar (fname);
if (wfname)
{
hfile = CreateFileW (wfname, da, sm, NULL, cd,
FILE_ATTRIBUTE_NORMAL, NULL);
if (hfile == INVALID_HANDLE_VALUE)
{
gnupg_w32_set_errno (-1);
if (DBG_IOBUF)
log_debug ("iobuf:direct_open '%s' CreateFile failed: %s\n",
fname, gpg_strerror (gpg_error_from_syserror()));
}
xfree (wfname);
}
else
hfile = INVALID_HANDLE_VALUE;
}
return hfile;
#else /*!HAVE_W32_SYSTEM*/
int oflag;
int cflag = S_IRUSR | S_IWUSR;
if (!mode700)
cflag |= S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
/* Note, that we do not handle all mode combinations */
if (strchr (mode, '+'))
{
if (fd_cache_invalidate (fname))
return GNUPG_INVALID_FD;
oflag = O_RDWR;
}
else if (strchr (mode, 'w'))
{
if (fd_cache_invalidate (fname))
return GNUPG_INVALID_FD;
oflag = O_WRONLY | O_CREAT | O_TRUNC;
}
else
{
oflag = O_RDONLY;
}
#ifdef O_BINARY
if (strchr (mode, 'b'))
oflag |= O_BINARY;
#endif
#ifdef __riscos__
{
struct stat buf;
/* Don't allow iobufs on directories */
if (!stat (fname, &buf) && S_ISDIR (buf.st_mode) && !S_ISREG (buf.st_mode))
return __set_errno (EISDIR);
}
#endif
return open (fname, oflag, cflag);
#endif /*!HAVE_W32_SYSTEM*/
}
/*
* Instead of closing an FD we keep it open and cache it for later reuse
* Note that this caching strategy only works if the process does not chdir.
*/
static void
fd_cache_close (const char *fname, gnupg_fd_t fp)
{
close_cache_t cc;
log_assert (fp);
if (!fname || !*fname)
{
#ifdef HAVE_W32_SYSTEM
CloseHandle (fp);
#else
close (fp);
#endif
if (DBG_IOBUF)
log_debug ("fd_cache_close (%d) real\n", FD_DBG (fp));
return;
}
/* try to reuse a slot */
for (cc = close_cache; cc; cc = cc->next)
{
if (cc->fp == GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname))
{
cc->fp = fp;
if (DBG_IOBUF)
log_debug ("fd_cache_close (%s) used existing slot\n", fname);
return;
}
}
/* add a new one */
if (DBG_IOBUF)
log_debug ("fd_cache_close (%s) new slot created\n", fname);
cc = xcalloc (1, sizeof *cc + strlen (fname));
strcpy (cc->fname, fname);
cc->fp = fp;
cc->next = close_cache;
close_cache = cc;
}
/*
* Do a direct_open on FNAME but first try to reuse one from the fd_cache
*/
static gnupg_fd_t
fd_cache_open (const char *fname, const char *mode)
{
close_cache_t cc;
log_assert (fname);
for (cc = close_cache; cc; cc = cc->next)
{
if (cc->fp != GNUPG_INVALID_FD && !fd_cache_strcmp (cc->fname, fname))
{
gnupg_fd_t fp = cc->fp;
cc->fp = GNUPG_INVALID_FD;
if (DBG_IOBUF)
log_debug ("fd_cache_open (%s) using cached fp\n", fname);
#ifdef HAVE_W32_SYSTEM
if (SetFilePointer (fp, 0, NULL, FILE_BEGIN) == 0xffffffff)
{
int ec = (int) GetLastError ();
log_error ("rewind file failed on handle %p: ec=%d\n", fp, ec);
gnupg_w32_set_errno (ec);
fp = GNUPG_INVALID_FD;
}
#else
if (lseek (fp, 0, SEEK_SET) == (off_t) - 1)
{
log_error ("can't rewind fd %d: %s\n", fp, strerror (errno));
fp = GNUPG_INVALID_FD;
}
#endif
return fp;
}
}
if (DBG_IOBUF)
log_debug ("fd_cache_open (%s) not cached\n", fname);
return direct_open (fname, mode, 0);
}
static int
file_filter (void *opaque, int control, iobuf_t chain, byte * buf,
size_t * ret_len)
{
file_filter_ctx_t *a = opaque;
gnupg_fd_t f = a->fp;
size_t size = *ret_len;
size_t nbytes = 0;
int rc = 0;
(void)chain; /* Not used. */
if (control == IOBUFCTRL_UNDERFLOW)
{
log_assert (size); /* We need a buffer. */
if (a->npeeked > a->upeeked)
{
nbytes = a->npeeked - a->upeeked;
if (nbytes > size)
nbytes = size;
memcpy (buf, a->peeked + a->upeeked, nbytes);
a->upeeked += nbytes;
*ret_len = nbytes;
}
else if (a->eof_seen)
{
rc = -1;
*ret_len = 0;
}
else if (a->delayed_rc)
{
rc = a->delayed_rc;
a->delayed_rc = 0;
if (rc == -1)
a->eof_seen = -1;
*ret_len = 0;
}
else
{
#ifdef HAVE_W32_SYSTEM
unsigned long nread;
nbytes = 0;
if (!ReadFile (f, buf, size, &nread, NULL))
{
int ec = (int) GetLastError ();
if (ec != ERROR_BROKEN_PIPE)
{
rc = gpg_error_from_errno (ec);
log_error ("%s: read error: %s (ec=%d)\n",
a->fname, gpg_strerror (rc), ec);
}
}
else if (!nread)
{
a->eof_seen = 1;
rc = -1;
}
else
{
nbytes = nread;
}
#else
int n;
nbytes = 0;
read_more:
do
{
n = read (f, buf + nbytes, size - nbytes);
}
while (n == -1 && errno == EINTR);
if (n > 0)
{
nbytes += n;
if (nbytes < size)
goto read_more;
}
else if (!n) /* eof */
{
if (nbytes)
a->delayed_rc = -1;
else
{
a->eof_seen = 1;
rc = -1;
}
}
else /* error */
{
rc = gpg_error_from_syserror ();
if (gpg_err_code (rc) != GPG_ERR_EPIPE)
log_error ("%s: read error: %s\n", a->fname, gpg_strerror (rc));
if (nbytes)
{
a->delayed_rc = rc;
rc = 0;
}
}
#endif
*ret_len = nbytes;
}
}
else if (control == IOBUFCTRL_FLUSH)
{
if (size)
{
#ifdef HAVE_W32_SYSTEM
byte *p = buf;
unsigned long n;
nbytes = size;
do
{
if (size && !WriteFile (f, p, nbytes, &n, NULL))
{
int ec = gnupg_w32_set_errno (-1);
rc = gpg_error_from_syserror ();
log_error ("%s: write error: %s (ec=%d)\n",
a->fname, gpg_strerror (rc), ec);
break;
}
p += n;
nbytes -= n;
}
while (nbytes);
nbytes = p - buf;
#else
byte *p = buf;
int n;
nbytes = size;
do
{
do
{
n = write (f, p, nbytes);
}
while (n == -1 && errno == EINTR);
if (n > 0)
{
p += n;
nbytes -= n;
}
}
while (n != -1 && nbytes);
if (n == -1)
{
rc = gpg_error_from_syserror ();
log_error ("%s: write error: %s\n", a->fname, strerror (errno));
}
nbytes = p - buf;
#endif
}
*ret_len = nbytes;
}
else if (control == IOBUFCTRL_INIT)
{
a->eof_seen = 0;
a->delayed_rc = 0;
a->keep_open = 0;
a->no_cache = 0;
a->npeeked = 0;
a->upeeked = 0;
}
else if (control == IOBUFCTRL_PEEK)
{
/* Peek on the input. */
#ifdef HAVE_W32_SYSTEM
unsigned long nread;
nbytes = 0;
if (!ReadFile (f, a->peeked, sizeof a->peeked, &nread, NULL))
{
int ec = (int) GetLastError ();
if (ec != ERROR_BROKEN_PIPE)
{
rc = gpg_error_from_errno (ec);
log_error ("%s: read error: %s (ec=%d)\n",
a->fname, gpg_strerror (rc), ec);
}
a->npeeked = 0;
}
else if (!nread)
{
a->eof_seen = 1;
a->npeeked = 0;
}
else
{
a->npeeked = nread;
}
#else /* Unix */
int n;
peek_more:
do
{
n = read (f, a->peeked + a->npeeked, sizeof a->peeked - a->npeeked);
}
while (n == -1 && errno == EINTR);
if (n > 0)
{
a->npeeked += n;
if (a->npeeked < sizeof a->peeked)
goto peek_more;
}
else if (!n) /* eof */
{
if (a->npeeked)
a->delayed_rc = -1;
else
a->eof_seen = 1;
}
else /* error */
{
rc = gpg_error_from_syserror ();
if (gpg_err_code (rc) != GPG_ERR_EPIPE)
log_error ("%s: read error: %s\n", a->fname, gpg_strerror (rc));
if (a->npeeked)
a->delayed_rc = rc;
}
#endif /* Unix */
size = a->npeeked < size? a->npeeked : size;
memcpy (buf, a->peeked, size);
*ret_len = size;
rc = 0; /* Return success - the user needs to check ret_len. */
}
else if (control == IOBUFCTRL_DESC)
{
mem2str (buf, "file_filter(fd)", *ret_len);
}
else if (control == IOBUFCTRL_FREE)
{
if (f != FD_FOR_STDIN && f != FD_FOR_STDOUT)
{
if (DBG_IOBUF)
log_debug ("%s: close fd/handle %d\n", a->fname, FD_DBG (f));
if (!a->keep_open)
fd_cache_close (a->no_cache ? NULL : a->fname, f);
}
xfree (a); /* We can free our context now. */
}
return rc;
}
/* Similar to file_filter but using the estream system. */
static int
file_es_filter (void *opaque, int control, iobuf_t chain, byte * buf,
size_t * ret_len)
{
file_es_filter_ctx_t *a = opaque;
estream_t f = a->fp;
size_t size = *ret_len;
size_t nbytes = 0;
int rc = 0;
(void)chain; /* Not used. */
if (control == IOBUFCTRL_UNDERFLOW)
{
log_assert (size); /* We need a buffer. */
if (a->eof_seen)
{
rc = -1;
*ret_len = 0;
}
else if (a->use_readlimit)
{
nbytes = 0;
if (!a->readlimit)
{ /* eof */
a->eof_seen = 1;
rc = -1;
}
else
{
if (size > a->readlimit)
size = a->readlimit;
rc = es_read (f, buf, size, &nbytes);
if (rc == -1)
{ /* error */
rc = gpg_error_from_syserror ();
log_error ("%s: read error: %s\n", a->fname,strerror (errno));
}
else if (!nbytes)
{ /* eof */
a->eof_seen = 1;
rc = -1;
}
else
a->readlimit -= nbytes;
}
*ret_len = nbytes;
}
else
{
nbytes = 0;
rc = es_read (f, buf, size, &nbytes);
if (rc == -1)
{ /* error */
rc = gpg_error_from_syserror ();
log_error ("%s: read error: %s\n", a->fname, strerror (errno));
}
else if (!nbytes)
{ /* eof */
a->eof_seen = 1;
rc = -1;
}
*ret_len = nbytes;
}
}
else if (control == IOBUFCTRL_FLUSH)
{
if (size)
{
byte *p = buf;
size_t nwritten;
nbytes = size;
do
{
nwritten = 0;
if (es_write (f, p, nbytes, &nwritten))
{
rc = gpg_error_from_syserror ();
log_error ("%s: write error: %s\n",
a->fname, strerror (errno));
break;
}
p += nwritten;
nbytes -= nwritten;
}
while (nbytes);
nbytes = p - buf;
}
*ret_len = nbytes;
}
else if (control == IOBUFCTRL_INIT)
{
a->eof_seen = 0;
a->no_cache = 0;
}
else if (control == IOBUFCTRL_DESC)
{
mem2str (buf, "estream_filter", *ret_len);
}
else if (control == IOBUFCTRL_FREE)
{
if (f != es_stdin && f != es_stdout)
{
if (DBG_IOBUF)
log_debug ("%s: es_fclose %p\n", a->fname, f);
if (!a->keep_open)
es_fclose (f);
}
f = NULL;
xfree (a); /* We can free our context now. */
}
return rc;
}
#ifdef HAVE_W32_SYSTEM
/* Because network sockets are special objects under Lose32 we have to
use a dedicated filter for them. */
static int
sock_filter (void *opaque, int control, iobuf_t chain, byte * buf,
size_t * ret_len)
{
sock_filter_ctx_t *a = opaque;
size_t size = *ret_len;
size_t nbytes = 0;
int rc = 0;
(void)chain;
if (control == IOBUFCTRL_UNDERFLOW)
{
log_assert (size); /* need a buffer */
if (a->eof_seen)
{
rc = -1;
*ret_len = 0;
}
else
{
int nread;
nread = recv (a->sock, buf, size, 0);
if (nread == SOCKET_ERROR)
{
int ec = (int) WSAGetLastError ();
rc = gpg_error_from_errno (ec);
log_error ("socket read error: ec=%d\n", ec);
}
else if (!nread)
{
a->eof_seen = 1;
rc = -1;
}
else
{
nbytes = nread;
}
*ret_len = nbytes;
}
}
else if (control == IOBUFCTRL_FLUSH)
{
if (size)
{
byte *p = buf;
int n;
nbytes = size;
do
{
n = send (a->sock, p, nbytes, 0);
if (n == SOCKET_ERROR)
{
int ec = (int) WSAGetLastError ();
gnupg_w32_set_errno (ec);
rc = gpg_error_from_syserror ();
log_error ("socket write error: ec=%d\n", ec);
break;
}
p += n;
nbytes -= n;
}
while (nbytes);
nbytes = p - buf;
}
*ret_len = nbytes;
}
else if (control == IOBUFCTRL_INIT)
{
a->eof_seen = 0;
a->keep_open = 0;
a->no_cache = 0;
}
else if (control == IOBUFCTRL_DESC)
{
mem2str (buf, "sock_filter", *ret_len);
}
else if (control == IOBUFCTRL_FREE)
{
if (!a->keep_open)
closesocket (a->sock);
xfree (a); /* we can free our context now */
}
return rc;
}
#endif /*HAVE_W32_SYSTEM*/
/****************
* This is used to implement the block write mode.
* Block reading is done on a byte by byte basis in readbyte(),
* without a filter
*/
static int
block_filter (void *opaque, int control, iobuf_t chain, byte * buffer,
size_t * ret_len)
{
block_filter_ctx_t *a = opaque;
char *buf = (char *)buffer;
size_t size = *ret_len;
int c, needed, rc = 0;
char *p;
if (control == IOBUFCTRL_UNDERFLOW)
{
size_t n = 0;
p = buf;
log_assert (size); /* need a buffer */
if (a->eof) /* don't read any further */
rc = -1;
while (!rc && size)
{
if (!a->size)
{ /* get the length bytes */
if (a->partial == 2)
{
a->eof = 1;
if (!n)
rc = -1;
break;
}
else if (a->partial)
{
/* These OpenPGP introduced huffman like encoded length
* bytes are really a mess :-( */
if (a->first_c)
{
c = a->first_c;
a->first_c = 0;
}
else if ((c = iobuf_get (chain)) == -1)
{
log_error ("block_filter: 1st length byte missing\n");
rc = GPG_ERR_BAD_DATA;
break;
}
if (c < 192)
{
a->size = c;
a->partial = 2;
if (!a->size)
{
a->eof = 1;
if (!n)
rc = -1;
break;
}
}
else if (c < 224)
{
a->size = (c - 192) * 256;
if ((c = iobuf_get (chain)) == -1)
{
log_error
("block_filter: 2nd length byte missing\n");
rc = GPG_ERR_BAD_DATA;
break;
}
a->size += c + 192;
a->partial = 2;
if (!a->size)
{
a->eof = 1;
if (!n)
rc = -1;
break;
}
}
else if (c == 255)
{
size_t len = 0;
int i;
for (i = 0; i < 4; i++)
if ((c = iobuf_get (chain)) == -1)
break;
else
len = ((len << 8) | c);
if (i < 4)
{
log_error ("block_filter: invalid 4 byte length\n");
rc = GPG_ERR_BAD_DATA;
break;
}
a->size = len;
a->partial = 2;
if (!a->size)
{
a->eof = 1;
if (!n)
rc = -1;
break;
}
}
else
{ /* Next partial body length. */
a->size = 1 << (c & 0x1f);
}
/* log_debug("partial: ctx=%p c=%02x size=%u\n", a, c, a->size); */
}
else
BUG ();
}
while (!rc && size && a->size)
{
needed = size < a->size ? size : a->size;
c = iobuf_read (chain, p, needed);
if (c < needed)
{
if (c == -1)
c = 0;
log_error
("block_filter %p: read error (size=%lu,a->size=%lu)\n",
a, (ulong) size + c, (ulong) a->size + c);
rc = GPG_ERR_BAD_DATA;
}
else
{
size -= c;
a->size -= c;
p += c;
n += c;
}
}
}
*ret_len = n;
}
else if (control == IOBUFCTRL_FLUSH)
{
if (a->partial)
{ /* the complicated openpgp scheme */
size_t blen, n, nbytes = size + a->buflen;
log_assert (a->buflen <= OP_MIN_PARTIAL_CHUNK);
if (nbytes < OP_MIN_PARTIAL_CHUNK)
{
/* not enough to write a partial block out; so we store it */
if (!a->buffer)
a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK);
memcpy (a->buffer + a->buflen, buf, size);
a->buflen += size;
}
else
{ /* okay, we can write out something */
/* do this in a loop to use the most efficient block lengths */
p = buf;
do
{
/* find the best matching block length - this is limited
* by the size of the internal buffering */
for (blen = OP_MIN_PARTIAL_CHUNK * 2,
c = OP_MIN_PARTIAL_CHUNK_2POW + 1; blen <= nbytes;
blen *= 2, c++)
;
blen /= 2;
c--;
/* write the partial length header */
log_assert (c <= 0x1f); /*;-) */
c |= 0xe0;
iobuf_put (chain, c);
if ((n = a->buflen))
{ /* write stuff from the buffer */
log_assert (n == OP_MIN_PARTIAL_CHUNK);
if (iobuf_write (chain, a->buffer, n))
rc = gpg_error_from_syserror ();
a->buflen = 0;
nbytes -= n;
}
if ((n = nbytes) > blen)
n = blen;
if (n && iobuf_write (chain, p, n))
rc = gpg_error_from_syserror ();
p += n;
nbytes -= n;
}
while (!rc && nbytes >= OP_MIN_PARTIAL_CHUNK);
/* store the rest in the buffer */
if (!rc && nbytes)
{
log_assert (!a->buflen);
log_assert (nbytes < OP_MIN_PARTIAL_CHUNK);
if (!a->buffer)
a->buffer = xmalloc (OP_MIN_PARTIAL_CHUNK);
memcpy (a->buffer, p, nbytes);
a->buflen = nbytes;
}
}
}
else
BUG ();
}
else if (control == IOBUFCTRL_INIT)
{
if (DBG_IOBUF)
log_debug ("init block_filter %p\n", a);
if (a->partial)
a->count = 0;
else if (a->use == IOBUF_INPUT)
a->count = a->size = 0;
else
a->count = a->size; /* force first length bytes */
a->eof = 0;
a->buffer = NULL;
a->buflen = 0;
}
else if (control == IOBUFCTRL_DESC)
{
mem2str (buf, "block_filter", *ret_len);
}
else if (control == IOBUFCTRL_FREE)
{
if (a->use == IOBUF_OUTPUT)
{ /* write the end markers */
if (a->partial)
{
u32 len;
/* write out the remaining bytes without a partial header
* the length of this header may be 0 - but if it is
* the first block we are not allowed to use a partial header
* and frankly we can't do so, because this length must be
* a power of 2. This is _really_ complicated because we
* have to check the possible length of a packet prior
* to it's creation: a chain of filters becomes complicated
* and we need a lot of code to handle compressed packets etc.
* :-(((((((
*/
/* construct header */
len = a->buflen;
/*log_debug("partial: remaining length=%u\n", len ); */
if (len < 192)
rc = iobuf_put (chain, len);
else if (len < 8384)
{
if (!(rc = iobuf_put (chain, ((len - 192) / 256) + 192)))
rc = iobuf_put (chain, ((len - 192) % 256));
}
else
{ /* use a 4 byte header */
if (!(rc = iobuf_put (chain, 0xff)))
if (!(rc = iobuf_put (chain, (len >> 24) & 0xff)))
if (!(rc = iobuf_put (chain, (len >> 16) & 0xff)))
if (!(rc = iobuf_put (chain, (len >> 8) & 0xff)))
rc = iobuf_put (chain, len & 0xff);
}
if (!rc && len)
rc = iobuf_write (chain, a->buffer, len);
if (rc)
{
log_error ("block_filter: write error: %s\n",
strerror (errno));
rc = gpg_error_from_syserror ();
}
xfree (a->buffer);
a->buffer = NULL;
a->buflen = 0;
}
else
BUG ();
}
else if (a->size)
{
log_error ("block_filter: pending bytes!\n");
}
if (DBG_IOBUF)
log_debug ("free block_filter %p\n", a);
xfree (a); /* we can free our context now */
}
return rc;
}
/* Change the default size for all IOBUFs to KILOBYTE. This needs to
* be called before any iobufs are used and can only be used once.
* Returns the current value. Using 0 has no effect except for
* returning the current value. */
unsigned int
iobuf_set_buffer_size (unsigned int kilobyte)
{
static int used;
if (!used && kilobyte)
{
if (kilobyte < 4)
kilobyte = 4;
else if (kilobyte > 16*1024)
kilobyte = 16*1024;
iobuf_buffer_size = kilobyte * 1024;
used = 1;
}
return iobuf_buffer_size / 1024;
}
#define MAX_IOBUF_DESC 32
/*
* Fill the buffer by the description of iobuf A.
* The buffer size should be MAX_IOBUF_DESC (or larger).
* Returns BUF as (const char *).
*/
static const char *
iobuf_desc (iobuf_t a, byte *buf)
{
size_t len = MAX_IOBUF_DESC;
if (! a || ! a->filter)
memcpy (buf, "?", 2);
else
a->filter (a->filter_ov, IOBUFCTRL_DESC, NULL, buf, &len);
return buf;
}
static void
print_chain (iobuf_t a)
{
if (!DBG_IOBUF)
return;
for (; a; a = a->chain)
{
byte desc[MAX_IOBUF_DESC];
log_debug ("iobuf chain: %d.%d '%s' filter_eof=%d start=%d len=%d\n",
a->no, a->subno, iobuf_desc (a, desc), a->filter_eof,
(int) a->d.start, (int) a->d.len);
}
}
int
iobuf_print_chain (iobuf_t a)
{
print_chain (a);
return 0;
}
iobuf_t
iobuf_alloc (int use, size_t bufsize)
{
iobuf_t a;
static int number = 0;
log_assert (use == IOBUF_INPUT || use == IOBUF_INPUT_TEMP
|| use == IOBUF_OUTPUT || use == IOBUF_OUTPUT_TEMP);
if (bufsize == 0)
{
log_bug ("iobuf_alloc() passed a bufsize of 0!\n");
bufsize = iobuf_buffer_size;
}
a = xcalloc (1, sizeof *a);
a->use = use;
a->d.buf = xmalloc (bufsize);
a->d.size = bufsize;
a->e_d.buf = NULL;
a->e_d.len = 0;
a->e_d.used = 0;
a->e_d.preferred = 0;
a->no = ++number;
a->subno = 0;
a->real_fname = NULL;
return a;
}
int
iobuf_close (iobuf_t a)
{
iobuf_t a_chain;
size_t dummy_len = 0;
int rc = 0;
for (; a; a = a_chain)
{
byte desc[MAX_IOBUF_DESC];
int rc2 = 0;
a_chain = a->chain;
if (a->use == IOBUF_OUTPUT && (rc = filter_flush (a)))
log_error ("filter_flush failed on close: %s\n", gpg_strerror (rc));
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: close '%s'\n",
a->no, a->subno, iobuf_desc (a, desc));
if (a->filter && (rc2 = a->filter (a->filter_ov, IOBUFCTRL_FREE,
a->chain, NULL, &dummy_len)))
log_error ("IOBUFCTRL_FREE failed on close: %s\n", gpg_strerror (rc));
if (! rc && rc2)
/* Whoops! An error occurred. Save it in RC if we haven't
already recorded an error. */
rc = rc2;
xfree (a->real_fname);
if (a->d.buf)
{
memset (a->d.buf, 0, a->d.size); /* erase the buffer */
xfree (a->d.buf);
}
xfree (a);
}
return rc;
}
int
iobuf_cancel (iobuf_t a)
{
const char *s;
iobuf_t a2;
int rc;
#if defined(HAVE_W32_SYSTEM) || defined(__riscos__)
char *remove_name = NULL;
#endif
if (a && a->use == IOBUF_OUTPUT)
{
s = iobuf_get_real_fname (a);
if (s && *s)
{
#if defined(HAVE_W32_SYSTEM) || defined(__riscos__)
remove_name = xstrdup (s);
#else
remove (s);
#endif
}
}
/* send a cancel message to all filters */
for (a2 = a; a2; a2 = a2->chain)
{
size_t dummy = 0;
if (a2->filter)
a2->filter (a2->filter_ov, IOBUFCTRL_CANCEL, a2->chain, NULL, &dummy);
}
rc = iobuf_close (a);
#if defined(HAVE_W32_SYSTEM) || defined(__riscos__)
if (remove_name)
{
/* Argg, MSDOS does not allow removing open files. So
* we have to do it here */
gnupg_remove (remove_name);
xfree (remove_name);
}
#endif
return rc;
}
iobuf_t
iobuf_temp (void)
{
return iobuf_alloc (IOBUF_OUTPUT_TEMP, iobuf_buffer_size);
}
iobuf_t
iobuf_temp_with_content (const char *buffer, size_t length)
{
iobuf_t a;
int i;
a = iobuf_alloc (IOBUF_INPUT_TEMP, length);
log_assert (length == a->d.size);
/* memcpy (a->d.buf, buffer, length); */
for (i=0; i < length; i++)
a->d.buf[i] = buffer[i];
a->d.len = length;
return a;
}
int
iobuf_is_pipe_filename (const char *fname)
{
if (!fname || (*fname=='-' && !fname[1]) )
return 1;
return gnupg_check_special_filename (fname) != GNUPG_INVALID_FD;
}
static iobuf_t
do_open (const char *fname, int special_filenames,
int use, const char *opentype, int mode700)
{
iobuf_t a;
gnupg_fd_t fp;
file_filter_ctx_t *fcx;
size_t len = 0;
int print_only = 0;
gnupg_fd_t fd;
byte desc[MAX_IOBUF_DESC];
log_assert (use == IOBUF_INPUT || use == IOBUF_OUTPUT);
if (special_filenames
/* NULL or '-'. */
&& (!fname || (*fname == '-' && !fname[1])))
{
if (use == IOBUF_INPUT)
{
fp = FD_FOR_STDIN;
fname = "[stdin]";
}
else
{
fp = FD_FOR_STDOUT;
fname = "[stdout]";
}
print_only = 1;
}
else if (!fname)
return NULL;
else if (special_filenames
&& (fd = gnupg_check_special_filename (fname)) != GNUPG_INVALID_FD)
return do_iobuf_fdopen (fd, opentype, 0);
else
{
if (use == IOBUF_INPUT)
fp = fd_cache_open (fname, opentype);
else
fp = direct_open (fname, opentype, mode700);
if (fp == GNUPG_INVALID_FD)
return NULL;
}
a = iobuf_alloc (use, iobuf_buffer_size);
fcx = xmalloc (sizeof *fcx + strlen (fname));
fcx->fp = fp;
fcx->print_only_name = print_only;
strcpy (fcx->fname, fname);
if (!print_only)
a->real_fname = xstrdup (fname);
a->filter = file_filter;
a->filter_ov = fcx;
file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: open '%s' desc=%s fd=%d\n",
a->no, a->subno, fname, iobuf_desc (a, desc),
FD_DBG (fcx->fp));
return a;
}
iobuf_t
iobuf_open (const char *fname)
{
return do_open (fname, 1, IOBUF_INPUT, "rb", 0);
}
iobuf_t
iobuf_create (const char *fname, int mode700)
{
return do_open (fname, 1, IOBUF_OUTPUT, "wb", mode700);
}
iobuf_t
iobuf_openrw (const char *fname)
{
return do_open (fname, 0, IOBUF_OUTPUT, "r+b", 0);
}
static iobuf_t
do_iobuf_fdopen (gnupg_fd_t fp, const char *mode, int keep_open)
{
iobuf_t a;
file_filter_ctx_t *fcx;
size_t len = 0;
a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT,
iobuf_buffer_size);
fcx = xmalloc (sizeof *fcx + 20);
fcx->fp = fp;
fcx->print_only_name = 1;
fcx->keep_open = keep_open;
sprintf (fcx->fname, "[fd %d]", FD_DBG (fp));
a->filter = file_filter;
a->filter_ov = fcx;
file_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: fdopen%s '%s'\n",
a->no, a->subno, keep_open? "_nc":"", fcx->fname);
iobuf_ioctl (a, IOBUF_IOCTL_NO_CACHE, 1, NULL);
return a;
}
iobuf_t
iobuf_fdopen (gnupg_fd_t fp, const char *mode)
{
return do_iobuf_fdopen (fp, mode, 0);
}
iobuf_t
iobuf_fdopen_nc (gnupg_fd_t fp, const char *mode)
{
return do_iobuf_fdopen (fp, mode, 1);
}
iobuf_t
iobuf_esopen (estream_t estream, const char *mode, int keep_open,
size_t readlimit)
{
iobuf_t a;
file_es_filter_ctx_t *fcx;
size_t len = 0;
a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT,
iobuf_buffer_size);
fcx = xtrymalloc (sizeof *fcx + 30);
fcx->fp = estream;
fcx->print_only_name = 1;
fcx->keep_open = keep_open;
fcx->readlimit = readlimit;
fcx->use_readlimit = !!readlimit;
snprintf (fcx->fname, 30, "[fd %p]", estream);
a->filter = file_es_filter;
a->filter_ov = fcx;
file_es_filter (fcx, IOBUFCTRL_INIT, NULL, NULL, &len);
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: esopen%s '%s'\n",
a->no, a->subno, keep_open? "_nc":"", fcx->fname);
return a;
}
iobuf_t
iobuf_sockopen (int fd, const char *mode)
{
iobuf_t a;
#ifdef HAVE_W32_SYSTEM
sock_filter_ctx_t *scx;
size_t len;
a = iobuf_alloc (strchr (mode, 'w') ? IOBUF_OUTPUT : IOBUF_INPUT,
iobuf_buffer_size);
scx = xmalloc (sizeof *scx + 25);
scx->sock = fd;
scx->print_only_name = 1;
sprintf (scx->fname, "[sock %d]", fd);
a->filter = sock_filter;
a->filter_ov = scx;
sock_filter (scx, IOBUFCTRL_INIT, NULL, NULL, &len);
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: sockopen '%s'\n", a->no, a->subno, scx->fname);
iobuf_ioctl (a, IOBUF_IOCTL_NO_CACHE, 1, NULL);
#else
a = do_iobuf_fdopen (fd, mode, 0);
#endif
return a;
}
int
iobuf_ioctl (iobuf_t a, iobuf_ioctl_t cmd, int intval, void *ptrval)
{
byte desc[MAX_IOBUF_DESC];
if (cmd == IOBUF_IOCTL_KEEP_OPEN)
{
/* Keep system filepointer/descriptor open. This was used in
the past by http.c; this ioctl is not directly used
anymore. */
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: ioctl '%s' keep_open=%d\n",
a ? a->no : -1, a ? a->subno : -1, iobuf_desc (a, desc),
intval);
for (; a; a = a->chain)
if (!a->chain && a->filter == file_filter)
{
file_filter_ctx_t *b = a->filter_ov;
b->keep_open = intval;
return 0;
}
#ifdef HAVE_W32_SYSTEM
else if (!a->chain && a->filter == sock_filter)
{
sock_filter_ctx_t *b = a->filter_ov;
b->keep_open = intval;
return 0;
}
#endif
}
else if (cmd == IOBUF_IOCTL_INVALIDATE_CACHE)
{
if (DBG_IOBUF)
log_debug ("iobuf-*.*: ioctl '%s' invalidate\n",
ptrval ? (char *) ptrval : "?");
if (!a && !intval && ptrval)
{
if (fd_cache_invalidate (ptrval))
return -1;
return 0;
}
}
else if (cmd == IOBUF_IOCTL_NO_CACHE)
{
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: ioctl '%s' no_cache=%d\n",
a ? a->no : -1, a ? a->subno : -1, iobuf_desc (a, desc),
intval);
for (; a; a = a->chain)
if (!a->chain && a->filter == file_filter)
{
file_filter_ctx_t *b = a->filter_ov;
b->no_cache = intval;
return 0;
}
#ifdef HAVE_W32_SYSTEM
else if (!a->chain && a->filter == sock_filter)
{
sock_filter_ctx_t *b = a->filter_ov;
b->no_cache = intval;
return 0;
}
#endif
}
else if (cmd == IOBUF_IOCTL_FSYNC)
{
/* Do a fsync on the open fd and return any errors to the caller
of iobuf_ioctl. Note that we work on a file name here. */
if (DBG_IOBUF)
log_debug ("iobuf-*.*: ioctl '%s' fsync\n",
ptrval? (const char*)ptrval:"<null>");
if (!a && !intval && ptrval)
{
return fd_cache_synchronize (ptrval);
}
}
else if (cmd == IOBUF_IOCTL_PEEK)
{
/* Peek at a justed opened file. Use this only directly after a
* file has been opened for reading. Don't use it after you did
* a seek. This works only if just file filter has been
- * pushed. Expects a buffer wit size INTVAL at PTRVAL and returns
+ * pushed. Expects a buffer with size INTVAL at PTRVAL and returns
* the number of bytes put into the buffer. */
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: ioctl '%s' peek\n",
a ? a->no : -1, a ? a->subno : -1, iobuf_desc (a, desc));
if (a->filter == file_filter && ptrval && intval)
{
file_filter_ctx_t *fcx = a->filter_ov;
size_t len = intval;
if (!file_filter (fcx, IOBUFCTRL_PEEK, NULL, ptrval, &len))
return (int)len;
}
}
return -1;
}
/****************
* Register an i/o filter.
*/
int
iobuf_push_filter (iobuf_t a,
int (*f) (void *opaque, int control,
iobuf_t chain, byte * buf, size_t * len),
void *ov)
{
return iobuf_push_filter2 (a, f, ov, 0);
}
int
iobuf_push_filter2 (iobuf_t a,
int (*f) (void *opaque, int control,
iobuf_t chain, byte * buf, size_t * len),
void *ov, int rel_ov)
{
iobuf_t b;
size_t dummy_len = 0;
int rc = 0;
if (a->use == IOBUF_OUTPUT && (rc = filter_flush (a)))
return rc;
if (a->subno >= MAX_NESTING_FILTER)
{
log_error ("i/o filter too deeply nested - corrupted data?\n");
return GPG_ERR_BAD_DATA;
}
/* We want to create a new filter and put it in front of A. A
simple implementation would do:
b = iobuf_alloc (...);
b->chain = a;
return a;
This is a bit problematic: A is the head of the pipeline and
there are potentially many pointers to it. Requiring the caller
to update all of these pointers is a burden.
An alternative implementation would add a level of indirection.
For instance, we could use a pipeline object, which contains a
pointer to the first filter in the pipeline. This is not what we
do either.
Instead, we allocate a new buffer (B) and copy the first filter's
state into that and use the initial buffer (A) for the new
filter. One limitation of this approach is that it is not
practical to maintain a pointer to a specific filter's state.
Before:
A
|
v 0x100 0x200
+----------+ +----------+
| filter x |--------->| filter y |---->....
+----------+ +----------+
After: B
|
v 0x300
+----------+
A | filter x |
| +----------+
v 0x100 ^ v 0x200
+----------+ +----------+
| filter w | | filter y |---->....
+----------+ +----------+
Note: filter x's address changed from 0x100 to 0x300, but A still
points to the head of the pipeline.
*/
b = xmalloc (sizeof *b);
memcpy (b, a, sizeof *b);
/* fixme: it is stupid to keep a copy of the name at every level
* but we need the name somewhere because the name known by file_filter
* may have been released when we need the name of the file */
b->real_fname = a->real_fname ? xstrdup (a->real_fname) : NULL;
/* remove the filter stuff from the new stream */
a->filter = NULL;
a->filter_ov = NULL;
a->filter_ov_owner = 0;
a->filter_eof = 0;
if (a->use == IOBUF_OUTPUT_TEMP)
/* A TEMP filter buffers any data sent to it; it does not forward
any data down the pipeline. If we add a new filter to the
pipeline, it shouldn't also buffer data. It should send it
downstream to be buffered. Thus, the correct type for a filter
added in front of an IOBUF_OUTPUT_TEMP filter is IOBUF_OUPUT, not
IOBUF_OUTPUT_TEMP. */
{
a->use = IOBUF_OUTPUT;
/* When pipeline is written to, the temp buffer's size is
increased accordingly. We don't need to allocate a 10 MB
buffer for a non-terminal filter. Just use the default
size. */
a->d.size = iobuf_buffer_size;
}
else if (a->use == IOBUF_INPUT_TEMP)
/* Same idea as above. */
{
a->use = IOBUF_INPUT;
a->d.size = iobuf_buffer_size;
}
/* The new filter (A) gets a new buffer.
If the pipeline is an output or temp pipeline, then giving the
buffer to the new filter means that data that was written before
the filter was pushed gets sent to the filter. That's clearly
wrong.
If the pipeline is an input pipeline, then giving the buffer to
the new filter (A) means that data that has read from (B), but
not yet read from the pipeline won't be processed by the new
filter (A)! That's certainly not what we want. */
a->d.buf = xmalloc (a->d.size);
a->d.len = 0;
a->d.start = 0;
/* disable nlimit for the new stream */
a->ntotal = b->ntotal + b->nbytes;
a->nlimit = a->nbytes = 0;
a->nofast = 0;
/* make a link from the new stream to the original stream */
a->chain = b;
/* setup the function on the new stream */
a->filter = f;
a->filter_ov = ov;
a->filter_ov_owner = rel_ov;
a->subno = b->subno + 1;
if (DBG_IOBUF)
{
byte desc[MAX_IOBUF_DESC];
log_debug ("iobuf-%d.%d: push '%s'\n",
a->no, a->subno, iobuf_desc (a, desc));
print_chain (a);
}
/* now we can initialize the new function if we have one */
if (a->filter && (rc = a->filter (a->filter_ov, IOBUFCTRL_INIT, a->chain,
NULL, &dummy_len)))
log_error ("IOBUFCTRL_INIT failed: %s\n", gpg_strerror (rc));
return rc;
}
/****************
* Remove an i/o filter.
*/
int
iobuf_pop_filter (iobuf_t a, int (*f) (void *opaque, int control,
iobuf_t chain, byte * buf, size_t * len),
void *ov)
{
iobuf_t b;
size_t dummy_len = 0;
int rc = 0;
byte desc[MAX_IOBUF_DESC];
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: pop '%s'\n",
a->no, a->subno, iobuf_desc (a, desc));
if (a->use == IOBUF_INPUT_TEMP || a->use == IOBUF_OUTPUT_TEMP)
{
/* This should be the last filter in the pipeline. */
log_assert (! a->chain);
return 0;
}
if (!a->filter)
{ /* this is simple */
b = a->chain;
log_assert (b);
xfree (a->d.buf);
xfree (a->real_fname);
memcpy (a, b, sizeof *a);
xfree (b);
return 0;
}
for (b = a; b; b = b->chain)
if (b->filter == f && (!ov || b->filter_ov == ov))
break;
if (!b)
log_bug ("iobuf_pop_filter(): filter function not found\n");
/* flush this stream if it is an output stream */
if (a->use == IOBUF_OUTPUT && (rc = filter_flush (b)))
{
log_error ("filter_flush failed in iobuf_pop_filter: %s\n",
gpg_strerror (rc));
return rc;
}
/* and tell the filter to free it self */
if (b->filter && (rc = b->filter (b->filter_ov, IOBUFCTRL_FREE, b->chain,
NULL, &dummy_len)))
{
log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc));
return rc;
}
if (b->filter_ov && b->filter_ov_owner)
{
xfree (b->filter_ov);
b->filter_ov = NULL;
}
/* and see how to remove it */
if (a == b && !b->chain)
log_bug ("can't remove the last filter from the chain\n");
else if (a == b)
{ /* remove the first iobuf from the chain */
/* everything from b is copied to a. This is save because
* a flush has been done on the to be removed entry
*/
b = a->chain;
xfree (a->d.buf);
xfree (a->real_fname);
memcpy (a, b, sizeof *a);
xfree (b);
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: popped filter\n", a->no, a->subno);
}
else if (!b->chain)
{ /* remove the last iobuf from the chain */
log_bug ("Ohh jeee, trying to remove a head filter\n");
}
else
{ /* remove an intermediate iobuf from the chain */
log_bug ("Ohh jeee, trying to remove an intermediate filter\n");
}
return rc;
}
/****************
* read underflow: read at least one byte into the buffer and return
* the first byte or -1 on EOF.
*/
static int
underflow (iobuf_t a, int clear_pending_eof)
{
return underflow_target (a, clear_pending_eof, 1);
}
/****************
* read underflow: read TARGET bytes into the buffer and return
* the first byte or -1 on EOF.
*/
static int
underflow_target (iobuf_t a, int clear_pending_eof, size_t target)
{
size_t len;
int rc;
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: underflow: buffer size: %d; still buffered: %d => space for %d bytes\n",
a->no, a->subno,
(int) a->d.size, (int) (a->d.len - a->d.start),
(int) (a->d.size - (a->d.len - a->d.start)));
if (a->use == IOBUF_INPUT_TEMP)
/* By definition, there isn't more data to read into the
buffer. */
return -1;
log_assert (a->use == IOBUF_INPUT);
a->e_d.used = 0;
/* If there is still some buffered data, then move it to the start
of the buffer and try to fill the end of the buffer. (This is
useful if we are called from iobuf_peek().) */
log_assert (a->d.start <= a->d.len);
a->d.len -= a->d.start;
if (a->d.len)
memmove (a->d.buf, &a->d.buf[a->d.start], a->d.len);
a->d.start = 0;
if (a->d.len < target && a->filter_eof)
/* The last time we tried to read from this filter, we got an EOF.
We couldn't return the EOF, because there was buffered data.
Since there is no longer any buffered data, return the
error. */
{
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: underflow: eof (pending eof)\n",
a->no, a->subno);
if (! clear_pending_eof)
return -1;
if (a->chain)
/* A filter follows this one. Free this filter. */
{
iobuf_t b = a->chain;
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: filter popped (pending EOF returned)\n",
a->no, a->subno);
xfree (a->d.buf);
xfree (a->real_fname);
memcpy (a, b, sizeof *a);
xfree (b);
print_chain (a);
}
else
a->filter_eof = 0; /* for the top level filter */
return -1; /* return one(!) EOF */
}
if (a->d.len == 0 && a->error)
/* The last time we tried to read from this filter, we got an
error. We couldn't return the error, because there was
buffered data. Since there is no longer any buffered data,
return the error. */
{
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: pending error (%s) returned\n",
a->no, a->subno, gpg_strerror (a->error));
return -1;
}
if (a->filter && ! a->filter_eof && ! a->error)
/* We have a filter function and the last time we tried to read we
didn't get an EOF or an error. Try to fill the buffer. */
{
/* Be careful to account for any buffered data. */
len = a->d.size - a->d.len;
if (a->e_d.preferred && a->d.len < IOBUF_ZEROCOPY_THRESHOLD_SIZE
&& (IOBUF_ZEROCOPY_THRESHOLD_SIZE - a->d.len) < len)
{
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: limit buffering as external drain is "
"preferred\n", a->no, a->subno);
len = IOBUF_ZEROCOPY_THRESHOLD_SIZE - a->d.len;
}
if (len == 0)
/* There is no space for more data. Don't bother calling
A->FILTER. */
rc = 0;
else
{
/* If no buffered data and drain buffer has been setup, and drain
* buffer is largish, read data directly to drain buffer. */
if (a->d.len == 0
&& a->e_d.buf
&& a->e_d.len >= IOBUF_ZEROCOPY_THRESHOLD_SIZE)
{
len = a->e_d.len;
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: underflow: A->FILTER (%lu bytes, to external drain)\n",
a->no, a->subno, (ulong)len);
rc = a->filter (a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain,
a->e_d.buf, &len);
a->e_d.used = len;
len = 0;
}
else
{
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: underflow: A->FILTER (%lu bytes)\n",
a->no, a->subno, (ulong)len);
rc = a->filter (a->filter_ov, IOBUFCTRL_UNDERFLOW, a->chain,
&a->d.buf[a->d.len], &len);
}
}
a->d.len += len;
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: A->FILTER() returned rc=%d (%s), read %lu bytes%s\n",
a->no, a->subno,
rc, rc == 0 ? "ok" : rc == -1 ? "EOF" : gpg_strerror (rc),
(ulong)(a->e_d.used ? a->e_d.used : len),
a->e_d.used ? " (to external buffer)" : "");
/* if( a->no == 1 ) */
/* log_hexdump (" data:", a->d.buf, len); */
if (rc == -1)
/* EOF. */
{
size_t dummy_len = 0;
/* Tell the filter to free itself */
if ((rc = a->filter (a->filter_ov, IOBUFCTRL_FREE, a->chain,
NULL, &dummy_len)))
log_error ("IOBUFCTRL_FREE failed: %s\n", gpg_strerror (rc));
/* Free everything except for the internal buffer. */
if (a->filter_ov && a->filter_ov_owner)
xfree (a->filter_ov);
a->filter_ov = NULL;
a->filter = NULL;
a->filter_eof = 1;
if (clear_pending_eof && a->d.len == 0 && a->e_d.used == 0
&& a->chain)
/* We don't need to keep this filter around at all:
- we got an EOF
- we have no buffered data
- a filter follows this one.
Unlink this filter. */
{
iobuf_t b = a->chain;
if (DBG_IOBUF)
log_debug ("iobuf-%d.%d: pop in underflow (nothing buffered, got EOF)\n",
a->no, a->subno);
xfree (a->d.buf);
xfree (a->real_fname);
memcpy (a, b, sizeof *a);
xfree (b);
print_chain (a);
return -1;
}
else if (a->d.len == 0 && a->e_d.used == 0)
/* We can't unlink this filter (it is the only one in the
pipeline), but we can immediately return EOF. */
return -1;
}
else if (rc)
/* Record the error. */
{
a->error = rc;
if (a->d.len == 0 && a->e_d.used == 0)
/* There is no buffered data. Immediately return EOF. */
return -1;
}
}
log_assert (a->d.start <= a->d.len);
if (a->e_d.used > 0)
return 0;
if (a->d.start < a->d.len)
return a->d.buf[a->d.start++];
/* EOF. */
return -1;
}
static int
filter_flush (iobuf_t a)
{
int external_used = 0;
byte *src_buf;
size_t src_len;
size_t len;
int rc;
a->e_d.used = 0;
if (a->use == IOBUF_OUTPUT_TEMP)
{ /* increase the temp buffer */
size_t newsize = a->d.size + iobuf_buffer_size;
if (DBG_IOBUF)
log_debug ("increasing temp iobuf from %lu to %lu\n",
(ulong) a->d.size, (ulong) newsize);
a->d.buf = xrealloc (a->d.buf, newsize);
a->d.size = newsize;
return 0;
}
else if (a->use != IOBUF_OUTPUT)
log_bug ("flush on non-output iobuf\n");
else if (!a->filter)
log_bug ("filter_flush: no filter\n");
if (a->d.len == 0 && a->e_d.buf && a->e_d.len > 0)
{
src_buf = a->e_d.buf;
src_len = a->e_d.len;
external_used = 1;
}
else
{
src_buf = a->d.buf;
src_len = a->d.len;
external_used = 0;
}
len = src_len;
rc = a->filter (a->filter_ov, IOBUFCTRL_FLUSH, a->chain, src_buf, &len);
if (!rc && len != src_len)
{
log_info ("filter_flush did not write all!\n");
rc = GPG_ERR_INTERNAL;
}
else if (rc)
a->error = rc;
a->d.len = 0;
if (external_used)
a->e_d.used = len;
return rc;
}
int
iobuf_readbyte (iobuf_t a)
{
int c;
if (a->use == IOBUF_OUTPUT || a->use == IOBUF_OUTPUT_TEMP)
{
log_bug ("iobuf_readbyte called on a non-INPUT pipeline!\n");
return -1;
}
log_assert (a->d.start <= a->d.len);
if (a->nlimit && a->nbytes >= a->nlimit)
return -1; /* forced EOF */
if (a->d.start < a->d.len)
{
c = a->d.buf[a->d.start++];
}
else if ((c = underflow (a, 1)) == -1)
return -1; /* EOF */
log_assert (a->d.start <= a->d.len);
/* Note: if underflow doesn't return EOF, then it returns the first
byte that was read and advances a->d.start appropriately. */
a->nbytes++;
return c;
}
int
iobuf_read (iobuf_t a, void *buffer, unsigned int buflen)
{
unsigned char *buf = (unsigned char *)buffer;
int c, n;
if (a->use == IOBUF_OUTPUT || a->use == IOBUF_OUTPUT_TEMP)
{
log_bug ("iobuf_read called on a non-INPUT pipeline!\n");
return -1;
}
if (a->nlimit)
{
/* Handle special cases. */
for (n = 0; n < buflen; n++)
{
if ((c = iobuf_readbyte (a)) == -1)
{
if (!n)
return -1; /* eof */
break;
}
if (buf)
{
*buf = c;
buf++;
}
}
return n;
}
a->e_d.buf = NULL;
a->e_d.len = 0;
/* Hint for how full to fill iobuf internal drain buffer. */
a->e_d.preferred = (a->use != IOBUF_INPUT_TEMP)
&& (buf && buflen >= IOBUF_ZEROCOPY_THRESHOLD_SIZE);
n = 0;
do
{
if (n < buflen && a->d.start < a->d.len)
/* Drain the buffer. */
{
unsigned size = a->d.len - a->d.start;
if (size > buflen - n)
size = buflen - n;
if (buf)
memcpy (buf, a->d.buf + a->d.start, size);
n += size;
a->d.start += size;
if (buf)
buf += size;
}
if (n < buflen)
/* Draining the internal buffer didn't fill BUFFER. Call
underflow to read more data into the filter's internal
buffer. */
{
if (a->use != IOBUF_INPUT_TEMP && buf && n < buflen)
{
/* Setup external drain buffer for faster moving of data
* (avoid memcpy). */
a->e_d.buf = buf;
a->e_d.len = (buflen - n) / IOBUF_ZEROCOPY_THRESHOLD_SIZE
* IOBUF_ZEROCOPY_THRESHOLD_SIZE;
if (a->e_d.len == 0)
a->e_d.buf = NULL;
if (a->e_d.buf && DBG_IOBUF)
log_debug ("iobuf-%d.%d: reading to external buffer, %lu bytes\n",
a->no, a->subno, (ulong)a->e_d.len);
}
if ((c = underflow (a, 1)) == -1)
/* EOF. If we managed to read something, don't return EOF
now. */
{
a->e_d.buf = NULL;
a->e_d.len = 0;
a->nbytes += n;
return n ? n : -1 /*EOF*/;
}
if (a->e_d.buf && a->e_d.used > 0)
{
/* Drain buffer was used, 'c' only contains return code
* 0 or -1. */
n += a->e_d.used;
buf += a->e_d.used;
}
else
{
if (buf)
*buf++ = c;
n++;
}
a->e_d.buf = NULL;
a->e_d.len = 0;
}
}
while (n < buflen);
a->nbytes += n;
return n;
}
int
iobuf_peek (iobuf_t a, byte * buf, unsigned buflen)
{
int n = 0;
log_assert (buflen > 0);
log_assert (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP);
if (buflen > a->d.size)
/* We can't peek more than we can buffer. */
buflen = a->d.size;
/* Try to fill the internal buffer with enough data to satisfy the
request. */
while (buflen > a->d.len - a->d.start)
{
if (underflow_target (a, 0, buflen) == -1)
/* EOF. We can't read any more. */
break;
/* Underflow consumes the first character (it's the return
value). unget() it by resetting the "file position". */
log_assert (a->d.start == 1);
a->d.start = 0;
}
n = a->d.len - a->d.start;
if (n > buflen)
n = buflen;
if (n == 0)
/* EOF. */
return -1;
memcpy (buf, &a->d.buf[a->d.start], n);
return n;
}
int
iobuf_writebyte (iobuf_t a, unsigned int c)
{
int rc;
if (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP)
{
log_bug ("iobuf_writebyte called on an input pipeline!\n");
return -1;
}
if (a->d.len == a->d.size)
if ((rc=filter_flush (a)))
return rc;
log_assert (a->d.len < a->d.size);
a->d.buf[a->d.len++] = c;
return 0;
}
int
iobuf_write (iobuf_t a, const void *buffer, unsigned int buflen)
{
const unsigned char *buf = (const unsigned char *)buffer;
int rc;
if (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP)
{
log_bug ("iobuf_write called on an input pipeline!\n");
return -1;
}
a->e_d.buf = NULL;
a->e_d.len = 0;
/* Hint for how full to fill iobuf internal drain buffer. */
a->e_d.preferred = (a->use != IOBUF_OUTPUT_TEMP)
&& (buflen >= IOBUF_ZEROCOPY_THRESHOLD_SIZE);
do
{
if ((a->use != IOBUF_OUTPUT_TEMP)
&& a->d.len == 0 && buflen >= IOBUF_ZEROCOPY_THRESHOLD_SIZE)
{
/* Setup external drain buffer for faster moving of data
* (avoid memcpy). */
a->e_d.buf = (byte *)buf;
a->e_d.len = buflen / IOBUF_ZEROCOPY_THRESHOLD_SIZE
* IOBUF_ZEROCOPY_THRESHOLD_SIZE;
if (a->e_d.len == 0)
a->e_d.buf = NULL;
if (a->e_d.buf && DBG_IOBUF)
log_debug ("iobuf-%d.%d: writing from external buffer, %lu bytes\n",
a->no, a->subno, (ulong)a->e_d.len);
}
if (a->e_d.buf == NULL && buflen && a->d.len < a->d.size)
{
unsigned size;
if (a->e_d.preferred && a->d.len < IOBUF_ZEROCOPY_THRESHOLD_SIZE)
size = IOBUF_ZEROCOPY_THRESHOLD_SIZE - a->d.len;
else
size = a->d.size - a->d.len;
if (size > buflen)
size = buflen;
memcpy (a->d.buf + a->d.len, buf, size);
buflen -= size;
buf += size;
a->d.len += size;
}
if (buflen)
{
rc = filter_flush (a);
if (rc)
{
a->e_d.buf = NULL;
a->e_d.len = 0;
return rc;
}
}
if (a->e_d.buf && a->e_d.used > 0)
{
buf += a->e_d.used;
buflen -= a->e_d.used;
}
a->e_d.buf = NULL;
a->e_d.len = 0;
}
while (buflen);
return 0;
}
int
iobuf_writestr (iobuf_t a, const char *buf)
{
if (a->use == IOBUF_INPUT || a->use == IOBUF_INPUT_TEMP)
{
log_bug ("iobuf_writestr called on an input pipeline!\n");
return -1;
}
return iobuf_write (a, buf, strlen (buf));
}
int
iobuf_write_temp (iobuf_t dest, iobuf_t source)
{
log_assert (source->use == IOBUF_OUTPUT || source->use == IOBUF_OUTPUT_TEMP);
log_assert (dest->use == IOBUF_OUTPUT || dest->use == IOBUF_OUTPUT_TEMP);
iobuf_flush_temp (source);
return iobuf_write (dest, source->d.buf, source->d.len);
}
size_t
iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen)
{
byte desc[MAX_IOBUF_DESC];
size_t n;
while (1)
{
int rc = filter_flush (a);
if (rc)
log_bug ("Flushing iobuf %d.%d (%s) from iobuf_temp_to_buffer failed. Ignoring.\n",
a->no, a->subno, iobuf_desc (a, desc));
if (! a->chain)
break;
a = a->chain;
}
n = a->d.len;
if (n > buflen)
n = buflen;
memcpy (buffer, a->d.buf, n);
return n;
}
/* Copies the data from the input iobuf SOURCE to the output iobuf
DEST until either an error is encountered or EOF is reached.
Returns the number of bytes copies or (size_t)(-1) on error. */
size_t
iobuf_copy (iobuf_t dest, iobuf_t source)
{
char *temp;
size_t temp_size;
size_t nread;
size_t nwrote = 0;
size_t max_read = 0;
int err;
log_assert (source->use == IOBUF_INPUT || source->use == IOBUF_INPUT_TEMP);
log_assert (dest->use == IOBUF_OUTPUT || source->use == IOBUF_OUTPUT_TEMP);
if (iobuf_error (dest))
return (size_t)(-1);
/* Use iobuf buffer size for temporary buffer. */
temp_size = iobuf_set_buffer_size(0) * 1024;
temp = xmalloc (temp_size);
while (1)
{
nread = iobuf_read (source, temp, temp_size);
if (nread == -1)
/* EOF. */
break;
if (nread > max_read)
max_read = nread;
err = iobuf_write (dest, temp, nread);
if (err)
break;
nwrote += nread;
}
/* Burn the buffer. */
if (max_read)
wipememory (temp, max_read);
xfree (temp);
return nwrote;
}
void
iobuf_flush_temp (iobuf_t temp)
{
if (temp->use == IOBUF_INPUT || temp->use == IOBUF_INPUT_TEMP)
log_bug ("iobuf_flush_temp called on an input pipeline!\n");
while (temp->chain)
iobuf_pop_filter (temp, temp->filter, NULL);
}
void
iobuf_set_limit (iobuf_t a, off_t nlimit)
{
if (nlimit)
a->nofast = 1;
else
a->nofast = 0;
a->nlimit = nlimit;
a->ntotal += a->nbytes;
a->nbytes = 0;
}
/* Return the length of the file behind A. If there is no file, return 0. */
uint64_t
iobuf_get_filelength (iobuf_t a)
{
/* Hmmm: file_filter may have already been removed */
for ( ; a->chain; a = a->chain )
;
if (a->filter != file_filter)
return 0;
{
file_filter_ctx_t *b = a->filter_ov;
gnupg_fd_t fp = b->fp;
#if defined(HAVE_W32_SYSTEM)
LARGE_INTEGER exsize;
if (GetFileSizeEx (fp, &exsize))
return exsize.QuadPart;
log_error ("GetFileSize for handle %p failed: %s\n",
fp, w32_strerror (-1));
#else /*!HAVE_W32_SYSTEM*/
struct stat st;
if ( !fstat (fp, &st) )
return st.st_size;
log_error("fstat() failed: %s\n", strerror(errno) );
#endif /*!HAVE_W32_SYSTEM*/
}
return 0;
}
gnupg_fd_t
iobuf_get_fd (iobuf_t a)
{
for (; a->chain; a = a->chain)
;
if (a->filter != file_filter)
return GNUPG_INVALID_FD;
{
file_filter_ctx_t *b = a->filter_ov;
gnupg_fd_t fp = b->fp;
return fp;
}
}
off_t
iobuf_tell (iobuf_t a)
{
return a->ntotal + a->nbytes;
}
#if !defined(HAVE_FSEEKO) && !defined(fseeko)
#ifdef HAVE_LIMITS_H
# include <limits.h>
#endif
#ifndef LONG_MAX
# define LONG_MAX ((long) ((unsigned long) -1 >> 1))
#endif
#ifndef LONG_MIN
# define LONG_MIN (-1 - LONG_MAX)
#endif
/****************
* A substitute for fseeko, for hosts that don't have it.
*/
static int
fseeko (FILE * stream, off_t newpos, int whence)
{
while (newpos != (long) newpos)
{
long pos = newpos < 0 ? LONG_MIN : LONG_MAX;
if (fseek (stream, pos, whence) != 0)
return -1;
newpos -= pos;
whence = SEEK_CUR;
}
return fseek (stream, (long) newpos, whence);
}
#endif
int
iobuf_seek (iobuf_t a, off_t newpos)
{
file_filter_ctx_t *b = NULL;
if (a->use == IOBUF_OUTPUT || a->use == IOBUF_INPUT)
{
/* Find the last filter in the pipeline. */
for (; a->chain; a = a->chain)
;
if (a->filter != file_filter)
return -1;
b = a->filter_ov;
#ifdef HAVE_W32_SYSTEM
if (SetFilePointer (b->fp, newpos, NULL, FILE_BEGIN) == 0xffffffff)
{
log_error ("SetFilePointer failed on handle %p: ec=%d\n",
b->fp, (int) GetLastError ());
return -1;
}
#else
if (lseek (b->fp, newpos, SEEK_SET) == (off_t) - 1)
{
log_error ("can't lseek: %s\n", strerror (errno));
return -1;
}
#endif
/* Discard the buffer it is not a temp stream. */
a->d.len = 0;
}
a->d.start = 0;
a->nbytes = 0;
a->nlimit = 0;
a->nofast = 0;
a->ntotal = newpos;
a->error = 0;
/* It is impossible for A->CHAIN to be non-NULL. If A is an INPUT
or OUTPUT buffer, then we find the last filter, which is defined
as A->CHAIN being NULL. If A is a TEMP filter, then A must be
the only filter in the pipe: when iobuf_push_filter adds a filter
to the front of a pipeline, it sets the new filter to be an
OUTPUT filter if the pipeline is an OUTPUT or TEMP pipeline and
to be an INPUT filter if the pipeline is an INPUT pipeline.
Thus, only the last filter in a TEMP pipeline can be a */
/* remove filters, but the last */
if (a->chain)
log_debug ("iobuf_pop_filter called in iobuf_seek - please report\n");
while (a->chain)
iobuf_pop_filter (a, a->filter, NULL);
return 0;
}
const char *
iobuf_get_real_fname (iobuf_t a)
{
if (a->real_fname)
return a->real_fname;
/* the old solution */
for (; a; a = a->chain)
if (!a->chain && a->filter == file_filter)
{
file_filter_ctx_t *b = a->filter_ov;
return b->print_only_name ? NULL : b->fname;
}
return NULL;
}
const char *
iobuf_get_fname (iobuf_t a)
{
for (; a; a = a->chain)
if (!a->chain && a->filter == file_filter)
{
file_filter_ctx_t *b = a->filter_ov;
return b->fname;
}
return NULL;
}
const char *
iobuf_get_fname_nonnull (iobuf_t a)
{
const char *fname;
fname = iobuf_get_fname (a);
return fname? fname : "[?]";
}
/****************
* Enable or disable partial body length mode (RFC 4880 4.2.2.4).
*
* If LEN is 0, this disables partial block mode by popping the
* partial body length filter, which must be the most recently
* added filter.
*
* If LEN is non-zero, it pushes a partial body length filter. If
* this is a read filter, LEN must be the length byte from the first
* chunk and A should be position just after this first partial body
* length header.
*/
void
iobuf_set_partial_body_length_mode (iobuf_t a, size_t len)
{
if (!len)
/* Disable partial body length mode. */
{
if (a->use == IOBUF_INPUT)
log_debug ("iobuf_pop_filter called in set_partial_block_mode"
" - please report\n");
log_assert (a->filter == block_filter);
iobuf_pop_filter (a, block_filter, NULL);
}
else
/* Enabled partial body length mode. */
{
block_filter_ctx_t *ctx = xcalloc (1, sizeof *ctx);
ctx->use = a->use;
ctx->partial = 1;
ctx->size = 0;
ctx->first_c = len;
iobuf_push_filter (a, block_filter, ctx);
}
}
unsigned int
iobuf_read_line (iobuf_t a, byte ** addr_of_buffer,
unsigned *length_of_buffer, unsigned *max_length)
{
int c;
char *buffer = (char *)*addr_of_buffer;
unsigned length = *length_of_buffer;
unsigned nbytes = 0;
unsigned maxlen = *max_length;
char *p;
/* The code assumes that we have space for at least a newline and a
NUL character in the buffer. This requires at least 2 bytes. We
don't complicate the code by handling the stupid corner case, but
simply assert that it can't happen. */
log_assert (!buffer || length >= 2 || maxlen >= 2);
if (!buffer || length <= 1)
/* must allocate a new buffer */
{
length = 256 <= maxlen ? 256 : maxlen;
buffer = xrealloc (buffer, length);
*addr_of_buffer = (unsigned char *)buffer;
*length_of_buffer = length;
}
p = buffer;
while (1)
{
if (!a->nofast && a->d.start < a->d.len && nbytes < length - 1)
/* Fast path for finding '\n' by using standard C library's optimized
memchr. */
{
unsigned size = a->d.len - a->d.start;
byte *newline_pos;
if (size > length - 1 - nbytes)
size = length - 1 - nbytes;
newline_pos = memchr (a->d.buf + a->d.start, '\n', size);
if (newline_pos)
{
/* Found newline, copy buffer and return. */
size = (newline_pos - (a->d.buf + a->d.start)) + 1;
memcpy (p, a->d.buf + a->d.start, size);
p += size;
nbytes += size;
a->d.start += size;
a->nbytes += size;
break;
}
else
{
/* No newline, copy buffer and continue. */
memcpy (p, a->d.buf + a->d.start, size);
p += size;
nbytes += size;
a->d.start += size;
a->nbytes += size;
}
}
else
{
c = iobuf_readbyte (a);
if (c == -1)
break;
*p++ = c;
nbytes++;
if (c == '\n')
break;
}
if (nbytes == length - 1)
/* We don't have enough space to add a \n and a \0. Increase
the buffer size. */
{
if (length == maxlen)
/* We reached the buffer's size limit! */
{
/* Skip the rest of the line. */
while ((c = iobuf_get (a)) != -1 && c != '\n')
;
/* p is pointing at the last byte in the buffer. We
always terminate the line with "\n\0" so overwrite
the previous byte with a \n. */
log_assert (p > buffer);
p[-1] = '\n';
/* Indicate truncation. */
*max_length = 0;
break;
}
length += length < 1024 ? 256 : 1024;
if (length > maxlen)
length = maxlen;
buffer = xrealloc (buffer, length);
*addr_of_buffer = (unsigned char *)buffer;
*length_of_buffer = length;
p = buffer + nbytes;
}
}
/* Add the terminating NUL. */
*p = 0;
/* Return the number of characters written to the buffer including
the newline, but not including the terminating NUL. */
return nbytes;
}
void
iobuf_skip_rest (iobuf_t a, unsigned long n, int partial)
{
if ( partial )
{
for (;;)
{
if (a->nofast || a->d.start >= a->d.len)
{
if (iobuf_readbyte (a) == -1)
{
break;
}
}
else
{
unsigned long count = a->d.len - a->d.start;
a->nbytes += count;
a->d.start = a->d.len;
}
}
}
else
{
unsigned long remaining = n;
while (remaining > 0)
{
if (a->nofast || a->d.start >= a->d.len)
{
if (iobuf_readbyte (a) == -1)
{
break;
}
--remaining;
}
else
{
unsigned long count = a->d.len - a->d.start;
if (count > remaining)
{
count = remaining;
}
a->nbytes += count;
a->d.start += count;
remaining -= count;
}
}
}
}
/* Check whether (BUF,LEN) is valid header for an OpenPGP compressed
* packet. LEN should be at least 6. */
static int
is_openpgp_compressed_packet (const unsigned char *buf, size_t len)
{
int c, ctb, pkttype;
int lenbytes;
ctb = *buf++; len--;
if (!(ctb & 0x80))
return 0; /* Invalid packet. */
if ((ctb & 0x40)) /* New style (OpenPGP) CTB. */
{
pkttype = (ctb & 0x3f);
if (!len)
return 0; /* Expected first length octet missing. */
c = *buf++; len--;
if (c < 192)
;
else if (c < 224)
{
if (!len)
return 0; /* Expected second length octet missing. */
}
else if (c == 255)
{
if (len < 4)
return 0; /* Expected length octets missing */
}
}
else /* Old style CTB. */
{
pkttype = (ctb>>2)&0xf;
lenbytes = ((ctb&3)==3)? 0 : (1<<(ctb & 3));
if (len < lenbytes)
return 0; /* Not enough length bytes. */
}
return (pkttype == 8);
}
/*
* Check if the file is compressed, by peeking the iobuf. You need to
* pass the iobuf with INP. Returns true if the buffer seems to be
* compressed.
*/
int
is_file_compressed (iobuf_t inp)
{
int i;
char buf[32];
int buflen;
struct magic_compress_s
{
byte len;
byte extchk;
byte magic[5];
} magic[] =
{
{ 3, 0, { 0x42, 0x5a, 0x68, 0x00 } }, /* bzip2 */
{ 3, 0, { 0x1f, 0x8b, 0x08, 0x00 } }, /* gzip */
{ 4, 0, { 0x50, 0x4b, 0x03, 0x04 } }, /* (pk)zip */
{ 5, 0, { '%', 'P', 'D', 'F', '-'} }, /* PDF */
{ 4, 1, { 0xff, 0xd8, 0xff, 0xe0 } }, /* Maybe JFIF */
{ 5, 2, { 0x89, 'P','N','G', 0x0d} } /* Likely PNG */
};
if (!inp)
return 0;
for ( ; inp->chain; inp = inp->chain )
;
buflen = iobuf_ioctl (inp, IOBUF_IOCTL_PEEK, sizeof buf, buf);
if (buflen < 0)
{
buflen = 0;
log_debug ("peeking at input failed\n");
}
if ( buflen < 6 )
{
return 0; /* Too short to check - assume uncompressed. */
}
for ( i = 0; i < DIM (magic); i++ )
{
if (!memcmp( buf, magic[i].magic, magic[i].len))
{
switch (magic[i].extchk)
{
case 0:
return 1; /* Is compressed. */
case 1:
if (buflen > 11 && !memcmp (buf + 6, "JFIF", 5))
return 1; /* JFIF: this likely a compressed JPEG. */
break;
case 2:
if (buflen > 8
&& buf[5] == 0x0a && buf[6] == 0x1a && buf[7] == 0x0a)
return 1; /* This is a PNG. */
break;
default:
break;
}
}
}
if (buflen >= 6 && is_openpgp_compressed_packet (buf, buflen))
{
return 1; /* Already compressed. */
}
return 0; /* Not detected as compressed. */
}
diff --git a/common/iobuf.h b/common/iobuf.h
index 4354c718d..5fa064c20 100644
--- a/common/iobuf.h
+++ b/common/iobuf.h
@@ -1,644 +1,644 @@
/* iobuf.h - I/O buffer
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_COMMON_IOBUF_H
#define GNUPG_COMMON_IOBUF_H
/* An iobuf is basically a filter in a pipeline.
Consider the following command, which consists of three filters
that are chained together:
$ cat file | base64 --decode | gunzip
The first filter reads the file from the file system and sends that
data to the second filter. The second filter decodes
base64-encoded data and sends the data to the third and last
filter. The last filter decompresses the data and the result is
displayed on the terminal. The iobuf system works in the same way
where each iobuf is a filter and the individual iobufs can be
chained together.
There are number of predefined filters. iobuf_open(), for
instance, creates a filter that reads from a specified file. And,
iobuf_temp_with_content() creates a filter that returns some
specified contents. There are also filters for writing content.
iobuf_openrw opens a file for writing. iobuf_temp creates a filter
that writes data to a fixed-sized buffer.
To chain filters together, you use the iobuf_push_filter()
function. The filters are chained together using the chain field
in the iobuf_t.
A pipeline can only be used for reading (IOBUF_INPUT) or for
writing (IOBUF_OUTPUT / IOBUF_OUTPUT_TEMP). When reading, data
flows from the last filter towards the first. That is, the user
calls iobuf_read(), the module reads from the first filter, which
gets its input from the second filter, etc. When writing, data
flows from the first filter towards the last. In this case, when
the user calls iobuf_write(), the data is written to the first
filter, which writes the transformed data to the second filter,
etc.
An iobuf_t contains some state about the filter. For instance, it
indicates if the filter has already returned EOF (filter_eof) and
the next filter in the pipeline, if any (chain). It also contains
a function pointer, filter. This is a generic function. It is
called when input is needed or output is available. In this case
it is passed a pointer to some filter-specific persistent state
(filter_ov), the actual operation, the next filter in the chain, if
any, and a buffer that either contains the contents to write, if
the pipeline is setup to write data, or is the place to store data,
if the pipeline is setup to read data.
Unlike a Unix pipeline, an IOBUF pipeline can return EOF multiple
times. This is similar to the following:
{ cat file1; cat file2; } | grep foo
However, instead of grep seeing a single stream, grep would see
each byte stream followed by an EOF marker. (When a filter returns
EOF, the EOF is returned to the user exactly once and then the
filter is removed from the pipeline.) */
/* For estream_t. */
#include <gpg-error.h>
#include "../common/types.h"
#include "../common/sysutils.h"
#define DBG_IOBUF iobuf_debug_mode
/* Filter control modes. */
enum
{
IOBUFCTRL_INIT = 1,
IOBUFCTRL_FREE = 2,
IOBUFCTRL_UNDERFLOW = 3,
IOBUFCTRL_FLUSH = 4,
IOBUFCTRL_DESC = 5,
IOBUFCTRL_CANCEL = 6,
IOBUFCTRL_PEEK = 7,
IOBUFCTRL_USER = 16
};
/* Command codes for iobuf_ioctl. */
typedef enum
{
IOBUF_IOCTL_KEEP_OPEN = 1, /* Uses intval. */
IOBUF_IOCTL_INVALIDATE_CACHE = 2, /* Uses ptrval. */
IOBUF_IOCTL_NO_CACHE = 3, /* Uses intval. */
IOBUF_IOCTL_FSYNC = 4, /* Uses ptrval. */
IOBUF_IOCTL_PEEK = 5 /* Uses intval and ptrval. */
} iobuf_ioctl_t;
enum iobuf_use
{
/* Pipeline is in input mode. The data flows from the end to the
beginning. That is, when reading from the pipeline, the first
filter gets its input from the second filter, etc. */
IOBUF_INPUT,
/* Pipeline is in input mode. The last filter in the pipeline is
a temporary buffer from which the data is "read". */
IOBUF_INPUT_TEMP,
/* Pipeline is in output mode. The data flows from the beginning
to the end. That is, when writing to the pipeline, the user
writes to the first filter, which transforms the data and sends
it to the second filter, etc. */
IOBUF_OUTPUT,
/* Pipeline is in output mode. The last filter in the pipeline is
a temporary buffer that grows as necessary. */
IOBUF_OUTPUT_TEMP
};
typedef struct iobuf_struct *iobuf_t;
typedef struct iobuf_struct *IOBUF; /* Compatibility with gpg 1.4. */
/* fixme: we should hide most of this stuff */
struct iobuf_struct
{
/* The type of filter. Either IOBUF_INPUT, IOBUF_OUTPUT or
IOBUF_OUTPUT_TEMP. */
enum iobuf_use use;
/* nlimit can be changed using iobuf_set_limit. If non-zero, it is
the number of additional bytes that can be read from the filter
before EOF is forcefully returned. */
off_t nlimit;
/* nbytes if the number of bytes that have been read (using
iobuf_get / iobuf_readbyte / iobuf_read) since the last call to
iobuf_set_limit. */
off_t nbytes;
/* The number of bytes read prior to the last call to
iobuf_set_limit. Thus, the total bytes read (i.e., the position
of stream) is ntotal + nbytes. */
off_t ntotal;
/* Whether we need to read from the filter one byte at a time or
whether we can do bulk reads. We need to read one byte at a time
if a limit (set via iobuf_set_limit) is active. */
int nofast;
/* A buffer for unread/unwritten data.
For an output pipeline (IOBUF_OUTPUT), this is the data that has
not yet been written to the filter. Consider a simple pipeline
consisting of a single stage, which writes to a file. When you
write to the pipeline (iobuf_writebyte or iobuf_write), the data
is first stored in this buffer. Only when the buffer is full or
you call iobuf_flush() is FILTER actually called and the data
written to the file.
For an input pipeline (IOBUF_INPUT), this is the data that has
been read from this filter, but not yet been read from the
preceding filter (or the user, if this filter is the head of the
pipeline). Again, consider a simple pipeline consisting of a
single stage. This stage reads from a file. If you read a
single byte (iobuf_get) and the buffer is empty, then FILTER is
called to fill the buffer. In this case, a single byte is not
requested, but the whole buffer is filled (if possible). */
struct
{
/* Size of the buffer. */
size_t size;
/* Number of bytes at the beginning of the buffer that have
already been consumed. (In other words: the index of the first
byte that hasn't been consumed.) This is only non-zero for
input filters. */
size_t start;
/* The number of bytes in the buffer including any bytes that have
been consumed. */
size_t len;
/* The buffer itself. */
byte *buf;
} d;
- /* A external drain buffer for reading/writting data skipping internal
+ /* A external drain buffer for reading/writing data skipping internal
draint buffer D.BUF. This allows zerocopy operation reducing
processing overhead across filter stack.
Used when by iobuf_read/iobuf_write when internal buffer has been
depleted and remaining external buffer length is large enough.
*/
struct
{
/* The external buffer provided by iobuf_read/iobuf_write caller. */
byte *buf;
/* The number of bytes in the external buffer. */
size_t len;
/* The number of bytes that were consumed from the external buffer. */
size_t used;
/* Gives hint for processing that the external buffer is preferred and
that internal buffer should be consumed early. */
int preferred;
} e_d;
/* When FILTER is called to read some data, it may read some data
and then return EOF. We can't return the EOF immediately.
Instead, we note that we observed the EOF and when the buffer is
finally empty, we return the EOF. */
int filter_eof;
/* Like filter_eof, when FILTER is called to read some data, it may
read some data and then return an error. We can't return the
error (in the form of an EOF) immediately. Instead, we note that
we observed the error and when the buffer is finally empty, we
return the EOF. */
int error;
/* The callback function to read data from the filter, etc. See
iobuf_filter_push for details. */
int (*filter) (void *opaque, int control,
iobuf_t chain, byte * buf, size_t * len);
/* An opaque pointer that can be used for local filter state. This
is passed as the first parameter to FILTER. */
void *filter_ov;
/* Whether the iobuf code should free(filter_ov) when destroying the
filter. */
int filter_ov_owner;
/* When using iobuf_open, iobuf_create, iobuf_openrw to open a file,
the file's name is saved here. This is used to delete the file
when an output pipeline (IOBUF_OUPUT) is canceled
(iobuf_cancel). */
char *real_fname;
/* The next filter in the pipeline. */
iobuf_t chain;
/* This field is for debugging. Each time a filter is allocated
(via iobuf_alloc()), a monotonically increasing counter is
incremented and this field is set to the new value. This field
should only be accessed via the iobuf_io macro. */
int no;
/* The number of filters in the pipeline following (not including)
this one. When you call iobuf_push_filter or iobuf_push_filter2,
this value is used to check the length of the pipeline if the
pipeline already contains 65 stages then these functions fail.
This amount of nesting typically indicates corrupted data or an
active denial of service attack. */
int subno;
};
extern int iobuf_debug_mode;
/* Change the default size for all IOBUFs to KILOBYTE. This needs to
* be called before any iobufs are used and can only be used once.
* Returns the current value. Using 0 has no effect except for
* returning the current value. */
unsigned int iobuf_set_buffer_size (unsigned int kilobyte);
/* Returns whether the specified filename corresponds to a pipe. In
particular, this function checks if FNAME is "-" and, if special
filenames are enabled (see check_special_filename), whether
FNAME is a special filename. */
int iobuf_is_pipe_filename (const char *fname);
/* Allocate a new filter. This filter doesn't have a function
assigned to it. Thus you need to manually set IOBUF->FILTER and
IOBUF->FILTER_OV, if required. This function is intended to help
create a new primary source or primary sink, i.e., the last filter
in the pipeline.
USE is IOBUF_INPUT, IOBUF_INPUT_TEMP, IOBUF_OUTPUT or
IOBUF_OUTPUT_TEMP.
BUFSIZE is the desired internal buffer size (that is, the size of
the typical read / write request). */
iobuf_t iobuf_alloc (int use, size_t bufsize);
/* Create an output filter that simply buffers data written to it.
This is useful for collecting data for later processing. The
buffer can be written to in the usual way (iobuf_write, etc.). The
data can later be extracted using iobuf_write_temp() or
iobuf_temp_to_buffer(). */
iobuf_t iobuf_temp (void);
/* Create an input filter that contains some data for reading. */
iobuf_t iobuf_temp_with_content (const char *buffer, size_t length);
/* Create an input file filter that reads from a file. If FNAME is
'-', reads from stdin. If special filenames are enabled
(iobuf_enable_special_filenames), then interprets special
filenames. */
iobuf_t iobuf_open (const char *fname);
/* Create an output file filter that writes to a file. If FNAME is
NULL or '-', writes to stdout. If special filenames are enabled
(iobuf_enable_special_filenames), then interprets special
filenames. If FNAME is not NULL, '-' or a special filename, the
file is opened for writing. If the file exists, it is truncated.
If MODE700 is TRUE, the file is created with mode 600. Otherwise,
mode 666 is used. */
iobuf_t iobuf_create (const char *fname, int mode700);
/* Create an output file filter that writes to a specified file.
Neither '-' nor special file names are recognized. */
iobuf_t iobuf_openrw (const char *fname);
/* Create a file filter using an existing file descriptor. If MODE
contains the letter 'w', creates an output filter. Otherwise,
creates an input filter. Note: MODE must reflect the file
descriptors actual mode! When the filter is destroyed, the file
descriptor is closed. */
iobuf_t iobuf_fdopen (gnupg_fd_t fd, const char *mode);
/* Like iobuf_fdopen, but doesn't close the file descriptor when the
filter is destroyed. */
iobuf_t iobuf_fdopen_nc (gnupg_fd_t fd, const char *mode);
/* Create a filter using an existing estream. If MODE contains the
letter 'w', creates an output filter. Otherwise, creates an input
filter. If KEEP_OPEN is TRUE, then the stream is not closed when
the filter is destroyed. Otherwise, the stream is closed when the
filter is destroyed. If READLIMIT is not 0 this gives a limit on
the number of bytes to read from estream. */
iobuf_t iobuf_esopen (estream_t estream, const char *mode, int keep_open,
size_t readlimit);
/* Create a filter using an existing socket. On Windows creates a
special socket filter. On non-Windows systems simply, this simply
calls iobuf_fdopen. */
iobuf_t iobuf_sockopen (int fd, const char *mode);
/* Set various options / perform different actions on a PIPELINE. See
the IOBUF_IOCTL_* macros above. */
int iobuf_ioctl (iobuf_t a, iobuf_ioctl_t cmd, int intval, void *ptrval);
/* Close a pipeline. The filters in the pipeline are first flushed
using iobuf_flush, if they are output filters, and then
IOBUFCTRL_FREE is called on each filter.
If any filter returns a non-zero value in response to the
IOBUFCTRL_FREE, that first such non-zero value is returned. Note:
processing is not aborted in this case. If all filters are freed
successfully, 0 is returned. */
int iobuf_close (iobuf_t iobuf);
/* Calls IOBUFCTRL_CANCEL on each filter in the pipeline. Then calls
io_close() on the pipeline. Finally, if the pipeline is an output
pipeline, deletes the file. Returns the result of calling
iobuf_close on the pipeline. */
int iobuf_cancel (iobuf_t iobuf);
/* Add a new filter to the front of a pipeline. A is the head of the
pipeline. F is the filter implementation. OV is an opaque pointer
that is passed to F and is normally used to hold any internal
state, such as a file pointer.
Note: you may only maintain a reference to an iobuf_t as a
reference to the head of the pipeline. That is, don't think about
setting a pointer in OV to point to the filter's iobuf_t. This is
because when we add a new filter to a pipeline, we memcpy the state
in A into new buffer. This has the advantage that there is no need
to update any references to the pipeline when a filter is added or
removed, but it also means that a filter's state moves around in
memory.
The behavior of the filter function is determined by the value of
the control parameter:
IOBUFCTRL_INIT: Called this value just before the filter is
linked into the pipeline. This can be used to initialize
internal data structures.
IOBUFCTRL_FREE: Called with this value just before the filter is
removed from the pipeline. Normally used to release internal
data structures, close a file handle, etc.
IOBUFCTRL_UNDERFLOW: Called with this value to fill the passed
buffer with more data. *LEN is the size of the buffer. Before
returning, it should be set to the number of bytes which were
written into the buffer. The function must return 0 to
indicate success, -1 on EOF and a GPG_ERR_xxxxx code for any
error.
Note: this function may both return data and indicate an error
or EOF. In this case, it simply writes the data to BUF, sets
*LEN and returns the appropriate return code. The implication
is that if an error occurs and no data has yet been written, it
is essential that *LEN be set to 0!
IOBUFCTRL_FLUSH: Called with this value to write out any
collected data. *LEN is the number of bytes in BUF that need
to be written out. Returns 0 on success and a GPG_ERR_* code
otherwise. *LEN must be set to the number of bytes that were
written out.
IOBUFCTRL_CANCEL: Called with this value when iobuf_cancel() is
called on the pipeline.
IOBUFCTRL_DESC: Called with this value to get a human-readable
description of the filter. *LEN is the size of the buffer.
The description is filled into BUF, NUL-terminated. Always
returns 0.
*/
int iobuf_push_filter (iobuf_t a, int (*f) (void *opaque, int control,
iobuf_t chain, byte * buf,
size_t * len), void *ov);
/* This variant of iobuf_push_filter allows the called to indicate
that OV should be freed when this filter is freed. That is, if
REL_OV is TRUE, then when the filter is popped or freed OV will be
freed after the filter function is called with control set to
IOBUFCTRL_FREE. */
int iobuf_push_filter2 (iobuf_t a,
int (*f) (void *opaque, int control, iobuf_t chain,
byte * buf, size_t * len), void *ov,
int rel_ov);
/* Pop the top filter. The top filter must have the filter function F
and the cookie OV. The cookie check is ignored if OV is NULL. */
int iobuf_pop_filter (iobuf_t a,
int (*f) (void *opaque, int control,
iobuf_t chain, byte * buf, size_t * len),
void *ov);
/* Used for debugging. Prints out the chain using log_debug if
IOBUF_DEBUG_MODE is not 0. */
int iobuf_print_chain (iobuf_t a);
/* Indicate that some error occurred on the specified filter. */
#define iobuf_set_error(a) do { (a)->error = 1; } while(0)
/* Return any pending error on filter A. */
#define iobuf_error(a) ((a)->error)
/* Limit the amount of additional data that may be read from the
filter. That is, if you've already read 100 bytes from A and you
set the limit to 50, then you can read up to an additional 50 bytes
(i.e., a total of 150 bytes) before EOF is forcefully returned.
Setting NLIMIT to 0 removes any active limit.
Note: using iobuf_seek removes any currently enforced limit! */
void iobuf_set_limit (iobuf_t a, off_t nlimit);
/* Returns the number of bytes that have been read from the pipeline.
Note: the result is undefined for IOBUF_OUTPUT and IOBUF_OUTPUT_TEMP
pipelines! */
off_t iobuf_tell (iobuf_t a);
/* There are two cases:
- If A is an INPUT or OUTPUT pipeline, then the last filter in the
pipeline is found. If that is not a file filter, -1 is returned.
Otherwise, an fseek(..., SEEK_SET) is performed on the file
descriptor.
- If A is a TEMP pipeline and the *first* (and thus only filter) is
a TEMP filter, then the "file position" is effectively unchanged.
That is, data is appended to the buffer and the seek does not
cause the size of the buffer to grow.
If no error occurred, then any limit previous set by
iobuf_set_limit() is cleared. Further, any error on the filter
(the file filter or the temp filter) is cleared.
Returns 0 on success and -1 if an error occurs. */
int iobuf_seek (iobuf_t a, off_t newpos);
/* Read a single byte. If a filter has no more data, returns -1 to
indicate the EOF. Generally, you don't want to use this function,
but instead prefer the iobuf_get macro, which is faster if there is
data in the internal buffer. */
int iobuf_readbyte (iobuf_t a);
/* Get a byte from the iobuf; must check for eof prior to this
function. This function returns values in the range 0 .. 255 or -1
to indicate EOF. iobuf_get_noeof() does not return -1 to indicate
EOF, but masks the returned value to be in the range 0 .. 255. */
#define iobuf_get(a) \
( ((a)->nofast || (a)->d.start >= (a)->d.len )? \
iobuf_readbyte((a)) : ( (a)->nbytes++, (a)->d.buf[(a)->d.start++] ) )
#define iobuf_get_noeof(a) (iobuf_get((a))&0xff)
/* Fill BUF with up to BUFLEN bytes. If a filter has no more data,
returns -1 to indicate the EOF. Otherwise returns the number of
bytes read. */
int iobuf_read (iobuf_t a, void *buf, unsigned buflen);
/* Read a line of input (including the '\n') from the pipeline.
The semantics are the same as for fgets(), but if the buffer is too
short a larger one will be allocated up to *MAX_LENGTH and the end
of the line except the trailing '\n' discarded. (Thus,
*ADDR_OF_BUFFER must be allocated using malloc().) If the buffer
is enlarged, then *LENGTH_OF_BUFFER will be updated to reflect the
new size. If the line is truncated, then *MAX_LENGTH will be set
to 0. If *ADDR_OF_BUFFER is NULL, a buffer is allocated using
malloc().
A line is considered a byte stream ending in a '\n'. Returns the
number of characters written to the buffer (i.e., excluding any
discarded characters due to truncation). Thus, use this instead of
strlen(buffer) to determine the length of the string as this is
unreliable if the input contains NUL characters.
EOF is indicated by a line of length zero.
The last LF may be missing due to an EOF. */
unsigned iobuf_read_line (iobuf_t a, byte ** addr_of_buffer,
unsigned *length_of_buffer, unsigned *max_length);
/* Read up to BUFLEN bytes from pipeline A. Note: this function can't
return more than the pipeline's internal buffer size. The return
value is the number of bytes actually written to BUF. If the
filter returns EOF, then this function returns -1.
This function does not clear any pending EOF. That is, if the
pipeline consists of two filters and the first one returns EOF
during the peek, then the subsequent iobuf_read* will still return
EOF before returning the data from the second filter. */
int iobuf_peek (iobuf_t a, byte * buf, unsigned buflen);
/* Write a byte to the pipeline. Returns 0 on success and an error
code otherwise. */
int iobuf_writebyte (iobuf_t a, unsigned c);
/* Alias for iobuf_writebyte. */
#define iobuf_put(a,c) iobuf_writebyte(a,c)
/* Write a sequence of bytes to the pipeline. Returns 0 on success
and an error code otherwise. */
int iobuf_write (iobuf_t a, const void *buf, unsigned buflen);
/* Write a string (not including the NUL terminator) to the pipeline.
Returns 0 on success and an error code otherwise. */
int iobuf_writestr (iobuf_t a, const char *buf);
/* Flushes the pipeline removing all filters but the sink (the last
filter) in the process. */
void iobuf_flush_temp (iobuf_t temp);
/* Flushes the pipeline SOURCE removing all filters but the sink (the
last filter) in the process (i.e., it calls
iobuf_flush_temp(source)) and then writes the data to the pipeline
DEST. Note: this doesn't free (iobuf_close()) SOURCE. Both SOURCE
and DEST must be output pipelines. */
int iobuf_write_temp (iobuf_t dest, iobuf_t source);
/* Flushes each filter in the pipeline (i.e., sends any buffered data
to the filter by calling IOBUFCTRL_FLUSH). Then, copies up to the
first BUFLEN bytes from the last filter's internal buffer (which
will only be non-empty if it is a temp filter) to the buffer
BUFFER. Returns the number of bytes actually copied. */
size_t iobuf_temp_to_buffer (iobuf_t a, byte * buffer, size_t buflen);
/* Copies the data from the input iobuf SOURCE to the output iobuf
DEST until either an error is encountered or EOF is reached.
Returns the number of bytes successfully written. If an error
occurred, then any buffered bytes are not returned to SOURCE and are
effectively lost. To check if an error occurred, use
iobuf_error. */
size_t iobuf_copy (iobuf_t dest, iobuf_t source);
/* Return the size of any underlying file. This only works with
file_filter based pipelines. */
uint64_t iobuf_get_filelength (iobuf_t a);
#define IOBUF_FILELENGTH_LIMIT 0xffffffff
/* Return the file descriptor designating the underlying file. This
only works with file_filter based pipelines. */
gnupg_fd_t iobuf_get_fd (iobuf_t a);
/* Return the real filename, if available. This only supports
pipelines that end in file filters. Returns NULL if not
available. */
const char *iobuf_get_real_fname (iobuf_t a);
/* Return the filename or a description thereof. For instance, for
iobuf_open("-"), this will return "[stdin]". This only supports
pipelines that end in file filters. Returns NULL if not
available. */
const char *iobuf_get_fname (iobuf_t a);
/* Like iobuf_getfname, but instead of returning NULL if no
description is available, return "[?]". */
const char *iobuf_get_fname_nonnull (iobuf_t a);
/* Pushes a filter on the pipeline that interprets the datastream as
an OpenPGP data block whose length is encoded using partial body
length headers (see Section 4.2.2.4 of RFC 4880). Concretely, it
just returns / writes the data and finishes the packet with an
EOF. */
void iobuf_set_partial_body_length_mode (iobuf_t a, size_t len);
/* If PARTIAL is set, then read from the pipeline until the first EOF
is returned.
If PARTIAL is 0, then read up to N bytes or until the first EOF is
returned.
Recall: a filter can return EOF. In this case, it and all
preceding filters are popped from the pipeline and the next read is
from the following filter (which may or may not return EOF). */
void iobuf_skip_rest (iobuf_t a, unsigned long n, int partial);
/* Check if the file is compressed, by peeking the iobuf. */
int is_file_compressed (iobuf_t inp);
#define iobuf_where(a) "[don't know]"
/* Each time a filter is allocated (via iobuf_alloc()), a
monotonically increasing counter is incremented and this field is
set to the new value. This macro returns that number. */
#define iobuf_id(a) ((a)->no)
#define iobuf_get_temp_buffer(a) ( (a)->d.buf )
#define iobuf_get_temp_length(a) ( (a)->d.len )
/* Whether the filter uses an in-memory buffer. */
#define iobuf_is_temp(a) ( (a)->use == IOBUF_OUTPUT_TEMP )
#endif /*GNUPG_COMMON_IOBUF_H*/
diff --git a/common/mkdir_p.h b/common/mkdir_p.h
index 1e939b32b..08f388916 100644
--- a/common/mkdir_p.h
+++ b/common/mkdir_p.h
@@ -1,52 +1,52 @@
/* mkdir_p.h - Create a directory and any missing parents.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef MKDIR_P_H
#define MKDIR_P_H
#include "types.h"
/* Create a directory as well as any missing parents.
- The arguments must be NULL termianted. If DIRECTORY_COMPONENTS...
+ The arguments must be NULL terminated. If DIRECTORY_COMPONENTS...
consists of two elements, "foo/bar" and "xyzzy", this function will
first try to create the directory "foo/bar" and then the directory
"foo/bar/xyzzy". On success returns 0, otherwise an error code is
returned. */
gpg_error_t gnupg_mkdir_p (const char *directory_component, ...) GPGRT_ATTR_SENTINEL(0);
/* Like mkdir_p, but DIRECTORY_COMPONENTS is a NULL terminated
array, e.g.:
char **dirs = { "foo", "bar", NULL };
amkdir_p (dirs);
*/
gpg_error_t gnupg_amkdir_p (const char **directory_components);
#endif
diff --git a/common/openpgp-fpr.c b/common/openpgp-fpr.c
index 7b110085f..7860d3872 100644
--- a/common/openpgp-fpr.c
+++ b/common/openpgp-fpr.c
@@ -1,283 +1,283 @@
/* openpgp-fpr.c - OpenPGP Fingerprint computation
* Copyright (C) 2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later)
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include "util.h"
#include "openpgpdefs.h"
/* Count the number of bits, assuming the A represents an unsigned big
* integer of length LEN bytes. */
static unsigned int
count_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
for (; len && !*a; len--, a++, n -=8)
;
if (len)
{
for (i=7; i && !(*a & (1<<i)); i--)
n--;
}
return n;
}
/* Variant of count_bits for simple octet strings. */
static unsigned int
count_sos_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
if (len == 0 || *a == 0)
return n;
for (i=7; i && !(*a & (1<<i)); i--)
n--;
return n;
}
gpg_error_t
compute_openpgp_fpr (int keyversion, int pgpalgo, unsigned long timestamp,
gcry_buffer_t *iov, int iovcnt,
unsigned char *result, unsigned int *r_resultlen)
{
gpg_error_t err;
int hashalgo;
unsigned char prefix[15];
size_t n;
int i;
if (r_resultlen)
*r_resultlen = 0;
if (iovcnt < 2)
return gpg_error (GPG_ERR_INV_ARG);
/* Note that iov[0] is reserved. */
for (n=0, i=1; i < iovcnt; i++)
n += iov[i].len;
i = 0;
if (keyversion == 5)
{
hashalgo = GCRY_MD_SHA256;
n += 10; /* Add the prefix length. */
prefix[i++] = 0x9a;
prefix[i++] = (n >> 24);
prefix[i++] = (n >> 16);
}
else if (keyversion == 4)
{
hashalgo = GCRY_MD_SHA1;
n += 6; /* Add the prefix length. */
prefix[i++] = 0x99;
}
else
return gpg_error (GPG_ERR_UNKNOWN_VERSION);
prefix[i++] = (n >> 8);
prefix[i++] = n;
prefix[i++] = keyversion;
prefix[i++] = (timestamp >> 24);
prefix[i++] = (timestamp >> 16);
prefix[i++] = (timestamp >> 8);
prefix[i++] = (timestamp);
prefix[i++] = pgpalgo;
if (keyversion == 5)
{
prefix[i++] = ((n-10) >> 24);
prefix[i++] = ((n-10) >> 16);
prefix[i++] = ((n-10) >> 8);
prefix[i++] = (n-10);
}
log_assert (i <= sizeof prefix);
/* The first element is reserved for our use; set it. */
iov[0].size = 0;
iov[0].off = 0;
iov[0].len = i;
iov[0].data = prefix;
/* for (i=0; i < iovcnt; i++) */
/* log_printhex (iov[i].data, iov[i].len, "cmpfpr i=%d: ", i); */
err = gcry_md_hash_buffers (hashalgo, 0, result, iov, iovcnt);
- /* log_printhex (result, 20, "fingerpint: "); */
+ /* log_printhex (result, 20, "fingerprint: "); */
/* Better clear the first element because it was set by us. */
iov[0].size = 0;
iov[0].off = 0;
iov[0].len = 0;
iov[0].data = NULL;
if (!err && r_resultlen)
*r_resultlen = (hashalgo == GCRY_MD_SHA1)? 20 : 32;
return err;
}
gpg_error_t
compute_openpgp_fpr_rsa (int keyversion, unsigned long timestamp,
const unsigned char *m, unsigned int mlen,
const unsigned char *e, unsigned int elen,
unsigned char *result, unsigned int *r_resultlen)
{
gcry_buffer_t iov[5] = { {0} };
unsigned char nbits_m[2], nbits_e[2];
unsigned int n;
/* Strip leading zeroes. */
for (; mlen && !*m; mlen--, m++)
;
for (; elen && !*e; elen--, e++)
;
/* Count bits. */
n = count_bits (m, mlen);
nbits_m[0] = n >> 8;
nbits_m[1] = n;
n = count_bits (e, elen);
nbits_e[0] = n >> 8;
nbits_e[1] = n;
/* Put parms into the array. Note that iov[0] is reserved. */
iov[1].len = 2;
iov[1].data = nbits_m;
iov[2].len = mlen;
iov[2].data = (void*)m;
iov[3].len = 2;
iov[3].data = nbits_e;
iov[4].len = elen;
iov[4].data = (void*)e;
return compute_openpgp_fpr (keyversion, PUBKEY_ALGO_RSA, timestamp,
iov, 5, result, r_resultlen);
}
/* Determine KDF hash algorithm and KEK encryption algorithm by CURVE.
* The returned buffer has a length of 4.
* Note: This needs to be kept in sync with the table in g10/ecdh.c */
static const unsigned char*
default_ecdh_params (unsigned int nbits)
{
/* See RFC-6637 for those constants.
0x03: Number of bytes
0x01: Version for this parameter format
KEK digest algorithm
KEK cipher algorithm
*/
if (nbits <= 256)
return (const unsigned char*)"\x03\x01\x08\x07";
else if (nbits <= 384)
return (const unsigned char*)"\x03\x01\x09\x09";
else
return (const unsigned char*)"\x03\x01\x0a\x09";
}
gpg_error_t
compute_openpgp_fpr_ecc (int keyversion, unsigned long timestamp,
const char *curvename, int for_encryption,
const unsigned char *q, unsigned int qlen,
const unsigned char *kdf, unsigned int kdflen,
unsigned char *result, unsigned int *r_resultlen)
{
gpg_error_t err;
const char *curveoidstr;
gcry_mpi_t curveoid = NULL;
unsigned int curvebits;
int pgpalgo;
const unsigned char *oidraw;
size_t oidrawlen;
gcry_buffer_t iov[5] = { {0} };
unsigned int iovlen;
unsigned char nbits_q[2];
unsigned int n;
curveoidstr = openpgp_curve_to_oid (curvename, &curvebits, &pgpalgo);
err = openpgp_oid_from_str (curveoidstr, &curveoid);
if (err)
goto leave;
oidraw = gcry_mpi_get_opaque (curveoid, &n);
if (!oidraw)
{
err = gpg_error_from_syserror ();
goto leave;
}
oidrawlen = (n+7)/8;
/* If the curve does not enforce a certain algorithm, we use the
* for_encryption flag to decide which algo to use. */
if (!pgpalgo)
pgpalgo = for_encryption? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
/* Count bits. */
n = count_sos_bits (q, qlen);
nbits_q[0] = n >> 8;
nbits_q[1] = n;
/* Put parms into the array. Note that iov[0] is reserved. */
iov[1].len = oidrawlen;
iov[1].data = (void*)oidraw;
iov[2].len = 2;
iov[2].data = nbits_q;
iov[3].len = qlen;
iov[3].data = (void*)q;
iovlen = 4;
if (pgpalgo == PUBKEY_ALGO_ECDH)
{
if (!kdf || !kdflen || !kdf[0])
{
/* No KDF given - use the default. */
kdflen = 4;
kdf = default_ecdh_params (curvebits);
}
iov[4].len = kdflen;
iov[4].data = (void*)kdf;
iovlen++;
}
err = compute_openpgp_fpr (keyversion, pgpalgo, timestamp,
iov, iovlen, result, r_resultlen);
leave:
gcry_mpi_release (curveoid);
return err;
}
diff --git a/common/openpgp-oid.c b/common/openpgp-oid.c
index 92f0dfbcd..0a7aae000 100644
--- a/common/openpgp-oid.c
+++ b/common/openpgp-oid.c
@@ -1,781 +1,781 @@
/* openpgp-oids.c - OID helper for OpenPGP
* Copyright (C) 2011 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include "util.h"
#include "openpgpdefs.h"
/* A table with all our supported OpenPGP curves. */
static struct {
const char *name; /* Standard name. */
const char *oidstr; /* IETF formatted OID. */
unsigned int nbits; /* Nominal bit length of the curve. */
const char *alias; /* NULL or alternative name of the curve. */
const char *abbr; /* NULL or abbreviated name of the curve. */
int pubkey_algo; /* Required OpenPGP algo or 0 for ECDSA/ECDH. */
enum gcry_kem_algos kem_algo; /* 0 or the KEM algorithm for PQC. */
} oidtable[] = {
{ "Curve25519", "1.3.6.1.4.1.3029.1.5.1", 255, "cv25519", NULL,
PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X25519 /* only during development */},
{ "Ed25519", "1.3.6.1.4.1.11591.15.1", 255, "ed25519", NULL,
PUBKEY_ALGO_EDDSA },
{ "Curve25519", "1.3.101.110", 255, "cv25519", NULL,
PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X25519 },
{ "Ed25519", "1.3.101.112", 255, "ed25519", NULL,
PUBKEY_ALGO_EDDSA },
{ "X448", "1.3.101.111", 448, "cv448", NULL,
PUBKEY_ALGO_ECDH, GCRY_KEM_RAW_X448 },
{ "Ed448", "1.3.101.113", 456, "ed448", NULL,
PUBKEY_ALGO_EDDSA },
{ "NIST P-256", "1.2.840.10045.3.1.7", 256, "nistp256" },
{ "NIST P-384", "1.3.132.0.34", 384, "nistp384" },
{ "NIST P-521", "1.3.132.0.35", 521, "nistp521" },
{ "brainpoolP256r1", "1.3.36.3.3.2.8.1.1.7", 256, NULL, "bp256",
0, GCRY_KEM_RAW_BP256 },
{ "brainpoolP384r1", "1.3.36.3.3.2.8.1.1.11", 384, NULL, "bp384",
0, GCRY_KEM_RAW_BP384 },
{ "brainpoolP512r1", "1.3.36.3.3.2.8.1.1.13", 512, NULL, "bp512",
0, GCRY_KEM_RAW_BP512 },
{ "secp256k1", "1.3.132.0.10", 256 },
{ NULL, NULL, 0}
};
/* The OID for Curve Ed25519 in OpenPGP format. The shorter v5
* variant may only be used with v5 keys. */
static const char oid_ed25519[] =
{ 0x09, 0x2b, 0x06, 0x01, 0x04, 0x01, 0xda, 0x47, 0x0f, 0x01 };
static const char oid_ed25519_v5[] = { 0x03, 0x2b, 0x65, 0x70 };
/* The OID for Curve25519 in OpenPGP format. The shorter v5
* variant may only be used with v5 keys. */
static const char oid_cv25519[] =
{ 0x0a, 0x2b, 0x06, 0x01, 0x04, 0x01, 0x97, 0x55, 0x01, 0x05, 0x01 };
static const char oid_cv25519_v5[] = { 0x03, 0x2b, 0x65, 0x6e };
/* The OID for X448 in OpenPGP format. */
/*
* Here, we have a little semantic discrepancy. X448 is the name of
* the ECDH computation and the OID is assigned to the algorithm in
* RFC 8410. Note that this OID is not the one which is assigned to
* the curve itself (originally in 8410). Nevertheless, we use "X448"
* for the curve in libgcrypt.
*/
static const char oid_cv448[] = { 0x03, 0x2b, 0x65, 0x6f };
/* The OID for Ed448 in OpenPGP format. */
static const char oid_ed448[] = { 0x03, 0x2b, 0x65, 0x71 };
/* A table to store keyalgo strings like "rsa2048 or "ed25519" so that
* we do not need to allocate them. This is currently a simple array
* but may eventually be changed to a fast data structure. Noet that
* unknown algorithms are stored with (NBITS,CURVE) set to (0,NULL). */
struct keyalgo_string_s
{
enum gcry_pk_algos algo; /* Mandatory. */
unsigned int nbits; /* Size for classical algos. */
char *curve; /* Curvename (OID) or NULL. */
char *name; /* Allocated name. */
};
static struct keyalgo_string_s *keyalgo_strings; /* The table. */
static size_t keyalgo_strings_size; /* Allocated size. */
static size_t keyalgo_strings_used; /* Used size. */
/* Helper for openpgp_oid_from_str. */
static size_t
make_flagged_int (unsigned long value, char *buf, size_t buflen)
{
int more = 0;
int shift;
/* fixme: figure out the number of bits in an ulong and start with
that value as shift (after making it a multiple of 7) a more
- straigtforward implementation is to do it in reverse order using
+ straightforward implementation is to do it in reverse order using
a temporary buffer - saves a lot of compares */
for (more=0, shift=28; shift > 0; shift -= 7)
{
if (more || value >= (1<<shift))
{
buf[buflen++] = 0x80 | (value >> shift);
value -= (value >> shift) << shift;
more = 1;
}
}
buf[buflen++] = value;
return buflen;
}
/* Convert the OID given in dotted decimal form in STRING to an DER
* encoding and store it as an opaque value at R_MPI. The format of
* the DER encoded is not a regular ASN.1 object but the modified
* format as used by OpenPGP for the ECC curve description. On error
* the function returns and error code an NULL is stored at R_BUG.
* Note that scanning STRING stops at the first white space
* character. */
gpg_error_t
openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi)
{
unsigned char *buf;
size_t buflen;
unsigned long val1, val;
const char *endp;
int arcno;
*r_mpi = NULL;
if (!string || !*string)
return gpg_error (GPG_ERR_INV_VALUE);
/* We can safely assume that the encoded OID is shorter than the string. */
buf = xtrymalloc (1 + strlen (string) + 2);
if (!buf)
return gpg_error_from_syserror ();
/* Save the first byte for the length. */
buflen = 1;
val1 = 0; /* Avoid compiler warning. */
arcno = 0;
do {
arcno++;
val = strtoul (string, (char**)&endp, 10);
if (!digitp (string) || !(*endp == '.' || !*endp))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_OID_STRING);
}
if (*endp == '.')
string = endp+1;
if (arcno == 1)
{
if (val > 2)
break; /* Not allowed, error caught below. */
val1 = val;
}
else if (arcno == 2)
{ /* Need to combine the first two arcs in one octet. */
if (val1 < 2)
{
if (val > 39)
{
xfree (buf);
return gpg_error (GPG_ERR_INV_OID_STRING);
}
buf[buflen++] = val1*40 + val;
}
else
{
val += 80;
buflen = make_flagged_int (val, buf, buflen);
}
}
else
{
buflen = make_flagged_int (val, buf, buflen);
}
} while (*endp == '.');
if (arcno == 1 || buflen < 2 || buflen > 254 )
{ /* It is not possible to encode only the first arc. */
xfree (buf);
return gpg_error (GPG_ERR_INV_OID_STRING);
}
*buf = buflen - 1;
*r_mpi = gcry_mpi_set_opaque (NULL, buf, buflen * 8);
if (!*r_mpi)
{
xfree (buf);
return gpg_error_from_syserror ();
}
return 0;
}
/* Return a malloced string representation of the OID in the buffer
* (BUF,LEN). In case of an error NULL is returned and ERRNO is set.
* As per OpenPGP spec the first byte of the buffer is the length of
* the rest; the function performs a consistency check. */
char *
openpgp_oidbuf_to_str (const unsigned char *buf, size_t len)
{
char *string, *p;
int n = 0;
unsigned long val, valmask;
valmask = (unsigned long)0xfe << (8 * (sizeof (valmask) - 1));
/* The first bytes gives the length; check consistency. */
if (!len || buf[0] != len -1)
{
gpg_err_set_errno (EINVAL);
return NULL;
}
/* Skip length byte. */
len--;
buf++;
/* To calculate the length of the string we can safely assume an
upper limit of 3 decimal characters per byte. Two extra bytes
account for the special first octet */
string = p = xtrymalloc (len*(1+3)+2+1);
if (!string)
return NULL;
if (!len)
{
*p = 0;
return string;
}
if (buf[0] < 40)
p += sprintf (p, "0.%d", buf[n]);
else if (buf[0] < 80)
p += sprintf (p, "1.%d", buf[n]-40);
else {
val = buf[n] & 0x7f;
while ( (buf[n]&0x80) && ++n < len )
{
if ( (val & valmask) )
goto badoid; /* Overflow. */
val <<= 7;
val |= buf[n] & 0x7f;
}
if (val < 80)
goto badoid;
val -= 80;
sprintf (p, "2.%lu", val);
p += strlen (p);
}
for (n++; n < len; n++)
{
val = buf[n] & 0x7f;
while ( (buf[n]&0x80) && ++n < len )
{
if ( (val & valmask) )
goto badoid; /* Overflow. */
val <<= 7;
val |= buf[n] & 0x7f;
}
sprintf (p, ".%lu", val);
p += strlen (p);
}
*p = 0;
return string;
badoid:
/* Return a special OID (gnu.gnupg.badoid) to indicate the error
case. The OID is broken and thus we return one which can't do
any harm. Formally this does not need to be a bad OID but an OID
with an arc that can't be represented in a 32 bit word is more
than likely corrupt. */
xfree (string);
return xtrystrdup ("1.3.6.1.4.1.11591.2.12242973");
}
/* Return a malloced string representation of the OID in the opaque
* MPI A. In case of an error NULL is returned and ERRNO is set. */
char *
openpgp_oid_to_str (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int lengthi;
if (!a
|| !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE)
|| !(buf = gcry_mpi_get_opaque (a, &lengthi)))
{
gpg_err_set_errno (EINVAL);
return NULL;
}
return openpgp_oidbuf_to_str (buf, (lengthi+7)/8);
}
/* Return true if (BUF,LEN) represents the OID for Ed25519. */
int
openpgp_oidbuf_is_ed25519 (const void *buf, size_t len)
{
if (!buf)
return 0;
return ((len == DIM (oid_ed25519)
&& !memcmp (buf, oid_ed25519, DIM (oid_ed25519)))
|| (len == DIM (oid_ed25519_v5)
&& !memcmp (buf, oid_ed25519_v5, DIM (oid_ed25519_v5))));
}
/* Return true if A represents the OID for Ed25519. */
int
openpgp_oid_is_ed25519 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_ed25519 (buf, (nbits+7)/8);
}
/* Return true if (BUF,LEN) represents the OID for Curve25519. */
int
openpgp_oidbuf_is_cv25519 (const void *buf, size_t len)
{
if (!buf)
return 0;
return ((len == DIM (oid_cv25519)
&& !memcmp (buf, oid_cv25519, DIM (oid_cv25519)))
|| (len == DIM (oid_cv25519_v5)
&& !memcmp (buf, oid_cv25519_v5, DIM (oid_cv25519_v5))));
}
/* Return true if (BUF,LEN) represents the OID for Ed448. */
static int
openpgp_oidbuf_is_ed448 (const void *buf, size_t len)
{
return (buf && len == DIM (oid_ed448)
&& !memcmp (buf, oid_ed448, DIM (oid_ed448)));
}
/* Return true if (BUF,LEN) represents the OID for X448. */
static int
openpgp_oidbuf_is_cv448 (const void *buf, size_t len)
{
return (buf && len == DIM (oid_cv448)
&& !memcmp (buf, oid_cv448, DIM (oid_cv448)));
}
/* Return true if the MPI A represents the OID for Curve25519. */
int
openpgp_oid_is_cv25519 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_cv25519 (buf, (nbits+7)/8);
}
/* Return true if the MPI A represents the OID for Ed448. */
int
openpgp_oid_is_ed448 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_ed448 (buf, (nbits+7)/8);
}
/* Return true if the MPI A represents the OID for X448. */
int
openpgp_oid_is_cv448 (gcry_mpi_t a)
{
const unsigned char *buf;
unsigned int nbits;
if (!a || !gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
return 0;
buf = gcry_mpi_get_opaque (a, &nbits);
return openpgp_oidbuf_is_cv448 (buf, (nbits+7)/8);
}
/* Map the Libgcrypt ECC curve NAME to an OID. If R_NBITS is not NULL
store the bit size of the curve there. Returns NULL for unknown
curve names. If R_ALGO is not NULL and a specific ECC algorithm is
required for this curve its OpenPGP algorithm number is stored
there; otherwise 0 is stored which indicates that ECDSA or ECDH can
be used. */
const char *
openpgp_curve_to_oid (const char *name, unsigned int *r_nbits, int *r_algo)
{
int i;
unsigned int nbits = 0;
const char *oidstr = NULL;
int algo = 0;
if (name)
{
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (oidtable[i].name, name)
|| (oidtable[i].alias
&& !ascii_strcasecmp (oidtable[i].alias, name)))
{
oidstr = oidtable[i].oidstr;
nbits = oidtable[i].nbits;
algo = oidtable[i].pubkey_algo;
break;
}
if (!oidtable[i].name)
{
/* If not found assume the input is already an OID and check
whether we support it. */
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (name, oidtable[i].oidstr))
{
oidstr = oidtable[i].oidstr;
nbits = oidtable[i].nbits;
algo = oidtable[i].pubkey_algo;
break;
}
}
}
if (r_nbits)
*r_nbits = nbits;
if (r_algo)
*r_algo = algo;
return oidstr;
}
/* Map an OpenPGP OID to the Libgcrypt curve name. Returns NULL for
* unknown curve names. MODE defines which version of the curve name
* is returned. For example:
*
* | OID | mode=0 | mode=1 | mode=2 |
* |----------------------+-----------------+-----------------+----------|
* | 1.2.840.10045.3.1.7 | nistp256 | NIST P-256 | nistp256 |
* | 1.3.36.3.3.2.8.1.1.7 | brainpoolP256r1 | brainpoolP256r1 | bp256 |
*
* Thus mode 0 returns the name as commonly used gpg, mode 1 returns
* the canonical name, and mode 2 prefers an abbreviated name over the
* commonly used name.
*/
const char *
openpgp_oid_to_curve (const char *oidstr, int mode)
{
int i;
if (!oidstr)
return NULL;
for (i=0; oidtable[i].name; i++)
if (!strcmp (oidtable[i].oidstr, oidstr))
{
if (mode == 2)
{
if (oidtable[i].abbr)
return oidtable[i].abbr;
mode = 0; /* No abbreviation - fallback to mode 0. */
}
return !mode && oidtable[i].alias? oidtable[i].alias : oidtable[i].name;
}
return NULL;
}
/* Map an OpenPGP OID, name or alias to the Libgcrypt curve name.
* Returns NULL for unknown curve names. Unless CANON is set we
* prefer an alias name here which is more suitable for printing. */
const char *
openpgp_oid_or_name_to_curve (const char *oidname, int canon)
{
int i;
if (!oidname)
return NULL;
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (oidtable[i].oidstr, oidname)
|| !ascii_strcasecmp (oidtable[i].name, oidname)
|| (oidtable[i].alias
&& !ascii_strcasecmp (oidtable[i].alias, oidname)))
return !canon && oidtable[i].alias? oidtable[i].alias : oidtable[i].name;
return NULL;
}
/* Return the KEM algorithm id for the curve with OIDNAME. */
enum gcry_kem_algos
openpgp_oid_to_kem_algo (const char *oidname)
{
int i;
if (!oidname)
return 0;
for (i=0; oidtable[i].name; i++)
if (!strcmp (oidtable[i].oidstr, oidname))
return oidtable[i].kem_algo;
for (i=0; oidtable[i].name; i++)
if (!ascii_strcasecmp (oidtable[i].name, oidname)
|| (oidtable[i].alias
&& !ascii_strcasecmp (oidtable[i].alias, oidname)))
return oidtable[i].kem_algo;
return 0;
}
/* Return true if the curve with NAME is supported. */
static int
curve_supported_p (const char *name)
{
int result = 0;
gcry_sexp_t keyparms;
if (!gcry_sexp_build (&keyparms, NULL, "(public-key(ecc(curve %s)))", name))
{
result = !!gcry_pk_get_curve (keyparms, 0, NULL);
gcry_sexp_release (keyparms);
}
return result;
}
/* Enumerate available and supported OpenPGP curves. The caller needs
to set the integer variable at ITERP to zero and keep on calling
this function until NULL is returned. */
const char *
openpgp_enum_curves (int *iterp)
{
int idx = *iterp;
while (idx >= 0 && idx < DIM (oidtable) && oidtable[idx].name)
{
if (curve_supported_p (oidtable[idx].name))
{
*iterp = idx + 1;
return oidtable[idx].alias? oidtable[idx].alias : oidtable[idx].name;
}
idx++;
}
*iterp = idx;
return NULL;
}
/* Return the Libgcrypt name for the gpg curve NAME if supported. If
* R_ALGO is not NULL the required OpenPGP public key algo or 0 is
* stored at that address. If R_NBITS is not NULL the nominal bitsize
* of the curves is stored there. NULL is returned if the curve is
* not supported. */
const char *
openpgp_is_curve_supported (const char *name, int *r_algo,
unsigned int *r_nbits)
{
int idx;
if (r_algo)
*r_algo = 0;
if (r_nbits)
*r_nbits = 0;
for (idx = 0; idx < DIM (oidtable) && oidtable[idx].name; idx++)
{
if ((!ascii_strcasecmp (name, oidtable[idx].name)
|| (oidtable[idx].alias
&& !ascii_strcasecmp (name, (oidtable[idx].alias)))
|| (oidtable[idx].abbr
&& !ascii_strcasecmp (name, (oidtable[idx].abbr))))
&& curve_supported_p (oidtable[idx].name))
{
if (r_algo)
*r_algo = oidtable[idx].pubkey_algo;
if (r_nbits)
*r_nbits = oidtable[idx].nbits;
return oidtable[idx].name;
}
}
return NULL;
}
/* Map a Gcrypt public key algorithm number to the used by OpenPGP.
* Returns 0 for unknown gcry algorithm. */
pubkey_algo_t
map_gcry_pk_to_openpgp (enum gcry_pk_algos algo)
{
switch (algo)
{
case GCRY_PK_EDDSA: return PUBKEY_ALGO_EDDSA;
case GCRY_PK_ECDSA: return PUBKEY_ALGO_ECDSA;
case GCRY_PK_ECDH: return PUBKEY_ALGO_ECDH;
case GCRY_PK_KEM: return PUBKEY_ALGO_KYBER;
default: return algo < 110 ? (pubkey_algo_t)algo : 0;
}
}
/* Map an OpenPGP public key algorithm number to the one used by
* Libgcrypt. Returns 0 for unknown gcry algorithm. */
enum gcry_pk_algos
map_openpgp_pk_to_gcry (pubkey_algo_t algo)
{
switch (algo)
{
case PUBKEY_ALGO_EDDSA: return GCRY_PK_EDDSA;
case PUBKEY_ALGO_ECDSA: return GCRY_PK_ECDSA;
case PUBKEY_ALGO_ECDH: return GCRY_PK_ECDH;
default: return algo < 110 ? (enum gcry_pk_algos)algo : 0;
}
}
/* Return a string describing the public key algorithm and the
* keysize. For elliptic curves the function prints the name of the
* curve because the keysize is a property of the curve. ALGO is the
* Gcrypt algorithm number, CURVE is either NULL or gives the OID of
* the curve, NBITS is either 0 or the size for algorithms like RSA.
* The returned string is taken from permanent table. Examples
* for the output are:
*
* "rsa3072" - RSA with 3072 bit
* "elg1024" - Elgamal with 1024 bit
* "ed25519" - ECC using the curve Ed25519.
* "E_1.2.3.4" - ECC using the unsupported curve with OID "1.2.3.4".
* "E_1.3.6.1.4.1.11591.2.12242973" - ECC with a bogus OID.
* "unknown_N" - Unknown OpenPGP algorithm N.
* If N is > 110 this is a gcrypt algo.
*/
const char *
get_keyalgo_string (enum gcry_pk_algos algo,
unsigned int nbits, const char *curve)
{
const char *prefix;
int i;
char *name, *curvebuf;
switch (algo)
{
case GCRY_PK_RSA: prefix = "rsa"; break;
case GCRY_PK_ELG: prefix = "elg"; break;
case GCRY_PK_DSA: prefix = "dsa"; break;
case GCRY_PK_ECC:
case GCRY_PK_ECDH:
case GCRY_PK_ECDSA:
case GCRY_PK_EDDSA: prefix = ""; break;
default: prefix = NULL; break;
}
if (prefix && *prefix && nbits)
{
for (i=0; i < keyalgo_strings_used; i++)
{
if (keyalgo_strings[i].algo == algo
&& keyalgo_strings[i].nbits
&& keyalgo_strings[i].nbits == nbits)
return keyalgo_strings[i].name;
}
/* Not yet in the table - add it. */
name = xasprintf ("%s%u", prefix, nbits);
nbits = nbits? nbits : 1; /* No nbits - oops - use 1 instead. */
curvebuf = NULL;
}
else if (prefix && !*prefix)
{
const char *curvename;
for (i=0; i < keyalgo_strings_used; i++)
{
if (keyalgo_strings[i].algo == algo
&& keyalgo_strings[i].curve && curve
&& !ascii_strcasecmp (keyalgo_strings[i].curve, curve))
return keyalgo_strings[i].name;
}
/* Not yet in the table - add it. */
curvename = openpgp_oid_or_name_to_curve (curve, 0);
if (curvename)
name = xasprintf ("%s", curvename);
else if (curve)
name = xasprintf ("E_%s", curve);
else
name = xasprintf ("E_error");
nbits = 0;
curvebuf = curve? xstrdup (curve) : NULL;
}
else
{
for (i=0; i < keyalgo_strings_used; i++)
{
if (keyalgo_strings[i].algo == algo
&& !keyalgo_strings[i].nbits
&& !keyalgo_strings[i].curve)
return keyalgo_strings[i].name;
}
/* Not yet in the table - add it. */
name = xasprintf ("unknown_%u", (unsigned int)algo);
nbits = 0;
curvebuf = NULL;
}
/* Store a new entry. This is a loop because of a possible nPth
* thread switch during xrealloc. */
while (keyalgo_strings_used >= keyalgo_strings_size)
{
keyalgo_strings_size += 10;
if (keyalgo_strings_size > 1024*1024)
log_fatal ("%s: table getting too large - possible DoS\n", __func__);
keyalgo_strings = xrealloc (keyalgo_strings, (keyalgo_strings_size
* sizeof *keyalgo_strings));
}
keyalgo_strings[keyalgo_strings_used].algo = algo;
keyalgo_strings[keyalgo_strings_used].nbits = nbits;
keyalgo_strings[keyalgo_strings_used].curve = curvebuf;
keyalgo_strings[keyalgo_strings_used].name = name;
keyalgo_strings_used++;
return name; /* Note that this is in the table. */
}
diff --git a/common/session-env.c b/common/session-env.c
index e774c1d9b..3ffe3f0f5 100644
--- a/common/session-env.c
+++ b/common/session-env.c
@@ -1,439 +1,439 @@
/* session-env.c - Session environment helper functions.
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <errno.h>
#include <ctype.h>
#include <assert.h>
#include <unistd.h>
#include "util.h"
#include "session-env.h"
struct variable_s
{
char *value; /* Pointer into NAME to the Nul terminated value. */
int is_default; /* The value is a default one. */
char name[1]; /* Nul terminated Name and space for the value. */
};
/* The session environment object. */
struct session_environment_s
{
size_t arraysize; /* Allocated size or ARRAY. */
size_t arrayused; /* Used size of ARRAY. */
struct variable_s **array; /* Array of variables. NULL slots are unused. */
};
/* A list of environment variables we pass from the actual user
(e.g. gpgme) down to the pinentry. We do not handle the locale
settings because they do not only depend on envvars. */
static struct
{
const char *name;
const char *assname; /* Name used by Assuan or NULL. */
} stdenvnames[] = {
{ "GPG_TTY", "ttyname" }, /* GnuPG specific envvar. */
{ "TERM", "ttytype" }, /* Used to set ttytype. */
{ "DISPLAY", "display" }, /* The X-Display. */
{ "XAUTHORITY","xauthority"}, /* Xlib Authentication. */
{ "XMODIFIERS" }, /* Used by Xlib to select X input
modules (eg "@im=SCIM"). */
{ "WAYLAND_DISPLAY" }, /* For the Wayland display engine. */
{ "XDG_SESSION_TYPE" }, /* Used by Qt and other non-GTK toolkits
to check for x11 or wayland. */
{ "QT_QPA_PLATFORM" }, /* Used by Qt to explicitly request
x11 or wayland; in particular, needed
to make Qt use Wayland on Gnome. */
{ "GTK_IM_MODULE" }, /* Used by gtk to select gtk input
modules (eg "scim-bridge"). */
{ "DBUS_SESSION_BUS_ADDRESS" },/* Used by GNOME3 to talk to gcr over
dbus */
{ "QT_IM_MODULE" }, /* Used by Qt to select qt input
modules (eg "xim"). */
{ "INSIDE_EMACS" }, /* Set by Emacs before running a
process. */
{ "PINENTRY_USER_DATA", "pinentry-user-data"},
/* Used for communication with
non-standard Pinentries. */
{ "PINENTRY_GEOM_HINT" } /* Used to pass window information. */
};
/* Track last allocated arraysize of all objects ever created. If
nothing has ever been allocated we use INITIAL_ARRAYSIZE and we
will never use more than MAXDEFAULT_ARRAYSIZE for initial
allocation. Note that this is not reentrant if used with a
preemptive thread model. */
static size_t lastallocatedarraysize;
#define INITIAL_ARRAYSIZE 8 /* Let's use the number of stdenvnames. */
#define CHUNK_ARRAYSIZE 10
#define MAXDEFAULT_ARRAYSIZE (INITIAL_ARRAYSIZE + CHUNK_ARRAYSIZE * 5)
/* Return the names of standard environment variables one after the
other. The caller needs to set the value at the address of
ITERATOR initially to 0 and then call this function until it
returns NULL. If ITERATOR is NULL, a single comma delimited string
with the names is returned; NULL is never returned in this case and
R_ASSNAME is ignored. */
const char *
session_env_list_stdenvnames (int *iterator, const char **r_assname)
{
int idx;
static char *commastring;
if (!iterator)
{
if (!commastring)
{
size_t len = 0;
char *p;
for (idx = 0; idx < DIM (stdenvnames); idx++)
len += strlen (stdenvnames[idx].name) + 1;
commastring = xtrymalloc (len);
if (!commastring)
{
log_error ("%s: error allocating string: %s\n", __func__,
gpg_strerror (gpg_error_from_syserror ()));
return "GPG_TTY,TERM,DISPLAY";
}
p = commastring;
for (idx = 0; idx < DIM (stdenvnames); idx++)
{
if (idx)
*p++ = ',';
p = stpcpy (p, stdenvnames[idx].name);
}
gpgrt_annotate_leaked_object (commastring);
}
return commastring;
}
idx = *iterator;
if (idx < 0 || idx >= DIM (stdenvnames))
return NULL;
*iterator = idx + 1;
if (r_assname)
*r_assname = stdenvnames[idx].assname;
return stdenvnames[idx].name;
}
/* Create a new session environment object. Return NULL and sets
ERRNO on failure. */
session_env_t
session_env_new (void)
{
session_env_t se;
se = xtrycalloc (1, sizeof *se);
if (se)
{
se->arraysize = (lastallocatedarraysize?
lastallocatedarraysize : INITIAL_ARRAYSIZE);
se->array = xtrycalloc (se->arraysize, sizeof *se->array);
if (!se->array)
{
xfree (se);
se = NULL;
}
}
return se;
}
/* Release a session environment object. */
void
session_env_release (session_env_t se)
{
int idx;
if (!se)
return;
if (se->arraysize > INITIAL_ARRAYSIZE
&& se->arraysize <= MAXDEFAULT_ARRAYSIZE
&& se->arraysize > lastallocatedarraysize)
lastallocatedarraysize = se->arraysize;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx])
xfree (se->array[idx]);
xfree (se->array);
xfree (se);
}
static gpg_error_t
delete_var (session_env_t se, const char *name)
{
int idx;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
{
xfree (se->array[idx]);
se->array[idx] = NULL;
}
return 0;
}
static gpg_error_t
update_var (session_env_t se, const char *string, size_t namelen,
const char *explicit_value, int set_default)
{
int idx;
int freeidx = -1;
const char *value;
size_t valuelen;
struct variable_s *var;
if (explicit_value)
value = explicit_value;
else
value = string + namelen + 1;
valuelen = strlen (value);
for (idx=0; idx < se->arrayused; idx++)
{
if (!se->array[idx])
freeidx = idx;
else if (!strncmp (se->array[idx]->name, string, namelen)
&& strlen (se->array[idx]->name) == namelen)
{
if (strlen (se->array[idx]->value) == valuelen)
{
/* The new value has the same length. We can update it
in-place. */
memcpy (se->array[idx]->value, value, valuelen);
se->array[idx]->is_default = !!set_default;
return 0;
}
/* Prepare for update. */
freeidx = idx;
}
}
if (freeidx == -1)
{
if (se->arrayused == se->arraysize)
{
/* Reallocate the array. */
size_t newsize;
struct variable_s **newarray;
newsize = se->arraysize + CHUNK_ARRAYSIZE;
newarray = xtrycalloc (newsize, sizeof *newarray);
if (!newarray)
return gpg_error_from_syserror ();
for (idx=0; idx < se->arrayused; idx++)
newarray[idx] = se->array[idx];
se->arraysize = newsize;
xfree (se->array);
se->array = newarray;
}
freeidx = se->arrayused++;
}
/* Allocate new memory and return an error if that didn't worked.
Allocating it first allows us to keep the old value; it doesn't
matter that arrayused has already been incremented in case of a
new entry - it will then pint to a NULL slot. */
var = xtrymalloc (sizeof *var + namelen + 1 + valuelen);
if (!var)
return gpg_error_from_syserror ();
var->is_default = !!set_default;
memcpy (var->name, string, namelen);
var->name[namelen] = '\0';
var->value = var->name + namelen + 1;
strcpy (var->value, value);
xfree (se->array[freeidx]);
se->array[freeidx] = var;
return 0;
}
/* Set or update an environment variable of the session environment.
String is similar to the putval(3) function but it is reentrant and
takes a copy. In particular it exhibits this behaviour:
<NAME> Delete envvar NAME
<KEY>= Set envvar NAME to the empty string
<KEY>=<VALUE> Set envvar NAME to VALUE
On success 0 is returned; on error an gpg-error code. */
gpg_error_t
session_env_putenv (session_env_t se, const char *string)
{
const char *s;
if (!string || !*string)
return gpg_error (GPG_ERR_INV_VALUE);
s = strchr (string, '=');
if (s == string)
return gpg_error (GPG_ERR_INV_VALUE);
if (!s)
return delete_var (se, string);
else
return update_var (se, string, s - string, NULL, 0);
}
-/* Same as session_env_putenv but with name and value given as distict
+/* Same as session_env_putenv but with name and value given as distinct
values. */
gpg_error_t
session_env_setenv (session_env_t se, const char *name, const char *value)
{
if (!name || !*name)
return gpg_error (GPG_ERR_INV_VALUE);
if (!value)
return delete_var (se, name);
else
return update_var (se, name, strlen (name), value, 0);
}
/* Return the value of the environment variable NAME from the SE
object. If the variable does not exist, NULL is returned. The
returned value is valid as long as SE is valid and as long it has
not been removed or updated by a call to session_env_putenv. The
caller MUST not change the returned value. */
char *
session_env_getenv (session_env_t se, const char *name)
{
int idx;
if (!se || !name || !*name)
return NULL;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
return se->array[idx]->is_default? NULL : se->array[idx]->value;
return NULL;
}
/* Return the value of the environment variable NAME from the SE
object. The returned value is valid as long as SE is valid and as
long it has not been removed or updated by a call to
session_env_putenv. If the variable does not exist, the function
- tries to return the value trough a call to getenv; if that returns
+ tries to return the value through a call to getenv; if that returns
a value, this value is recorded and used. If no value could be
found, returns NULL. The caller must not change the returned
value. */
char *
session_env_getenv_or_default (session_env_t se, const char *name,
int *r_default)
{
int idx;
char *defvalue;
if (r_default)
*r_default = 0;
if (!se || !name || !*name)
return NULL;
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
{
if (r_default && se->array[idx]->is_default)
*r_default = 1;
return se->array[idx]->value;
}
/* Get the default value with an additional fallback for GPG_TTY. */
defvalue = getenv (name);
if ((!defvalue || !*defvalue) && !strcmp (name, "GPG_TTY")
&& gnupg_ttyname (0))
{
defvalue = gnupg_ttyname (0);
}
if (defvalue)
{
/* Record the default value for later use so that we are safe
from later modifications of the environment. We need to take
a copy to better cope with the rules of putenv(3). We ignore
the error of the update function because we can't return an
explicit error anyway and the following scan would then fail
anyway. */
update_var (se, name, strlen (name), defvalue, 1);
for (idx=0; idx < se->arrayused; idx++)
if (se->array[idx] && !strcmp (se->array[idx]->name, name))
{
if (r_default && se->array[idx]->is_default)
*r_default = 1;
return se->array[idx]->value;
}
}
return NULL;
}
/* List the entire environment stored in SE. The caller initially
needs to set the value of ITERATOR to 0 and then call this function
until it returns NULL. The value is returned at R_VALUE. If
R_DEFAULT is not NULL, the default flag is stored on return. The
default flag indicates that the value has been taken from the
process's environment. The caller must not change the returned
name or value. */
char *
session_env_listenv (session_env_t se, int *iterator,
const char **r_value, int *r_default)
{
int idx = *iterator;
if (!se || idx < 0)
return NULL;
for (; idx < se->arrayused; idx++)
if (se->array[idx])
{
*iterator = idx+1;
if (r_default)
*r_default = se->array[idx]->is_default;
if (r_value)
*r_value = se->array[idx]->value;
return se->array[idx]->name;
}
return NULL;
}
diff --git a/common/sexp-parse.h b/common/sexp-parse.h
index 0403d65f5..86372e028 100644
--- a/common/sexp-parse.h
+++ b/common/sexp-parse.h
@@ -1,137 +1,137 @@
/* sexp-parse.h - S-expression helper functions
* Copyright (C) 2002, 2003, 2007 Free Software Foundation, Inc.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef SEXP_PARSE_H
#define SEXP_PARSE_H
#include <gpg-error.h>
/* Return the length of the next S-Exp part and update the pointer to
the first data byte. 0 is returned on error */
static inline size_t
snext (unsigned char const **buf)
{
const unsigned char *s;
int n;
s = *buf;
for (n=0; *s && *s != ':' && (*s >= '0' && *s <= '9'); s++)
n = n*10 + (*s - '0');
if (!n || *s != ':')
return 0; /* we don't allow empty lengths */
*buf = s+1;
return n;
}
/* Skip over the S-Expression BUF points to and update BUF to point to
the character right behind. DEPTH gives the initial number of open
lists and may be passed as a positive number to skip over the
remainder of an S-Expression if the current position is somewhere
in an S-Expression. The function may return an error code if it
encounters an impossible condition. */
static inline gpg_error_t
sskip (unsigned char const **buf, int *depth)
{
const unsigned char *s = *buf;
size_t n;
int d = *depth;
while (d > 0)
{
if (*s == '(')
{
d++;
s++;
}
else if (*s == ')')
{
d--;
s++;
}
else
{
if (!d)
return gpg_error (GPG_ERR_INV_SEXP);
n = snext (&s);
if (!n)
return gpg_error (GPG_ERR_INV_SEXP);
s += n;
}
}
*buf = s;
*depth = d;
return 0;
}
/* Check whether the string at the address BUF points to matches
the token. Return true on match and update BUF to point behind the
token. Return false and do not update the buffer if it does not
match. */
static inline int
smatch (unsigned char const **buf, size_t buflen, const char *token)
{
size_t toklen = strlen (token);
if (buflen != toklen || memcmp (*buf, token, toklen))
return 0;
*buf += toklen;
return 1;
}
-/* Format VALUE for use as the length indicatior of an S-expression.
+/* Format VALUE for use as the length indicator of an S-expression.
The caller needs to provide a buffer HELP_BUFFER with a length of
HELP_BUFLEN. The return value is a pointer into HELP_BUFFER with
the formatted length string. The colon and a trailing nul are
appended. HELP_BUFLEN must be at least 3 - a more useful value is
15. If LENGTH is not NULL, the LENGTH of the resulting string
(excluding the terminating nul) is stored at that address. */
static inline char *
smklen (char *help_buffer, size_t help_buflen, size_t value, size_t *length)
{
char *p = help_buffer + help_buflen;
if (help_buflen >= 3)
{
*--p = 0;
*--p = ':';
do
{
*--p = '0' + (value % 10);
value /= 10;
}
while (value && p > help_buffer);
}
if (length)
*length = (help_buffer + help_buflen) - p;
return p;
}
#endif /*SEXP_PARSE_H*/
diff --git a/common/sexputil.c b/common/sexputil.c
index 15fd7cf1d..f9842391e 100644
--- a/common/sexputil.c
+++ b/common/sexputil.c
@@ -1,1240 +1,1240 @@
/* sexputil.c - Utility functions for S-expressions.
* Copyright (C) 2005, 2007, 2009 Free Software Foundation, Inc.
* Copyright (C) 2013 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* This file implements a few utility functions useful when working
with canonical encrypted S-expressions (i.e. not the S-exprssion
objects from libgcrypt). */
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "util.h"
#include "tlv.h"
#include "sexp-parse.h"
#include "openpgpdefs.h" /* for pubkey_algo_t */
/* Return a malloced string with the S-expression CANON in advanced
format. Returns NULL on error. */
static char *
sexp_to_string (gcry_sexp_t sexp)
{
size_t n;
char *result;
if (!sexp)
return NULL;
n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, NULL, 0);
if (!n)
return NULL;
result = xtrymalloc (n);
if (!result)
return NULL;
n = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_ADVANCED, result, n);
if (!n)
BUG ();
return result;
}
/* Return a malloced string with the S-expression CANON in advanced
format. Returns NULL on error. */
char *
canon_sexp_to_string (const unsigned char *canon, size_t canonlen)
{
size_t n;
gcry_sexp_t sexp;
char *result;
n = gcry_sexp_canon_len (canon, canonlen, NULL, NULL);
if (!n)
return NULL;
if (gcry_sexp_sscan (&sexp, NULL, canon, n))
return NULL;
result = sexp_to_string (sexp);
gcry_sexp_release (sexp);
return result;
}
/* Print the canonical encoded S-expression in SEXP in advanced
format. SEXPLEN may be passed as 0 is SEXP is known to be valid.
With TEXT of NULL print just the raw S-expression, with TEXT just
an empty string, print a trailing linefeed, otherwise print an
entire debug line. */
void
log_printcanon (const char *text, const unsigned char *sexp, size_t sexplen)
{
if (text && *text)
log_debug ("%s ", text);
if (sexp)
{
char *buf = canon_sexp_to_string (sexp, sexplen);
log_printf ("%s", buf? buf : "[invalid S-expression]");
xfree (buf);
}
if (text)
log_printf ("\n");
}
/* Print the gcrypt S-expression SEXP in advanced format. With TEXT
of NULL print just the raw S-expression, with TEXT just an empty
string, print a trailing linefeed, otherwise print an entire debug
line. */
void
log_printsexp (const char *text, gcry_sexp_t sexp)
{
if (text && *text)
log_debug ("%s ", text);
if (sexp)
{
char *buf = sexp_to_string (sexp);
log_printf ("%s", buf? buf : "[invalid S-expression]");
xfree (buf);
}
if (text)
log_printf ("\n");
}
/* Helper function to create a canonical encoded S-expression from a
Libgcrypt S-expression object. The function returns 0 on success
and the malloced canonical S-expression is stored at R_BUFFER and
the allocated length at R_BUFLEN. On error an error code is
returned and (NULL, 0) stored at R_BUFFER and R_BUFLEN. If the
allocated buffer length is not required, NULL by be used for
R_BUFLEN. */
gpg_error_t
make_canon_sexp (gcry_sexp_t sexp, unsigned char **r_buffer, size_t *r_buflen)
{
size_t len;
unsigned char *buf;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;;
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
if (!len)
return gpg_error (GPG_ERR_BUG);
buf = xtrymalloc (len);
if (!buf)
return gpg_error_from_syserror ();
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len);
if (!len)
return gpg_error (GPG_ERR_BUG);
*r_buffer = buf;
if (r_buflen)
*r_buflen = len;
return 0;
}
/* Same as make_canon_sexp but pad the buffer to multiple of 64
bits. If SECURE is set, secure memory will be allocated. */
gpg_error_t
make_canon_sexp_pad (gcry_sexp_t sexp, int secure,
unsigned char **r_buffer, size_t *r_buflen)
{
size_t len;
unsigned char *buf;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;;
len = gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, NULL, 0);
if (!len)
return gpg_error (GPG_ERR_BUG);
len += (8 - len % 8) % 8;
buf = secure? xtrycalloc_secure (1, len) : xtrycalloc (1, len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_sprint (sexp, GCRYSEXP_FMT_CANON, buf, len))
return gpg_error (GPG_ERR_BUG);
*r_buffer = buf;
if (r_buflen)
*r_buflen = len;
return 0;
}
/* Return the so called "keygrip" which is the SHA-1 hash of the
- public key parameters expressed in a way dependend on the algorithm.
+ public key parameters expressed in a way dependent on the algorithm.
KEY is expected to be an canonical encoded S-expression with a
public or private key. KEYLEN is the length of that buffer.
GRIP must be at least 20 bytes long. On success 0 is returned, on
error an error code. */
gpg_error_t
keygrip_from_canon_sexp (const unsigned char *key, size_t keylen,
unsigned char *grip)
{
gpg_error_t err;
gcry_sexp_t sexp;
if (!grip)
return gpg_error (GPG_ERR_INV_VALUE);
err = gcry_sexp_sscan (&sexp, NULL, (const char *)key, keylen);
if (err)
return err;
if (!gcry_pk_get_keygrip (sexp, grip))
err = gpg_error (GPG_ERR_INTERNAL);
gcry_sexp_release (sexp);
return err;
}
/* Compare two simple S-expressions like "(3:foo)". Returns 0 if they
are identical or !0 if they are not. Note that this function can't
be used for sorting. */
int
cmp_simple_canon_sexp (const unsigned char *a_orig,
const unsigned char *b_orig)
{
const char *a = (const char *)a_orig;
const char *b = (const char *)b_orig;
unsigned long n1, n2;
char *endp;
if (!a && !b)
return 0; /* Both are NULL, they are identical. */
if (!a || !b)
return 1; /* One is NULL, they are not identical. */
if (*a != '(' || *b != '(')
log_bug ("invalid S-exp in cmp_simple_canon_sexp\n");
a++;
n1 = strtoul (a, &endp, 10);
a = endp;
b++;
n2 = strtoul (b, &endp, 10);
b = endp;
if (*a != ':' || *b != ':' )
log_bug ("invalid S-exp in cmp_simple_canon_sexp\n");
if (n1 != n2)
return 1; /* Not the same. */
for (a++, b++; n1; n1--, a++, b++)
if (*a != *b)
return 1; /* Not the same. */
return 0;
}
/* Helper for cmp_canon_sexp. */
static int
cmp_canon_sexp_def_tcmp (void *ctx, int depth,
const unsigned char *aval, size_t alen,
const unsigned char *bval, size_t blen)
{
(void)ctx;
(void)depth;
if (alen > blen)
return 1;
else if (alen < blen)
return -1;
else
return memcmp (aval, bval, alen);
}
/* Compare the two canonical encoded s-expressions A with maximum
* length ALEN and B with maximum length BLEN.
*
* Returns 0 if they match.
*
* If TCMP is NULL, this is not different really different from a
* memcmp but does not consider any garbage after the last closing
* parentheses.
*
* If TCMP is not NULL, it is expected to be a function to compare the
* values of each token. TCMP is called for each token while parsing
* the s-expressions until TCMP return a non-zero value. Here the CTX
* receives the provided value TCMPCTX, DEPTH is the number of
* currently open parentheses and (AVAL,ALEN) and (BVAL,BLEN) the
* values of the current token. TCMP needs to return zero to indicate
* that the tokens match. */
int
cmp_canon_sexp (const unsigned char *a, size_t alen,
const unsigned char *b, size_t blen,
int (*tcmp)(void *ctx, int depth,
const unsigned char *aval, size_t avallen,
const unsigned char *bval, size_t bvallen),
void *tcmpctx)
{
const unsigned char *a_buf, *a_tok;
const unsigned char *b_buf, *b_tok;
size_t a_buflen, a_toklen;
size_t b_buflen, b_toklen;
int a_depth, b_depth, ret;
if ((!a && !b) || (!alen && !blen))
return 0; /* Both are NULL, they are identical. */
if (!a || !b)
return !!a - !!b; /* One is NULL, they are not identical. */
if (*a != '(' || *b != '(')
log_bug ("invalid S-exp in %s\n", __func__);
if (!tcmp)
tcmp = cmp_canon_sexp_def_tcmp;
a_depth = 0;
a_buf = a;
a_buflen = alen;
b_depth = 0;
b_buf = b;
b_buflen = blen;
for (;;)
{
if (parse_sexp (&a_buf, &a_buflen, &a_depth, &a_tok, &a_toklen))
return -1; /* A is invalid. */
if (parse_sexp (&b_buf, &b_buflen, &b_depth, &b_tok, &b_toklen))
return -1; /* B is invalid. */
if (!a_depth && !b_depth)
return 0; /* End of both expressions - they match. */
if (a_depth != b_depth)
return a_depth - b_depth; /* Not the same structure */
if (!a_tok && !b_tok)
; /* parens */
else if (a_tok && b_tok)
{
ret = tcmp (tcmpctx, a_depth, a_tok, a_toklen, b_tok, b_toklen);
if (ret)
return ret; /* Mismatch */
}
else /* One has a paren other has not. */
return !!a_tok - !!b_tok;
}
}
/* Create a simple S-expression from the hex string at LINE. Returns
a newly allocated buffer with that canonical encoded S-expression
or NULL in case of an error. On return the number of characters
scanned in LINE will be stored at NSCANNED. This functions stops
converting at the first character not representing a hexdigit. Odd
numbers of hex digits are allowed; a leading zero is then
assumed. If no characters have been found, NULL is returned.*/
unsigned char *
make_simple_sexp_from_hexstr (const char *line, size_t *nscanned)
{
size_t n, len;
const char *s;
unsigned char *buf;
unsigned char *p;
char numbuf[50], *numbufp;
size_t numbuflen;
for (n=0, s=line; hexdigitp (s); s++, n++)
;
if (nscanned)
*nscanned = n;
if (!n)
return NULL;
len = ((n+1) & ~0x01)/2;
numbufp = smklen (numbuf, sizeof numbuf, len, &numbuflen);
buf = xtrymalloc (1 + numbuflen + len + 1 + 1);
if (!buf)
return NULL;
buf[0] = '(';
p = (unsigned char *)stpcpy ((char *)buf+1, numbufp);
s = line;
if ((n&1))
{
*p++ = xtoi_1 (s);
s++;
n--;
}
for (; n > 1; n -=2, s += 2)
*p++ = xtoi_2 (s);
*p++ = ')';
*p = 0; /* (Not really needed.) */
return buf;
}
/* Return the hash algorithm from a KSBA sig-val. SIGVAL is a
canonical encoded S-expression. Return 0 if the hash algorithm is
not encoded in SIG-VAL or it is not supported by libgcrypt. */
int
hash_algo_from_sigval (const unsigned char *sigval)
{
const unsigned char *s = sigval;
size_t n;
int depth;
char buffer[50];
if (!s || *s != '(')
return 0; /* Invalid S-expression. */
s++;
n = snext (&s);
if (!n)
return 0; /* Invalid S-expression. */
if (!smatch (&s, n, "sig-val"))
return 0; /* Not a sig-val. */
if (*s != '(')
return 0; /* Invalid S-expression. */
s++;
/* Skip over the algo+parameter list. */
depth = 1;
if (sskip (&s, &depth) || depth)
return 0; /* Invalid S-expression. */
if (*s != '(')
return 0; /* No further list. */
/* Check whether this is (hash ALGO). */
s++;
n = snext (&s);
if (!n)
return 0; /* Invalid S-expression. */
if (!smatch (&s, n, "hash"))
return 0; /* Not a "hash" keyword. */
n = snext (&s);
if (!n || n+1 >= sizeof (buffer))
return 0; /* Algorithm string is missing or too long. */
memcpy (buffer, s, n);
buffer[n] = 0;
return gcry_md_map_name (buffer);
}
/* Create a public key S-expression for an RSA public key from the
modulus M with length MLEN and the public exponent E with length
ELEN. Returns a newly allocated buffer of NULL in case of a memory
allocation problem. If R_LEN is not NULL, the length of the
canonical S-expression is stored there. */
unsigned char *
make_canon_sexp_from_rsa_pk (const void *m_arg, size_t mlen,
const void *e_arg, size_t elen,
size_t *r_len)
{
const unsigned char *m = m_arg;
const unsigned char *e = e_arg;
int m_extra = 0;
int e_extra = 0;
char mlen_str[35];
char elen_str[35];
unsigned char *keybuf, *p;
const char part1[] = "(10:public-key(3:rsa(1:n";
const char part2[] = ")(1:e";
const char part3[] = ")))";
/* Remove leading zeroes. */
for (; mlen && !*m; mlen--, m++)
;
for (; elen && !*e; elen--, e++)
;
/* Insert a leading zero if the number would be zero or interpreted
as negative. */
if (!mlen || (m[0] & 0x80))
m_extra = 1;
if (!elen || (e[0] & 0x80))
e_extra = 1;
/* Build the S-expression. */
snprintf (mlen_str, sizeof mlen_str, "%u:", (unsigned int)mlen+m_extra);
snprintf (elen_str, sizeof elen_str, "%u:", (unsigned int)elen+e_extra);
keybuf = xtrymalloc (strlen (part1) + strlen (mlen_str) + mlen + m_extra
+ strlen (part2) + strlen (elen_str) + elen + e_extra
+ strlen (part3) + 1);
if (!keybuf)
return NULL;
p = stpcpy (keybuf, part1);
p = stpcpy (p, mlen_str);
if (m_extra)
*p++ = 0;
memcpy (p, m, mlen);
p += mlen;
p = stpcpy (p, part2);
p = stpcpy (p, elen_str);
if (e_extra)
*p++ = 0;
memcpy (p, e, elen);
p += elen;
p = stpcpy (p, part3);
if (r_len)
*r_len = p - keybuf;
return keybuf;
}
/* Return the parameters of a public RSA key expressed as an
canonical encoded S-expression. */
gpg_error_t
get_rsa_pk_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
unsigned char const **r_n, size_t *r_nlen,
unsigned char const **r_e, size_t *r_elen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
size_t rsa_n_len, rsa_e_len;
*r_n = NULL;
*r_nlen = 0;
*r_e = NULL;
*r_elen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || !((toklen == 10 && !memcmp ("public-key", tok, toklen))
|| (toklen == 11 && !memcmp ("private-key", tok, toklen))))
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 3 || memcmp ("rsa", tok, toklen))
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
return gpg_error (GPG_ERR_DUP_VALUE);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!rsa_n || !rsa_n_len || !rsa_e || !rsa_e_len)
return gpg_error (GPG_ERR_BAD_PUBKEY);
*r_n = rsa_n;
*r_nlen = rsa_n_len;
*r_e = rsa_e;
*r_elen = rsa_e_len;
return 0;
}
/* Return the public key parameter Q of a public RSA or ECC key
* expressed as an canonical encoded S-expression. */
gpg_error_t
get_ecc_q_from_canon_sexp (const unsigned char *keydata, size_t keydatalen,
unsigned char const **r_q, size_t *r_qlen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2;
const unsigned char *ecc_q = NULL;
size_t ecc_q_len = 0;
*r_q = NULL;
*r_qlen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok || toklen != 10 || memcmp ("public-key", tok, toklen))
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 3 && !memcmp ("ecc", tok, toklen))
;
else if (tok && toklen == 5 && (!memcmp ("ecdsa", tok, toklen)
|| !memcmp ("eddsa", tok, toklen)))
;
else
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
return gpg_error (GPG_ERR_DUP_VALUE);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!ecc_q || !ecc_q_len)
return gpg_error (GPG_ERR_BAD_PUBKEY);
*r_q = ecc_q;
*r_qlen = ecc_q_len;
return 0;
}
/* Return an uncompressed point (X,Y) in P at R_BUF as a malloced
* buffer with its byte length stored at R_BUFLEN. May not be used
* for sensitive data. */
static gpg_error_t
ec2os (gcry_mpi_t x, gcry_mpi_t y, gcry_mpi_t p,
unsigned char **r_buf, unsigned int *r_buflen)
{
gpg_error_t err;
int pbytes = (mpi_get_nbits (p)+7)/8;
size_t n;
unsigned char *buf, *ptr;
*r_buf = NULL;
*r_buflen = 0;
buf = xtrymalloc (1 + 2*pbytes);
if (!buf)
return gpg_error_from_syserror ();
*buf = 04; /* Uncompressed point. */
ptr = buf+1;
err = gcry_mpi_print (GCRYMPI_FMT_USG, ptr, pbytes, &n, x);
if (err)
{
xfree (buf);
return err;
}
if (n < pbytes)
{
memmove (ptr+(pbytes-n), ptr, n);
memset (ptr, 0, (pbytes-n));
}
ptr += pbytes;
err = gcry_mpi_print (GCRYMPI_FMT_USG, ptr, pbytes, &n, y);
if (err)
{
xfree (buf);
return err;
}
if (n < pbytes)
{
memmove (ptr+(pbytes-n), ptr, n);
memset (ptr, 0, (pbytes-n));
}
*r_buf = buf;
*r_buflen = 1 + 2*pbytes;
return 0;
}
/* Convert the ECC parameter Q in the canonical s-expression
* (KEYDATA,KEYDATALEN) to uncompressed form. On success and if a
* conversion was done, the new canonical encoded s-expression is
* returned at (R_NEWKEYDAT,R_NEWKEYDATALEN); if a conversion was not
* required (NULL,0) is stored there. On error an error code is
* returned. The function may take any kind of key but will only do
* the conversion for ECC curves where compression is supported. */
gpg_error_t
uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata,
size_t keydatalen,
unsigned char **r_newkeydata,
size_t *r_newkeydatalen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen, n;
int depth, last_depth1, last_depth2;
const unsigned char *q_ptr; /* Points to the value of "q". */
size_t q_ptrlen; /* Remaining length in KEYDATA. */
size_t q_toklen; /* Q's length including prefix. */
const unsigned char *curve_ptr; /* Points to the value of "curve". */
size_t curve_ptrlen; /* Remaining length in KEYDATA. */
gcry_mpi_t x, y; /* Point Q */
gcry_mpi_t p, a, b; /* Curve parameters. */
gcry_mpi_t x3, t, p1_4; /* Helper */
int y_bit;
unsigned char *qvalue; /* Q in uncompressed form. */
unsigned int qvaluelen;
unsigned char *dst; /* Helper */
char lenstr[35]; /* Helper for a length prefix. */
*r_newkeydata = NULL;
*r_newkeydatalen = 0;
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok)
return gpg_error (GPG_ERR_BAD_PUBKEY);
else if (toklen == 10 || !memcmp ("public-key", tok, toklen))
;
else if (toklen == 11 || !memcmp ("private-key", tok, toklen))
;
else if (toklen == 20 || !memcmp ("shadowed-private-key", tok, toklen))
;
else
return gpg_error (GPG_ERR_BAD_PUBKEY);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 3 && !memcmp ("ecc", tok, toklen))
;
else if (tok && toklen == 5 && !memcmp ("ecdsa", tok, toklen))
;
else
return 0; /* Other algo - no need for conversion. */
last_depth1 = depth;
q_ptr = curve_ptr = NULL;
q_ptrlen = 0; /*(silence cc warning)*/
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1 && *tok == 'q' && !q_ptr)
{
q_ptr = buf;
q_ptrlen = buflen;
}
else if (tok && toklen == 5 && !memcmp (tok, "curve", 5) && !curve_ptr)
{
curve_ptr = buf;
curve_ptrlen = buflen;
}
if (q_ptr && curve_ptr)
break; /* We got all what we need. */
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (!q_ptr)
return 0; /* No Q - nothing to do. */
/* Get Q's value and check whether uncompressing is at all required. */
buf = q_ptr;
buflen = q_ptrlen;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (toklen < 2 || !(*tok == 0x02 || *tok == 0x03))
return 0; /* Invalid length or not compressed. */
q_toklen = buf - q_ptr; /* We want the length with the prefix. */
/* Put the x-coordinate of q into X and remember the y bit */
y_bit = (*tok == 0x03);
err = gcry_mpi_scan (&x, GCRYMPI_FMT_USG, tok+1, toklen-1, NULL);
if (err)
return err;
/* For uncompressing we need to know the curve. */
if (!curve_ptr)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_INV_CURVE);
}
buf = curve_ptr;
buflen = curve_ptrlen;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
{
gcry_mpi_release (x);
return err;
}
{
char name[50];
gcry_sexp_t curveparam;
if (toklen + 1 > sizeof name)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_TOO_LARGE);
}
mem2str (name, tok, toklen+1);
curveparam = gcry_pk_get_param (GCRY_PK_ECC, name);
if (!curveparam)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
err = gcry_sexp_extract_param (curveparam, NULL, "pab", &p, &a, &b, NULL);
gcry_sexp_release (curveparam);
if (err)
{
gcry_mpi_release (x);
return gpg_error (GPG_ERR_INTERNAL);
}
}
if (!mpi_test_bit (p, 1))
{
/* No support for point compression for this curve. */
gcry_mpi_release (x);
gcry_mpi_release (p);
gcry_mpi_release (a);
gcry_mpi_release (b);
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
/*
* Recover Y. The Weierstrass curve: y^2 = x^3 + a*x + b
*/
x3 = mpi_new (0);
t = mpi_new (0);
p1_4 = mpi_new (0);
y = mpi_new (0);
/* Compute right hand side. */
mpi_powm (x3, x, GCRYMPI_CONST_THREE, p);
mpi_mul (t, a, x);
mpi_mod (t, t, p);
mpi_add (t, t, b);
mpi_mod (t, t, p);
mpi_add (t, t, x3);
mpi_mod (t, t, p);
/*
* When p mod 4 = 3, modular square root of A can be computed by
* A^((p+1)/4) mod p
*/
/* Compute (p+1)/4 into p1_4 */
mpi_rshift (p1_4, p, 2);
mpi_add_ui (p1_4, p1_4, 1);
mpi_powm (y, t, p1_4, p);
if (y_bit != mpi_test_bit (y, 0))
mpi_sub (y, p, y);
gcry_mpi_release (p1_4);
gcry_mpi_release (t);
gcry_mpi_release (x3);
gcry_mpi_release (a);
gcry_mpi_release (b);
err = ec2os (x, y, p, &qvalue, &qvaluelen);
gcry_mpi_release (x);
gcry_mpi_release (y);
gcry_mpi_release (p);
if (err)
return err;
snprintf (lenstr, sizeof lenstr, "%u:", (unsigned int)qvaluelen);
/* Note that for simplicity we do not subtract the old length of Q
* for the new buffer. */
*r_newkeydata = xtrymalloc (qvaluelen + strlen(lenstr) + qvaluelen);
if (!*r_newkeydata)
return gpg_error_from_syserror ();
dst = *r_newkeydata;
n = q_ptr - keydata;
memcpy (dst, keydata, n); /* Copy first part of original data. */
dst += n;
n = strlen (lenstr);
memcpy (dst, lenstr, n); /* Copy new prefix of Q's value. */
dst += n;
memcpy (dst, qvalue, qvaluelen); /* Copy new value of Q. */
dst += qvaluelen;
log_assert (q_toklen < q_ptrlen);
n = q_ptrlen - q_toklen;
memcpy (dst, q_ptr + q_toklen, n);/* Copy rest of original data. */
dst += n;
*r_newkeydatalen = dst - *r_newkeydata;
xfree (qvalue);
return 0;
}
/* Return the algo of a public KEY of SEXP. */
int
get_pk_algo_from_key (gcry_sexp_t key)
{
gcry_sexp_t list;
const char *s;
size_t n;
char algoname[10];
int algo = 0;
list = gcry_sexp_nth (key, 1);
if (!list)
goto out;
s = gcry_sexp_nth_data (list, 0, &n);
if (!s)
goto out;
if (n >= sizeof (algoname))
goto out;
memcpy (algoname, s, n);
algoname[n] = 0;
algo = gcry_pk_map_name (algoname);
if (algo == GCRY_PK_ECC)
{
gcry_sexp_t l1;
int i;
l1 = gcry_sexp_find_token (list, "flags", 0);
for (i = l1 ? gcry_sexp_length (l1)-1 : 0; i > 0; i--)
{
s = gcry_sexp_nth_data (l1, i, &n);
if (!s)
continue; /* Not a data element. */
if (n == 5 && !memcmp (s, "eddsa", 5))
{
algo = GCRY_PK_EDDSA;
break;
}
}
gcry_sexp_release (l1);
l1 = gcry_sexp_find_token (list, "curve", 0);
s = gcry_sexp_nth_data (l1, 1, &n);
if (n == 5 && !memcmp (s, "Ed448", 5))
algo = GCRY_PK_EDDSA;
gcry_sexp_release (l1);
}
out:
gcry_sexp_release (list);
return algo;
}
/* This is a variant of get_pk_algo_from_key but takes an canonical
* encoded S-expression as input. Returns a GCRYPT public key
* identiier or 0 on error. */
int
get_pk_algo_from_canon_sexp (const unsigned char *keydata, size_t keydatalen)
{
gcry_sexp_t sexp;
int algo;
if (gcry_sexp_sscan (&sexp, NULL, keydata, keydatalen))
return 0;
algo = get_pk_algo_from_key (sexp);
gcry_sexp_release (sexp);
return algo;
}
/* Given the public key S_PKEY, return a new buffer with a descriptive
* string for its algorithm. This function may return NULL on memory
* error. If R_ALGOID is not NULL the gcrypt algo id is stored there. */
char *
pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid)
{
const char *prefix;
gcry_sexp_t l1;
char *algoname;
int algo;
char *result;
if (r_algoid)
*r_algoid = 0;
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
l1 = gcry_sexp_find_token (s_pkey, "private-key", 0);
if (!l1)
return xtrystrdup ("E_no_key");
{
gcry_sexp_t l_tmp = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l_tmp;
}
algoname = gcry_sexp_nth_string (l1, 0);
gcry_sexp_release (l1);
if (!algoname)
return xtrystrdup ("E_no_algo");
algo = gcry_pk_map_name (algoname);
switch (algo)
{
case GCRY_PK_RSA: prefix = "rsa"; break;
case GCRY_PK_ELG: prefix = "elg"; break;
case GCRY_PK_DSA: prefix = "dsa"; break;
case GCRY_PK_ECC: prefix = ""; break;
default: prefix = NULL; break;
}
if (prefix && *prefix)
result = xtryasprintf ("%s%u", prefix, gcry_pk_get_nbits (s_pkey));
else if (prefix)
{
const char *curve = gcry_pk_get_curve (s_pkey, 0, NULL);
const char *name = openpgp_oid_to_curve
(openpgp_curve_to_oid (curve, NULL, NULL), 0);
if (name)
result = xtrystrdup (name);
else if (curve)
result = xtryasprintf ("X_%s", curve);
else
result = xtrystrdup ("E_unknown");
}
else
result = xtryasprintf ("X_algo_%d", algo);
if (r_algoid)
*r_algoid = algo;
xfree (algoname);
return result;
}
/* Map a pubkey algo id from gcrypt to a string. This is the same as
* gcry_pk_algo_name but makes sure that the ECC algo identifiers are
* not all mapped to "ECC". */
const char *
pubkey_algo_to_string (int algo)
{
if (algo == GCRY_PK_ECDSA)
return "ECDSA";
else if (algo == GCRY_PK_ECDH)
return "ECDH";
else if (algo == GCRY_PK_EDDSA)
return "EdDSA";
else
return gcry_pk_algo_name (algo);
}
/* Map a hash algo id from gcrypt to a string. This is the same as
* gcry_md_algo_name but the returned string is lower case, as
* expected by libksba and it avoids some overhead. */
const char *
hash_algo_to_string (int algo)
{
static const struct
{
const char *name;
int algo;
} hashnames[] =
{
{ "sha256", GCRY_MD_SHA256 },
{ "sha512", GCRY_MD_SHA512 },
{ "sha1", GCRY_MD_SHA1 },
{ "sha384", GCRY_MD_SHA384 },
{ "sha224", GCRY_MD_SHA224 },
{ "sha3-224", GCRY_MD_SHA3_224 },
{ "sha3-256", GCRY_MD_SHA3_256 },
{ "sha3-384", GCRY_MD_SHA3_384 },
{ "sha3-512", GCRY_MD_SHA3_512 },
{ "ripemd160", GCRY_MD_RMD160 },
{ "rmd160", GCRY_MD_RMD160 },
{ "md2", GCRY_MD_MD2 },
{ "md4", GCRY_MD_MD4 },
{ "tiger", GCRY_MD_TIGER },
{ "haval", GCRY_MD_HAVAL },
{ "sm3", GCRY_MD_SM3 },
{ "md5", GCRY_MD_MD5 }
};
int i;
for (i=0; i < DIM (hashnames); i++)
if (algo == hashnames[i].algo)
return hashnames[i].name;
return "?";
}
/* Map cipher modes to a string. */
const char *
cipher_mode_to_string (int mode)
{
switch (mode)
{
case GCRY_CIPHER_MODE_CFB: return "CFB";
case GCRY_CIPHER_MODE_CBC: return "CBC";
case GCRY_CIPHER_MODE_GCM: return "GCM";
case GCRY_CIPHER_MODE_OCB: return "OCB";
case 14: return "EAX"; /* Only in gcrypt 1.9 */
default: return "[?]";
}
}
-/* Return the cannonical name of the ECC curve in KEY. */
+/* Return the canonical name of the ECC curve in KEY. */
const char *
get_ecc_curve_from_key (gcry_sexp_t key)
{
gcry_sexp_t list = NULL;
gcry_sexp_t l2 = NULL;
const char *curve_name = NULL;
char *name = NULL;
/* Check that the first element is valid. */
list = gcry_sexp_find_token (key, "public-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "private-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "protected-private-key", 0);
if (!list)
list = gcry_sexp_find_token (key, "shadowed-private-key", 0);
if (!list)
goto leave;
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
l2 = NULL;
name = gcry_sexp_nth_string (list, 0);
if (!name)
goto leave;
if (gcry_pk_map_name (name) != GCRY_PK_ECC)
goto leave;
l2 = gcry_sexp_find_token (list, "curve", 0);
xfree (name);
name = gcry_sexp_nth_string (l2, 1);
curve_name = openpgp_oid_or_name_to_curve (name, 1);
gcry_sexp_release (l2);
leave:
xfree (name);
gcry_sexp_release (list);
return curve_name;
}
diff --git a/common/sysutils.c b/common/sysutils.c
index 780af58bd..2bacae2ea 100644
--- a/common/sysutils.c
+++ b/common/sysutils.c
@@ -1,2009 +1,2009 @@
/* sysutils.c - system helpers
* Copyright (C) 1991-2001, 2003-2004,
* 2006-2008 Free Software Foundation, Inc.
* Copyright (C) 2013-2016 Werner Koch
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
# undef HAVE_NPTH
# undef USE_NPTH
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <limits.h>
#include <sys/types.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_STAT
# include <sys/stat.h>
#endif
#if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2
# include <asm/sysinfo.h>
# include <asm/unistd.h>
#endif
#include <time.h>
#ifdef HAVE_SETRLIMIT
# include <sys/time.h>
# include <sys/resource.h>
#endif
#ifdef HAVE_PWD_H
# include <pwd.h>
# include <grp.h>
#endif /*HAVE_PWD_H*/
#ifdef HAVE_W32_SYSTEM
# if WINVER < 0x0500
# define WINVER 0x0500 /* Required for AllowSetForegroundWindow. */
# endif
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/socket.h>
# include <sys/un.h>
#endif
#ifdef HAVE_INOTIFY_INIT
# include <sys/inotify.h>
#endif /*HAVE_INOTIFY_INIT*/
#ifdef HAVE_NPTH
# include <npth.h>
#endif
#include <fcntl.h>
#include <dirent.h>
#include <assuan.h>
#include "util.h"
#include "i18n.h"
#include "sysutils.h"
#define tohex(n) ((n) < 10 ? ((n) + '0') : (((n) - 10) + 'A'))
/* The object used with our opendir functions. We need to define our
* own so that we can properly handle Unicode on Windows. */
struct gnupg_dir_s
{
#ifdef HAVE_W32_SYSTEM
_WDIR *dir; /* The system's DIR pointer. */
#else
DIR *dir; /* The system's DIR pointer. */
#endif
struct gnupg_dirent_s dirent; /* The current dirent. */
size_t namesize; /* If not 0 the allocated size of dirent.d_name. */
char name[256]; /* Only used if NAMESIZE is 0. */
};
/* Flag to tell whether special file names are enabled. See gpg.c for
* an explanation of these file names. */
static int allow_special_filenames;
#ifdef HAVE_W32_SYSTEM
/* State of gnupg_inhibit_set_foregound_window. */
static int inhibit_set_foregound_window;
/* Disable the use of _open_osfhandle. */
static int no_translate_sys2libc_fd;
#endif
static GPGRT_INLINE gpg_error_t
my_error_from_syserror (void)
{
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
static GPGRT_INLINE gpg_error_t
my_error (int e)
{
return gpg_err_make (default_errsource, (e));
}
#if defined(__linux__) && defined(__alpha__) && __GLIBC__ < 2
#warning using trap_unaligned
static int
setsysinfo(unsigned long op, void *buffer, unsigned long size,
int *start, void *arg, unsigned long flag)
{
return syscall(__NR_osf_setsysinfo, op, buffer, size, start, arg, flag);
}
void
trap_unaligned(void)
{
unsigned int buf[2];
buf[0] = SSIN_UACPROC;
buf[1] = UAC_SIGBUS | UAC_NOPRINT;
setsysinfo(SSI_NVPAIRS, buf, 1, 0, 0, 0);
}
#else
void
trap_unaligned(void)
{ /* dummy */
}
#endif
int
disable_core_dumps (void)
{
#ifdef HAVE_DOSISH_SYSTEM
return 0;
#else
# ifdef HAVE_SETRLIMIT
struct rlimit limit;
/* We only set the current limit unless we were not able to
retrieve the old value. */
if (getrlimit (RLIMIT_CORE, &limit))
limit.rlim_max = 0;
limit.rlim_cur = 0;
if( !setrlimit (RLIMIT_CORE, &limit) )
return 0;
if( errno != EINVAL && errno != ENOSYS )
log_fatal (_("can't disable core dumps: %s\n"), strerror(errno) );
#endif
return 1;
#endif
}
int
enable_core_dumps (void)
{
#ifdef HAVE_DOSISH_SYSTEM
return 0;
#else
# ifdef HAVE_SETRLIMIT
struct rlimit limit;
if (getrlimit (RLIMIT_CORE, &limit))
return 1;
limit.rlim_cur = limit.rlim_max;
setrlimit (RLIMIT_CORE, &limit);
return 1; /* We always return true because this function is
merely a debugging aid. */
# endif
return 1;
#endif
}
#ifdef HAVE_W32_SYSTEM
static int
any8bitchar (const char *string)
{
if (string)
for ( ; *string; string++)
if ((*string & 0x80))
return 1;
return 0;
}
#endif /*HAVE_W32_SYSTEM*/
/* Helper for gnupg_w32_set_errno. */
#ifdef HAVE_W32_SYSTEM
static int
map_w32_to_errno (DWORD w32_err)
{
switch (w32_err)
{
case 0:
return 0;
case ERROR_FILE_NOT_FOUND:
return ENOENT;
case ERROR_PATH_NOT_FOUND:
return ENOENT;
case ERROR_ACCESS_DENIED:
return EPERM; /* ReactOS uses EACCES ("Permission denied") and
* is likely right because they used an
* undocumented function to associate the error
* codes. However we have always used EPERM
* ("Operation not permitted", e.g. function is
* required to be called by root) and we better
* stick to that to avoid surprising bugs. */
case ERROR_INVALID_HANDLE:
return EBADF;
case ERROR_INVALID_BLOCK:
return ENOMEM;
case ERROR_NOT_ENOUGH_MEMORY:
return ENOMEM;
case ERROR_NO_DATA:
return EPIPE;
case ERROR_ALREADY_EXISTS:
return EEXIST;
case ERROR_FILE_INVALID:
return EIO;
/* This mapping has been taken from reactOS. */
case ERROR_TOO_MANY_OPEN_FILES: return EMFILE;
case ERROR_ARENA_TRASHED: return ENOMEM;
case ERROR_BAD_ENVIRONMENT: return E2BIG;
case ERROR_BAD_FORMAT: return ENOEXEC;
case ERROR_INVALID_DRIVE: return ENOENT;
case ERROR_CURRENT_DIRECTORY: return EACCES;
case ERROR_NOT_SAME_DEVICE: return EXDEV;
case ERROR_NO_MORE_FILES: return ENOENT;
case ERROR_WRITE_PROTECT: return EACCES;
case ERROR_BAD_UNIT: return EACCES;
case ERROR_NOT_READY: return EACCES;
case ERROR_BAD_COMMAND: return EACCES;
case ERROR_CRC: return EACCES;
case ERROR_BAD_LENGTH: return EACCES;
case ERROR_SEEK: return EACCES;
case ERROR_NOT_DOS_DISK: return EACCES;
case ERROR_SECTOR_NOT_FOUND: return EACCES;
case ERROR_OUT_OF_PAPER: return EACCES;
case ERROR_WRITE_FAULT: return EACCES;
case ERROR_READ_FAULT: return EACCES;
case ERROR_GEN_FAILURE: return EACCES;
case ERROR_SHARING_VIOLATION: return EACCES;
case ERROR_LOCK_VIOLATION: return EACCES;
case ERROR_WRONG_DISK: return EACCES;
case ERROR_SHARING_BUFFER_EXCEEDED: return EACCES;
case ERROR_BAD_NETPATH: return ENOENT;
case ERROR_NETWORK_ACCESS_DENIED: return EACCES;
case ERROR_BAD_NET_NAME: return ENOENT;
case ERROR_FILE_EXISTS: return EEXIST;
case ERROR_CANNOT_MAKE: return EACCES;
case ERROR_FAIL_I24: return EACCES;
case ERROR_NO_PROC_SLOTS: return EAGAIN;
case ERROR_DRIVE_LOCKED: return EACCES;
case ERROR_BROKEN_PIPE: return EPIPE;
case ERROR_DISK_FULL: return ENOSPC;
case ERROR_INVALID_TARGET_HANDLE: return EBADF;
case ERROR_WAIT_NO_CHILDREN: return ECHILD;
case ERROR_CHILD_NOT_COMPLETE: return ECHILD;
case ERROR_DIRECT_ACCESS_HANDLE: return EBADF;
case ERROR_SEEK_ON_DEVICE: return EACCES;
case ERROR_DIR_NOT_EMPTY: return ENOTEMPTY;
case ERROR_NOT_LOCKED: return EACCES;
case ERROR_BAD_PATHNAME: return ENOENT;
case ERROR_MAX_THRDS_REACHED: return EAGAIN;
case ERROR_LOCK_FAILED: return EACCES;
case ERROR_INVALID_STARTING_CODESEG: return ENOEXEC;
case ERROR_INVALID_STACKSEG: return ENOEXEC;
case ERROR_INVALID_MODULETYPE: return ENOEXEC;
case ERROR_INVALID_EXE_SIGNATURE: return ENOEXEC;
case ERROR_EXE_MARKED_INVALID: return ENOEXEC;
case ERROR_BAD_EXE_FORMAT: return ENOEXEC;
case ERROR_ITERATED_DATA_EXCEEDS_64k: return ENOEXEC;
case ERROR_INVALID_MINALLOCSIZE: return ENOEXEC;
case ERROR_DYNLINK_FROM_INVALID_RING: return ENOEXEC;
case ERROR_IOPL_NOT_ENABLED: return ENOEXEC;
case ERROR_INVALID_SEGDPL: return ENOEXEC;
case ERROR_AUTODATASEG_EXCEEDS_64k: return ENOEXEC;
case ERROR_RING2SEG_MUST_BE_MOVABLE: return ENOEXEC;
case ERROR_RELOC_CHAIN_XEEDS_SEGLIM: return ENOEXEC;
case ERROR_INFLOOP_IN_RELOC_CHAIN: return ENOEXEC;
case ERROR_FILENAME_EXCED_RANGE: return ENOENT;
case ERROR_NESTING_NOT_ALLOWED: return EAGAIN;
case ERROR_NOT_ENOUGH_QUOTA: return ENOMEM;
default:
return EIO;
}
}
#endif /*HAVE_W32_SYSTEM*/
/* Set ERRNO from the Windows error. EC may be -1 to use the last
* error. Returns the Windows error code. */
#ifdef HAVE_W32_SYSTEM
int
gnupg_w32_set_errno (int ec)
{
/* FIXME: Replace by gpgrt_w32_set_errno. */
if (ec == -1)
ec = GetLastError ();
_set_errno (map_w32_to_errno (ec));
return ec;
}
#endif /*HAVE_W32_SYSTEM*/
/* Allow the use of special "-&nnn" style file names. */
void
enable_special_filenames (void)
{
allow_special_filenames = 1;
}
/* Disable the use use of _open_osfhandle on Windows. */
void
disable_translate_sys2libc_fd (void)
{
#ifdef HAVE_W32_SYSTEM
no_translate_sys2libc_fd = 1;
#endif
}
/* Return a string which is used as a kind of process ID. */
const byte *
get_session_marker (size_t *rlen)
{
static byte marker[SIZEOF_UNSIGNED_LONG*2];
static int initialized;
if (!initialized)
{
gcry_create_nonce (marker, sizeof marker);
initialized = 1;
}
*rlen = sizeof (marker);
return marker;
}
/* Return a random number in an unsigned int. */
unsigned int
get_uint_nonce (void)
{
unsigned int value;
gcry_create_nonce (&value, sizeof value);
return value;
}
#if 0 /* not yet needed - Note that this will require inclusion of
cmacros.am in Makefile.am */
int
check_permissions(const char *path,int extension,int checkonly)
{
#if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM)
char *tmppath;
struct stat statbuf;
int ret=1;
int isdir=0;
if(opt.no_perm_warn)
return 0;
if(extension && path[0]!=DIRSEP_C)
{
if(strchr(path,DIRSEP_C))
tmppath=make_filename(path,NULL);
else
tmppath=make_filename(GNUPG_LIBDIR,path,NULL);
}
else
tmppath=m_strdup(path);
/* It's okay if the file doesn't exist */
if(stat(tmppath,&statbuf)!=0)
{
ret=0;
goto end;
}
isdir=S_ISDIR(statbuf.st_mode);
/* Per-user files must be owned by the user. Extensions must be
owned by the user or root. */
if((!extension && statbuf.st_uid != getuid()) ||
(extension && statbuf.st_uid!=0 && statbuf.st_uid!=getuid()))
{
if(!checkonly)
log_info(_("Warning: unsafe ownership on %s \"%s\"\n"),
isdir?"directory":extension?"extension":"file",path);
goto end;
}
/* This works for both directories and files - basically, we don't
care what the owner permissions are, so long as the group and
other permissions are 0 for per-user files, and non-writable for
extensions. */
if((extension && (statbuf.st_mode & (S_IWGRP|S_IWOTH)) !=0) ||
(!extension && (statbuf.st_mode & (S_IRWXG|S_IRWXO)) != 0))
{
char *dir;
/* However, if the directory the directory/file is in is owned
by the user and is 700, then this is not a problem.
Theoretically, we could walk this test up to the root
directory /, but for the sake of sanity, I'm stopping at one
level down. */
dir= make_dirname (tmppath);
if(stat(dir,&statbuf)==0 && statbuf.st_uid==getuid() &&
S_ISDIR(statbuf.st_mode) && (statbuf.st_mode & (S_IRWXG|S_IRWXO))==0)
{
xfree (dir);
ret=0;
goto end;
}
m_free(dir);
if(!checkonly)
log_info(_("Warning: unsafe permissions on %s \"%s\"\n"),
isdir?"directory":extension?"extension":"file",path);
goto end;
}
ret=0;
end:
m_free(tmppath);
return ret;
#endif /* HAVE_STAT && !HAVE_DOSISH_SYSTEM */
return 0;
}
#endif
/* Wrapper around the usual sleep function. This one won't wake up
before the sleep time has really elapsed. When build with Pth it
merely calls pth_sleep and thus suspends only the current
thread. */
void
gnupg_sleep (unsigned int seconds)
{
#ifdef USE_NPTH
npth_sleep (seconds);
#else
/* Fixme: make sure that a sleep won't wake up to early. */
# ifdef HAVE_W32_SYSTEM
Sleep (seconds*1000);
# else
sleep (seconds);
# endif
#endif
}
/* Wrapper around the platforms usleep function. This one won't wake
* up before the sleep time has really elapsed. When build with nPth
* it merely calls npth_usleep and thus suspends only the current
* thread. */
void
gnupg_usleep (unsigned int usecs)
{
#if defined(USE_NPTH)
npth_usleep (usecs);
#elif defined(HAVE_W32_SYSTEM)
Sleep ((usecs + 999) / 1000);
#elif defined(HAVE_NANOSLEEP)
if (usecs)
{
struct timespec req;
struct timespec rem;
req.tv_sec = usecs / 1000000;
req.tv_nsec = (usecs % 1000000) * 1000;
while (nanosleep (&req, &rem) < 0 && errno == EINTR)
req = rem;
}
#else /*Standard Unix*/
if (usecs)
{
struct timeval tv;
tv.tv_sec = usecs / 1000000;
tv.tv_usec = usecs % 1000000;
select (0, NULL, NULL, NULL, &tv);
}
#endif
}
/* This function is a NOP for POSIX systems but required under Windows
as the file handles as returned by OS calls (like CreateFile) are
different from the libc file descriptors (like open). This function
translates system file handles to libc file handles. FOR_WRITE
gives the direction of the handle. */
#if defined(HAVE_W32_SYSTEM)
static int
translate_sys2libc_fd (gnupg_fd_t fd, int for_write)
{
int x;
if (fd == GNUPG_INVALID_FD)
return -1;
/* Note that _open_osfhandle is currently defined to take and return
a long. */
x = _open_osfhandle ((intptr_t)fd, for_write ? 1 : 0);
if (x == -1)
log_error ("failed to translate osfhandle %p\n", (void *) fd);
return x;
}
#endif /*!HAVE_W32_SYSTEM */
/* This is the same as translate_sys2libc_fd but takes an integer
which is assumed to be such an system handle. */
int
translate_sys2libc_fd_int (int fd, int for_write)
{
#ifdef HAVE_W32_SYSTEM
if (fd <= 2 || no_translate_sys2libc_fd)
return fd; /* Do not do this for stdin, stdout, and stderr. */
return translate_sys2libc_fd ((void*)(intptr_t)fd, for_write);
#else
(void)for_write;
return fd;
#endif
}
/*
* Parse the string representation of a file reference (file handle on
* Windows or file descriptor on POSIX) in FDSTR. The string
- * representation may be either of folllowing:
+ * representation may be either of following:
* (1) 0, 1, or 2 which means stdin, stdout, and stderr, respectively.
* (2) Integer representation (by %d of printf).
* (3) Hex representation which starts as "0x".
*
* Then, fill R_SYSHD, according to the value of a file reference.
*
*/
gpg_error_t
gnupg_parse_fdstr (const char *fdstr, es_syshd_t *r_syshd)
{
int fd = -1;
#ifdef HAVE_W32_SYSTEM
gnupg_fd_t hd;
char *endptr;
int base;
if (!strcmp (fdstr, "0"))
fd = 0;
else if (!strcmp (fdstr, "1"))
fd = 1;
else if (!strcmp (fdstr, "2"))
fd = 2;
if (fd >= 0)
{
r_syshd->type = ES_SYSHD_FD;
r_syshd->u.fd = fd;
return 0;
}
if (!strncmp (fdstr, "0x", 2))
{
base = 16;
fdstr += 2;
}
else
base = 10;
gpg_err_set_errno (0);
#ifdef _WIN64
hd = (gnupg_fd_t)strtoll (fdstr, &endptr, base);
#else
hd = (gnupg_fd_t)strtol (fdstr, &endptr, base);
#endif
if (errno != 0 || endptr == fdstr || *endptr != '\0')
return gpg_error (GPG_ERR_INV_ARG);
r_syshd->type = ES_SYSHD_HANDLE;
r_syshd->u.handle = hd;
return 0;
#else
fd = atoi (fdstr);
r_syshd->type = ES_SYSHD_FD;
r_syshd->u.fd = fd;
return 0;
#endif
}
/* Check whether FNAME has the form "-&nnnn", where N is a non-zero
* number. Returns this number or -1 if it is not the case. If the
* caller wants to use the file descriptor for writing FOR_WRITE shall
* be set to 1. If NOTRANSLATE is set the Windows specific mapping is
* not done. */
int
check_special_filename (const char *fname, int for_write, int notranslate)
{
if (allow_special_filenames
&& fname && *fname == '-' && fname[1] == '&')
{
int i;
fname += 2;
for (i=0; digitp (fname+i); i++ )
;
if (!fname[i])
{
if (notranslate)
return atoi (fname);
else
{
es_syshd_t syshd;
if (gnupg_parse_fdstr (fname, &syshd))
return -1;
#ifdef HAVE_W32_SYSTEM
if (syshd.type == ES_SYSHD_FD)
return syshd.u.fd;
else
return translate_sys2libc_fd ((gnupg_fd_t)syshd.u.handle, for_write);
#else
(void)for_write;
return syshd.u.fd;
#endif
}
}
}
return -1;
}
/* Check whether FNAME has the form "-&nnnn", where N is a number
* representing a file. Returns GNUPG_INVALID_FD if it is not the
* case. Returns a file descriptor on POSIX, a system handle on
* Windows. */
gnupg_fd_t
gnupg_check_special_filename (const char *fname)
{
if (allow_special_filenames
&& fname && *fname == '-' && fname[1] == '&')
{
int i;
fname += 2;
for (i=0; digitp (fname+i); i++ )
;
if (!fname[i])
{
es_syshd_t syshd;
if (gnupg_parse_fdstr (fname, &syshd))
return GNUPG_INVALID_FD;
#ifdef HAVE_W32_SYSTEM
if (syshd.type == ES_SYSHD_FD)
{
if (syshd.u.fd == 0)
return GetStdHandle (STD_INPUT_HANDLE);
else if (syshd.u.fd == 1)
return GetStdHandle (STD_OUTPUT_HANDLE);
else if (syshd.u.fd == 2)
return GetStdHandle (STD_ERROR_HANDLE);
}
else
return syshd.u.handle;
#else
return syshd.u.fd;
#endif
}
}
return GNUPG_INVALID_FD;
}
/* Replacement for tmpfile(). This is required because the tmpfile
function of Windows' runtime library is broken, insecure, ignores
TMPDIR and so on. In addition we create a file with an inheritable
handle. */
FILE *
gnupg_tmpfile (void)
{
#ifdef HAVE_W32_SYSTEM
int attempts, n;
char buffer[MAX_PATH+7+12+1];
char *name, *p;
HANDLE file;
int pid = GetCurrentProcessId ();
unsigned int value = 0;
int i;
SECURITY_ATTRIBUTES sec_attr;
memset (&sec_attr, 0, sizeof sec_attr );
sec_attr.nLength = sizeof sec_attr;
sec_attr.bInheritHandle = TRUE;
n = GetTempPath (MAX_PATH+1, buffer);
if (!n || n > MAX_PATH || strlen (buffer) > MAX_PATH)
{
gpg_err_set_errno (ENOENT);
return NULL;
}
p = buffer + strlen (buffer);
p = stpcpy (p, "_gnupg");
/* We try to create the directory but don't care about an error as
it may already exist and the CreateFile would throw an error
anyway. */
CreateDirectory (buffer, NULL);
*p++ = '\\';
name = p;
for (attempts=0; attempts < 10; attempts++)
{
p = name;
value += (GetTickCount () ^ ((pid<<16) & 0xffff0000));
for (i=0; i < 8; i++)
*p++ = tohex (((value >> (7 - i)*4) & 0x0f));
strcpy (p, ".tmp");
file = CreateFile (buffer,
GENERIC_READ | GENERIC_WRITE,
0,
&sec_attr,
CREATE_NEW,
FILE_ATTRIBUTE_TEMPORARY | FILE_FLAG_DELETE_ON_CLOSE,
NULL);
if (file != INVALID_HANDLE_VALUE)
{
FILE *fp;
int fd = _open_osfhandle ((intptr_t)file, 0);
if (fd == -1)
{
CloseHandle (file);
return NULL;
}
fp = fdopen (fd, "w+b");
if (!fp)
{
int save = errno;
close (fd);
gpg_err_set_errno (save);
return NULL;
}
return fp;
}
Sleep (1); /* One ms as this is the granularity of GetTickCount. */
}
gpg_err_set_errno (ENOENT);
return NULL;
#else /*!HAVE_W32_SYSTEM*/
return tmpfile ();
#endif /*!HAVE_W32_SYSTEM*/
}
/* Make sure that the standard file descriptors are opened. Obviously
some folks close them before an exec and the next file we open will
get one of them assigned and thus any output (i.e. diagnostics) end
up in that file (e.g. the trustdb). Not actually a gpg problem as
this will happen with almost all utilities when called in a wrong
way. However we try to minimize the damage here and raise
awareness of the problem.
Must be called before we open any files! */
void
gnupg_reopen_std (const char *pgmname)
{
#ifdef F_GETFD
int did_stdin = 0;
int did_stdout = 0;
int did_stderr = 0;
FILE *complain;
if (fcntl (STDIN_FILENO, F_GETFD) == -1 && errno ==EBADF)
{
if (open ("/dev/null",O_RDONLY) == STDIN_FILENO)
did_stdin = 1;
else
did_stdin = 2;
}
if (fcntl (STDOUT_FILENO, F_GETFD) == -1 && errno == EBADF)
{
if (open ("/dev/null",O_WRONLY) == STDOUT_FILENO)
did_stdout = 1;
else
did_stdout = 2;
}
if (fcntl (STDERR_FILENO, F_GETFD)==-1 && errno==EBADF)
{
if (open ("/dev/null", O_WRONLY) == STDERR_FILENO)
did_stderr = 1;
else
did_stderr = 2;
}
/* It's hard to log this sort of thing since the filehandle we would
complain to may be closed... */
if (!did_stderr)
complain = stderr;
else if (!did_stdout)
complain = stdout;
else
complain = NULL;
if (complain)
{
if (did_stdin == 1)
fprintf (complain, "%s: WARNING: standard input reopened\n", pgmname);
if (did_stdout == 1)
fprintf (complain, "%s: WARNING: standard output reopened\n", pgmname);
if (did_stderr == 1)
fprintf (complain, "%s: WARNING: standard error reopened\n", pgmname);
if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2)
fprintf(complain,"%s: fatal: unable to reopen standard input,"
" output, or error\n", pgmname);
}
if (did_stdin == 2 || did_stdout == 2 || did_stderr == 2)
exit (3);
#else /* !F_GETFD */
(void)pgmname;
#endif
}
/* Inhibit calls to AllowSetForegroundWindow on Windows. Calling this
* with YES set to true calls to gnupg_allow_set_foregound_window are
* shunted. */
void
gnupg_inhibit_set_foregound_window (int yes)
{
#ifdef HAVE_W32_SYSTEM
inhibit_set_foregound_window = yes;
#else
(void)yes;
#endif
}
/* Hack required for Windows. */
void
gnupg_allow_set_foregound_window (pid_t pid)
{
if (!pid)
log_info ("%s called with invalid pid %lu\n",
"gnupg_allow_set_foregound_window", (unsigned long)pid);
#if defined(HAVE_W32_SYSTEM)
else if (inhibit_set_foregound_window)
;
else if (!AllowSetForegroundWindow ((pid_t)pid == (pid_t)(-1)?ASFW_ANY:pid))
{
char *flags = getenv ("GNUPG_EXEC_DEBUG_FLAGS");
if (flags && (atoi (flags) & 2))
log_info ("AllowSetForegroundWindow(%lu) failed: %s\n",
(unsigned long)pid, w32_strerror (-1));
}
#endif
}
int
gnupg_remove (const char *fname)
{
#ifdef HAVE_W32_SYSTEM
int rc;
wchar_t *wfname;
wfname = utf8_to_wchar (fname);
if (!wfname)
rc = 0;
else
{
rc = DeleteFileW (wfname);
if (!rc)
gnupg_w32_set_errno (-1);
xfree (wfname);
}
if (!rc)
return -1;
return 0;
#else
/* It is common to use /dev/null for testing. We better don't
* remove that file. */
if (fname && !strcmp (fname, "/dev/null"))
return 0;
else
return remove (fname);
#endif
}
/* Helper for gnupg_rename_file. */
#ifdef HAVE_W32_SYSTEM
static int
w32_rename (const char *oldname, const char *newname)
{
if (any8bitchar (oldname) || any8bitchar (newname))
{
wchar_t *woldname, *wnewname;
int ret;
woldname = utf8_to_wchar (oldname);
if (!woldname)
return -1;
wnewname = utf8_to_wchar (newname);
if (!wnewname)
{
xfree (wnewname);
return -1;
}
ret = _wrename (woldname, wnewname);
xfree (wnewname);
xfree (woldname);
return ret;
}
else
return rename (oldname, newname);
}
#endif /*HAVE_W32_SYSTEM*/
/* Wrapper for rename(2) to handle Windows peculiarities. If
* BLOCK_SIGNALS is not NULL and points to a variable set to true, all
* signals will be blocked by calling gnupg_block_all_signals; the
* caller needs to call gnupg_unblock_all_signals if that variable is
* still set to true on return. */
gpg_error_t
gnupg_rename_file (const char *oldname, const char *newname, int *block_signals)
{
gpg_error_t err = 0;
if (block_signals && *block_signals)
gnupg_block_all_signals ();
#ifdef HAVE_DOSISH_SYSTEM
{
int wtime = 0;
gnupg_remove (newname);
again:
if (w32_rename (oldname, newname))
{
if (GetLastError () == ERROR_SHARING_VIOLATION)
{
/* Another process has the file open. We do not use a
* lock for read but instead we wait until the other
* process has closed the file. This may take long but
* that would also be the case with a dotlock approach for
* read and write. Note that we don't need this on Unix
* due to the inode concept.
*
* So let's wait until the rename has worked. The retry
* intervals are 50, 100, 200, 400, 800, 50ms, ... */
if (!wtime || wtime >= 800)
wtime = 50;
else
wtime *= 2;
if (wtime >= 800)
log_info (_("waiting for file '%s' to become accessible ...\n"),
oldname);
Sleep (wtime);
goto again;
}
err = my_error_from_syserror ();
}
}
#else /* Unix */
{
#ifdef __riscos__
gnupg_remove (newname);
#endif
if (rename (oldname, newname) )
err = my_error_from_syserror ();
}
#endif /* Unix */
if (block_signals && *block_signals && err)
{
gnupg_unblock_all_signals ();
*block_signals = 0;
}
if (err)
log_error (_("renaming '%s' to '%s' failed: %s\n"),
oldname, newname, gpg_strerror (err));
return err;
}
#ifndef HAVE_W32_SYSTEM
static mode_t
modestr_to_mode (const char *modestr, mode_t oldmode)
{
static struct {
char letter;
mode_t value;
} table[] = { { '-', 0 },
{ 'r', S_IRUSR }, { 'w', S_IWUSR }, { 'x', S_IXUSR },
{ 'r', S_IRGRP }, { 'w', S_IWGRP }, { 'x', S_IXGRP },
{ 'r', S_IROTH }, { 'w', S_IWOTH }, { 'x', S_IXOTH } };
int idx;
mode_t mode = 0;
/* For now we only support a string as used by ls(1) and no octal
* numbers. The first character must be a dash. */
for (idx=0; idx < 10 && *modestr; idx++, modestr++)
{
if (*modestr == table[idx].letter)
mode |= table[idx].value;
else if (*modestr == '.')
{
if (!idx)
; /* Skip the dummy. */
else if ((oldmode & table[idx].value))
mode |= (oldmode & table[idx].value);
else
mode &= ~(oldmode & table[idx].value);
}
else if (*modestr != '-')
break;
}
return mode;
}
#endif
/* A wrapper around mkdir which takes a string for the mode argument.
This makes it easier to handle the mode argument which is not
defined on all systems. The format of the modestring is
"-rwxrwxrwx"
'-' is a don't care or not set. 'r', 'w', 'x' are read allowed,
write allowed, execution allowed with the first group for the user,
the second for the group and the third for all others. If the
string is shorter than above the missing mode characters are meant
to be not set. */
int
gnupg_mkdir (const char *name, const char *modestr)
{
- /* Note that gpgrt_mkdir also sets ERRNO in addition to returing an
+ /* Note that gpgrt_mkdir also sets ERRNO in addition to returning an
* gpg-error style error code. */
return gpgrt_mkdir (name, modestr);
}
/* A simple wrapper around chdir. NAME is expected to be utf8
* encoded. */
int
gnupg_chdir (const char *name)
{
/* Note that gpgrt_chdir also sets ERRNO in addition to returning an
* gpg-error style error code. */
return gpgrt_chdir (name);
}
/* A wrapper around rmdir. NAME is expected to be utf8 encoded. */
int
gnupg_rmdir (const char *name)
{
#ifdef HAVE_W32_SYSTEM
int rc;
wchar_t *wfname;
wfname = utf8_to_wchar (name);
if (!wfname)
rc = 0;
else
{
rc = RemoveDirectoryW (wfname);
if (!rc)
gnupg_w32_set_errno (-1);
xfree (wfname);
}
if (!rc)
return -1;
return 0;
#else
return rmdir (name);
#endif
}
/* A wrapper around chmod which takes a string for the mode argument.
This makes it easier to handle the mode argument which is not
defined on all systems. The format of the modestring is the same
as for gnupg_mkdir with extra feature that a '.' keeps the original
mode bit. */
int
gnupg_chmod (const char *name, const char *modestr)
{
#ifdef HAVE_W32_SYSTEM
(void)name;
(void)modestr;
return 0;
#else
mode_t oldmode;
if (strchr (modestr, '.'))
{
/* Get the old mode so that a '.' can copy that bit. */
struct stat st;
if (stat (name, &st))
return -1;
oldmode = st.st_mode;
}
else
oldmode = 0;
return chmod (name, modestr_to_mode (modestr, oldmode));
#endif
}
/* Our version of mkdtemp. The API is identical to POSIX.1-2008
version. We do not use a system provided mkdtemp because we have a
good RNG instantly available and this way we don't have diverging
versions. */
char *
gnupg_mkdtemp (char *tmpl)
{
/* A lower bound on the number of temporary files to attempt to
generate. The maximum total number of temporary file names that
can exist for a given template is 62**6 (5*36**3 for Windows).
It should never be necessary to try all these combinations.
Instead if a reasonable number of names is tried (we define
reasonable as 62**3 or 5*36**3) fail to give the system
administrator the chance to remove the problems. */
#ifdef HAVE_W32_SYSTEM
static const char letters[] =
"abcdefghijklmnopqrstuvwxyz0123456789";
# define NUMBER_OF_LETTERS 36
# define ATTEMPTS_MIN (5 * 36 * 36 * 36)
#else
static const char letters[] =
"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
# define NUMBER_OF_LETTERS 62
# define ATTEMPTS_MIN (62 * 62 * 62)
#endif
int len;
char *XXXXXX;
uint64_t value;
unsigned int count;
int save_errno = errno;
/* The number of times to attempt to generate a temporary file. To
conform to POSIX, this must be no smaller than TMP_MAX. */
#if ATTEMPTS_MIN < TMP_MAX
unsigned int attempts = TMP_MAX;
#else
unsigned int attempts = ATTEMPTS_MIN;
#endif
len = strlen (tmpl);
if (len < 6 || strcmp (&tmpl[len - 6], "XXXXXX"))
{
gpg_err_set_errno (EINVAL);
return NULL;
}
/* This is where the Xs start. */
XXXXXX = &tmpl[len - 6];
/* Get a random start value. */
gcry_create_nonce (&value, sizeof value);
/* Loop until a directory was created. */
for (count = 0; count < attempts; value += 7777, ++count)
{
uint64_t v = value;
/* Fill in the random bits. */
XXXXXX[0] = letters[v % NUMBER_OF_LETTERS];
v /= NUMBER_OF_LETTERS;
XXXXXX[1] = letters[v % NUMBER_OF_LETTERS];
v /= NUMBER_OF_LETTERS;
XXXXXX[2] = letters[v % NUMBER_OF_LETTERS];
v /= NUMBER_OF_LETTERS;
XXXXXX[3] = letters[v % NUMBER_OF_LETTERS];
v /= NUMBER_OF_LETTERS;
XXXXXX[4] = letters[v % NUMBER_OF_LETTERS];
v /= NUMBER_OF_LETTERS;
XXXXXX[5] = letters[v % NUMBER_OF_LETTERS];
if (!gnupg_mkdir (tmpl, "-rwx"))
{
gpg_err_set_errno (save_errno);
return tmpl;
}
if (errno != EEXIST)
return NULL;
}
/* We got out of the loop because we ran out of combinations to try. */
gpg_err_set_errno (EEXIST);
return NULL;
}
int
gnupg_setenv (const char *name, const char *value, int overwrite)
{
#ifdef HAVE_W32_SYSTEM
/* Windows maintains (at least) two sets of environment variables.
One set can be accessed by GetEnvironmentVariable and
SetEnvironmentVariable. This set is inherited by the children.
The other set is maintained in the C runtime, and is accessed
using getenv and putenv. We try to keep them in sync by
modifying both sets. */
{
int exists;
char tmpbuf[10];
exists = GetEnvironmentVariable (name, tmpbuf, sizeof tmpbuf);
if ((! exists || overwrite) && !SetEnvironmentVariable (name, value))
{
gpg_err_set_errno (EINVAL); /* (Might also be ENOMEM.) */
return -1;
}
}
#endif /*W32*/
#ifdef HAVE_SETENV
return setenv (name, value, overwrite);
#else /*!HAVE_SETENV*/
if (! getenv (name) || overwrite)
#if defined(HAVE_W32_SYSTEM) && defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES)
{
int e = _putenv_s (name, value);
if (e)
{
gpg_err_set_errno (e);
return -1;
}
else
return 0;
}
#else
{
char *buf;
(void)overwrite;
if (!name || !value)
{
gpg_err_set_errno (EINVAL);
return -1;
}
buf = strconcat (name, "=", value, NULL);
if (!buf)
return -1;
# if __GNUC__
# warning no setenv - using putenv but leaking memory.
# endif
return putenv (buf);
}
#endif /*!HAVE_W32_SYSTEM*/
return 0;
#endif /*!HAVE_SETENV*/
}
int
gnupg_unsetenv (const char *name)
{
#ifdef HAVE_W32_SYSTEM
/* Windows maintains (at least) two sets of environment variables.
One set can be accessed by GetEnvironmentVariable and
SetEnvironmentVariable. This set is inherited by the children.
The other set is maintained in the C runtime, and is accessed
using getenv and putenv. We try to keep them in sync by
modifying both sets. */
if (!SetEnvironmentVariable (name, NULL))
{
gpg_err_set_errno (EINVAL); /* (Might also be ENOMEM.) */
return -1;
}
#endif /*W32*/
#ifdef HAVE_UNSETENV
return unsetenv (name);
#elif defined(HAVE_W32_SYSTEM) && defined(_CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES)
{
int e = _putenv_s (name, "");
if (e)
{
gpg_err_set_errno (e);
return -1;
}
else
return 0;
}
#else /*!HAVE_UNSETENV*/
{
char *buf;
int r;
if (!name)
{
gpg_err_set_errno (EINVAL);
return -1;
}
buf = strconcat (name, "=", NULL);
if (!buf)
return -1;
r = putenv (buf);
# ifdef HAVE_W32_SYSTEM
/* For Microsoft implementation, we can free the memory in this
use case. */
xfree (buf);
# else
# if __GNUC__
# warning no unsetenv - trying putenv but leaking memory.
# endif
# endif
return r;
}
#endif /*!HAVE_UNSETENV*/
}
/* Return the current working directory as a malloced string. Return
NULL and sets ERRNO on error. */
char *
gnupg_getcwd (void)
{
return gpgrt_getcwd ();
}
/* A simple wrapper around access. NAME is expected to be utf8
* encoded. This function returns an error code and sets ERRNO. */
gpg_err_code_t
gnupg_access (const char *name, int mode)
{
return gpgrt_access (name, mode);
}
/* A wrapper around stat to handle Unicode file names under Windows. */
#ifdef HAVE_STAT
int
gnupg_stat (const char *name, struct stat *statbuf)
{
# ifdef HAVE_W32_SYSTEM
# if __MINGW32_MAJOR_VERSION > 3
/* mingw.org's MinGW */
# define STRUCT_STAT _stat
# elif defined(_USE_32BIT_TIME_T)
/* MinGW64 for i686 */
# define STRUCT_STAT _stat32
# else
/* MinGW64 for x86_64 */
# define STRUCT_STAT _stat64i32
# endif
if (any8bitchar (name))
{
wchar_t *wname;
struct STRUCT_STAT st32;
int ret;
wname = utf8_to_wchar (name);
if (!wname)
return -1;
ret = _wstat (wname, &st32);
xfree (wname);
if (!ret)
{
statbuf->st_dev = st32.st_dev;
statbuf->st_ino = st32.st_ino;
statbuf->st_mode = st32.st_mode;
statbuf->st_nlink = st32.st_nlink;
statbuf->st_uid = st32.st_uid;
statbuf->st_gid = st32.st_gid;
statbuf->st_rdev = st32.st_rdev;
statbuf->st_size = st32.st_size;
statbuf->st_atime = st32.st_atime;
statbuf->st_mtime = st32.st_mtime;
statbuf->st_ctime = st32.st_ctime;
}
return ret;
}
else
return stat (name, statbuf);
# else
return stat (name, statbuf);
# endif
}
#endif /*HAVE_STAT*/
/* A wrapper around open to handle Unicode file names under Windows. */
int
gnupg_open (const char *name, int flags, unsigned int mode)
{
#ifdef HAVE_W32_SYSTEM
if (any8bitchar (name))
{
wchar_t *wname;
int ret;
wname = utf8_to_wchar (name);
if (!wname)
return -1;
ret = _wopen (wname, flags, mode);
xfree (wname);
return ret;
}
else
return open (name, flags, mode);
#else
return open (name, flags, mode);
#endif
}
/* A wrapper around opendir to handle Unicode file names under
* Windows. This assumes the mingw toolchain. */
gnupg_dir_t
gnupg_opendir (const char *name)
{
#ifdef HAVE_W32_SYSTEM
_WDIR *dir;
wchar_t *wname;
#else
DIR *dir;
#endif
gnupg_dir_t gdir;
#ifdef HAVE_W32_SYSTEM
/* Note: See gpgtar-create for an alternative implementation which
* could be used here to avoid a mingw dependency. */
wname = utf8_to_wchar (name);
if (!wname)
return NULL;
dir = _wopendir (wname);
xfree (wname);
#else
dir = opendir (name);
#endif
if (!dir)
return NULL;
gdir = xtrymalloc (sizeof *gdir);
if (!gdir)
{
int save_errno = errno;
#ifdef HAVE_W32_SYSTEM
_wclosedir (dir);
#else
closedir (dir);
#endif
gpg_err_set_errno (save_errno);
return NULL;
}
gdir->dir = dir;
gdir->namesize = 0;
gdir->dirent.d_name = gdir->name;
return gdir;
}
gnupg_dirent_t
gnupg_readdir (gnupg_dir_t gdir)
{
#ifdef HAVE_W32_SYSTEM
char *namebuffer = NULL;
struct _wdirent *de;
#else
struct dirent *de;
#endif
size_t n;
gnupg_dirent_t gde;
const char *name;
if (!gdir)
{
gpg_err_set_errno (EINVAL);
return 0;
}
#ifdef HAVE_W32_SYSTEM
de = _wreaddir (gdir->dir);
if (!de)
return NULL;
namebuffer = wchar_to_utf8 (de->d_name);
if (!namebuffer)
return NULL;
name = namebuffer;
#else
de = readdir (gdir->dir);
if (!de)
return NULL;
name = de->d_name;
#endif
gde = &gdir->dirent;
n = strlen (name);
if (gdir->namesize)
{
/* Use allocated buffer. */
if (n+1 >= gdir->namesize || !gde->d_name)
{
gdir->namesize = n + 256;
xfree (gde->d_name);
gde->d_name = xtrymalloc (gdir->namesize);
if (!gde->d_name)
return NULL; /* ERRNO is already set. */
}
strcpy (gde->d_name, name);
}
else if (n+1 >= sizeof (gdir->name))
{
/* Switch to allocated buffer. */
gdir->namesize = n + 256;
gde->d_name = xtrymalloc (gdir->namesize);
if (!gde->d_name)
return NULL; /* ERRNO is already set. */
strcpy (gde->d_name, name);
}
else
{
/* Use static buffer. */
gde->d_name = gdir->name;
strcpy (gde->d_name, name);
}
#ifdef HAVE_W32_SYSTEM
xfree (namebuffer);
#endif
return gde;
}
int
gnupg_closedir (gnupg_dir_t gdir)
{
#ifdef HAVE_W32_SYSTEM
_WDIR *dir;
#else
DIR *dir;
#endif
if (!gdir)
return 0;
dir = gdir->dir;
if (gdir->namesize)
xfree (gdir->dirent.d_name);
xfree (gdir);
#ifdef HAVE_W32_SYSTEM
return _wclosedir (dir);
#else
return closedir (dir);
#endif
}
/* Try to set an envvar. Print only a notice on error. */
#ifndef HAVE_W32_SYSTEM
static void
try_set_envvar (const char *name, const char *value, int silent)
{
if (gnupg_setenv (name, value, 1))
if (!silent)
log_info ("error setting envvar %s to '%s': %s\n", name, value,
gpg_strerror (my_error_from_syserror ()));
}
#endif /*!HAVE_W32_SYSTEM*/
/* Switch to USER which is either a name or an UID. This is a nop
* under Windows. Note that in general it is only possible to switch
* to another user id if the process is running under root. if silent
* is set no diagnostics are printed. */
gpg_error_t
gnupg_chuid (const char *user, int silent)
{
#ifdef HAVE_W32_SYSTEM
(void)user; /* Not implemented for Windows - ignore. */
(void)silent;
return 0;
#elif HAVE_PWD_H /* A proper Unix */
unsigned long ul;
struct passwd *pw;
struct stat st;
char *endp;
gpg_error_t err;
gpg_err_set_errno (0);
ul = strtoul (user, &endp, 10);
if (errno || endp == user || *endp)
pw = getpwnam (user); /* Not a number; assume USER is a name. */
else
pw = getpwuid ((uid_t)ul);
if (!pw)
{
if (!silent)
log_error ("user '%s' not found\n", user);
return my_error (GPG_ERR_NOT_FOUND);
}
/* Try to set some envvars even if we are already that user. */
if (!stat (pw->pw_dir, &st))
try_set_envvar ("HOME", pw->pw_dir, silent);
try_set_envvar ("USER", pw->pw_name, silent);
try_set_envvar ("LOGNAME", pw->pw_name, silent);
#ifdef _AIX
try_set_envvar ("LOGIN", pw->pw_name, silent);
#endif
if (getuid () == pw->pw_uid)
return 0; /* We are already this user. */
/* If we need to switch set PATH to a standard value and make sure
* GNUPGHOME is not set. */
try_set_envvar ("PATH", "/usr/local/bin:/usr/bin:/bin", silent);
if (gnupg_unsetenv ("GNUPGHOME"))
if (!silent)
log_info ("error unsetting envvar %s: %s\n", "GNUPGHOME",
gpg_strerror (gpg_error_from_syserror ()));
if (initgroups (pw->pw_name, pw->pw_gid))
{
err = my_error_from_syserror ();
if (!silent)
log_error ("error setting supplementary groups for '%s': %s\n",
pw->pw_name, gpg_strerror (err));
return err;
}
if (setuid (pw->pw_uid))
{
err = my_error_from_syserror ();
log_error ("error switching to user '%s': %s\n",
pw->pw_name, gpg_strerror (err));
return err;
}
return 0;
#else /*!HAVE_PWD_H */
if (!silent)
log_info ("system is missing passwd querying functions\n");
return my_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
#ifdef HAVE_W32_SYSTEM
/* Return the user's security identifier from the current process. */
PSID
w32_get_user_sid (void)
{
int okay = 0;
HANDLE proc = NULL;
HANDLE token = NULL;
TOKEN_USER *user = NULL;
PSID sid = NULL;
DWORD tokenlen, sidlen;
proc = OpenProcess (PROCESS_QUERY_INFORMATION, FALSE, GetCurrentProcessId());
if (!proc)
goto leave;
if (!OpenProcessToken (proc, TOKEN_QUERY, &token))
goto leave;
if (!GetTokenInformation (token, TokenUser, NULL, 0, &tokenlen)
&& GetLastError() != ERROR_INSUFFICIENT_BUFFER)
goto leave;
user = xtrymalloc (tokenlen);
if (!user)
goto leave;
if (!GetTokenInformation (token, TokenUser, user, tokenlen, &tokenlen))
goto leave;
if (!IsValidSid (user->User.Sid))
goto leave;
sidlen = GetLengthSid (user->User.Sid);
sid = xtrymalloc (sidlen);
if (!sid)
goto leave;
if (!CopySid (sidlen, sid, user->User.Sid))
goto leave;
okay = 1;
leave:
xfree (user);
if (token)
CloseHandle (token);
if (proc)
CloseHandle (proc);
if (!okay)
{
xfree (sid);
sid = NULL;
}
return sid;
}
#endif /*HAVE_W32_SYSTEM*/
/* Support for inotify under Linux. */
/* Store a new inotify file handle for FNAME at R_FD or return an
* error code. This file descriptor watch the removal of FNAME. */
gpg_error_t
gnupg_inotify_watch_delete_self (int *r_fd, const char *fname)
{
#if HAVE_INOTIFY_INIT
gpg_error_t err;
int fd;
*r_fd = -1;
if (!fname)
return my_error (GPG_ERR_INV_VALUE);
fd = inotify_init ();
if (fd == -1)
return my_error_from_syserror ();
if (inotify_add_watch (fd, fname, IN_DELETE_SELF) == -1)
{
err = my_error_from_syserror ();
close (fd);
return err;
}
*r_fd = fd;
return 0;
#else /*!HAVE_INOTIFY_INIT*/
(void)fname;
*r_fd = -1;
return my_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!HAVE_INOTIFY_INIT*/
}
/* Store a new inotify file handle for SOCKET_NAME at R_FD or return
* an error code. */
gpg_error_t
gnupg_inotify_watch_socket (int *r_fd, const char *socket_name)
{
#if HAVE_INOTIFY_INIT
gpg_error_t err;
char *fname;
int fd;
char *p;
*r_fd = -1;
if (!socket_name)
return my_error (GPG_ERR_INV_VALUE);
fname = xtrystrdup (socket_name);
if (!fname)
return my_error_from_syserror ();
fd = inotify_init ();
if (fd == -1)
{
err = my_error_from_syserror ();
xfree (fname);
return err;
}
/* We need to watch the directory for the file because there won't
* be an IN_DELETE_SELF for a socket file. To handle a removal of
* the directory we also watch the directory itself. */
p = strrchr (fname, '/');
if (p)
*p = 0;
if (inotify_add_watch (fd, fname,
(IN_DELETE|IN_DELETE_SELF|IN_EXCL_UNLINK)) == -1)
{
err = my_error_from_syserror ();
close (fd);
xfree (fname);
return err;
}
xfree (fname);
*r_fd = fd;
return 0;
#else /*!HAVE_INOTIFY_INIT*/
(void)socket_name;
*r_fd = -1;
return my_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!HAVE_INOTIFY_INIT*/
}
/* Read an inotify event and return true if it matches NAME or if it
* sees an IN_DELETE_SELF event for the directory of NAME. */
int
gnupg_inotify_has_name (int fd, const char *name)
{
#if USE_NPTH && HAVE_INOTIFY_INIT
#define BUFSIZE_FOR_INOTIFY (sizeof (struct inotify_event) + 255 + 1)
union {
struct inotify_event ev;
char _buf[sizeof (struct inotify_event) + 255 + 1];
} buf;
struct inotify_event *evp;
int n;
n = npth_read (fd, &buf, sizeof buf);
/* log_debug ("notify read: n=%d\n", n); */
evp = &buf.ev;
while (n >= sizeof (struct inotify_event))
{
/* log_debug (" mask=%x len=%u name=(%s)\n", */
/* evp->mask, (unsigned int)evp->len, evp->len? evp->name:""); */
if ((evp->mask & IN_UNMOUNT))
{
/* log_debug (" found (dir unmounted)\n"); */
return 3; /* Directory was unmounted. */
}
if ((evp->mask & IN_DELETE_SELF))
{
/* log_debug (" found (dir removed)\n"); */
return 2; /* Directory was removed. */
}
if ((evp->mask & IN_DELETE))
{
if (evp->len >= strlen (name) && !strcmp (evp->name, name))
{
/* log_debug (" found (file removed)\n"); */
return 1; /* File was removed. */
}
}
n -= sizeof (*evp) + evp->len;
evp = (struct inotify_event *)(void *)
((char *)evp + sizeof (*evp) + evp->len);
}
#else /*!(USE_NPTH && HAVE_INOTIFY_INIT)*/
(void)fd;
(void)name;
#endif /*!(USE_NPTH && HAVE_INOTIFY_INIT)*/
return 0; /* Not found. */
}
/* Return a malloc'ed string that is the path to the passed
* unix-domain socket (or return NULL if this is not a valid
* unix-domain socket). We use a plain int here because it is only
* used on Linux.
*
* FIXME: This function needs to be moved to libassuan. */
#ifndef HAVE_W32_SYSTEM
char *
gnupg_get_socket_name (int fd)
{
struct sockaddr_un un;
socklen_t len = sizeof(un);
char *name = NULL;
if (getsockname (fd, (struct sockaddr*)&un, &len) != 0)
log_error ("could not getsockname(%d): %s\n", fd,
gpg_strerror (my_error_from_syserror ()));
else if (un.sun_family != AF_UNIX)
log_error ("file descriptor %d is not a unix-domain socket\n", fd);
else if (len <= offsetof (struct sockaddr_un, sun_path))
log_error ("socket name not present for file descriptor %d\n", fd);
else if (len > sizeof(un))
log_error ("socket name for file descriptor %d was truncated "
"(passed %zu bytes, wanted %u)\n", fd, sizeof(un), len);
else
{
size_t namelen = len - offsetof (struct sockaddr_un, sun_path);
/* log_debug ("file descriptor %d has path %s (%zu octets)\n", fd, */
/* un.sun_path, namelen); */
name = xtrymalloc (namelen + 1);
if (!name)
log_error ("failed to allocate memory for name of fd %d: %s\n",
fd, gpg_strerror (my_error_from_syserror ()));
else
{
memcpy (name, un.sun_path, namelen);
name[namelen] = 0;
}
}
return name;
}
#endif /*!HAVE_W32_SYSTEM*/
/* Check whether FD is valid. */
int
gnupg_fd_valid (int fd)
{
int d = dup (fd);
if (d < 0)
return 0;
close (d);
return 1;
}
/* Open a stream from FD (a file descriptor on POSIX, a system
handle on Windows), non-closed. */
estream_t
open_stream_nc (gnupg_fd_t fd, const char *mode)
{
es_syshd_t syshd;
#ifdef HAVE_W32_SYSTEM
syshd.type = ES_SYSHD_HANDLE;
syshd.u.handle = fd;
#else
syshd.type = ES_SYSHD_FD;
syshd.u.fd = fd;
#endif
return es_sysopen_nc (&syshd, mode);
}
diff --git a/common/t-iobuf.c b/common/t-iobuf.c
index aacf27a8b..9aa0720f6 100644
--- a/common/t-iobuf.c
+++ b/common/t-iobuf.c
@@ -1,440 +1,440 @@
/* t-iobuf.c - Simple module test for iobuf.c
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: (LGPL-3.0-or-later OR GPL-2.0-or-later)
*/
/* The whole code here does not very fill into our general test frame
- * work patter. But let's keep it as it is. */
+ * work pattern. But let's keep it as it is. */
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <assert.h>
#include <stdlib.h>
#include "iobuf.h"
#include "stringhelp.h"
static void *
xmalloc (size_t n)
{
void *p = malloc (n);
if (!p)
{
fprintf (stderr, "t-iobuf: out of core\n");
abort ();
}
return p;
}
/* Return every other byte. In particular, reads two bytes, returns
the second one. */
static int
every_other_filter (void *opaque, int control,
iobuf_t chain, byte *buf, size_t *len)
{
(void) opaque;
if (control == IOBUFCTRL_DESC)
{
mem2str (buf, "every_other_filter", *len);
}
if (control == IOBUFCTRL_UNDERFLOW)
{
int c = iobuf_readbyte (chain);
int c2;
if (c == -1)
c2 = -1;
else
c2 = iobuf_readbyte (chain);
/* printf ("Discarding %d (%c); return %d (%c)\n", c, c, c2, c2); */
if (c2 == -1)
{
*len = 0;
return -1;
}
*buf = c2;
*len = 1;
return 0;
}
return 0;
}
static int
double_filter (void *opaque, int control,
iobuf_t chain, byte *buf, size_t *len)
{
(void) opaque;
if (control == IOBUFCTRL_DESC)
{
mem2str (buf, "double_filter", *len);
}
if (control == IOBUFCTRL_FLUSH)
{
int i;
for (i = 0; i < *len; i ++)
{
int rc;
rc = iobuf_writebyte (chain, buf[i]);
if (rc)
return rc;
rc = iobuf_writebyte (chain, buf[i]);
if (rc)
return rc;
}
}
return 0;
}
struct content_filter_state
{
int pos;
int len;
const char *buffer;
};
static struct content_filter_state *
content_filter_new (const char *buffer)
{
struct content_filter_state *state
= xmalloc (sizeof (struct content_filter_state));
state->pos = 0;
state->len = strlen (buffer);
state->buffer = buffer;
return state;
}
static int
content_filter (void *opaque, int control,
iobuf_t chain, byte *buf, size_t *len)
{
struct content_filter_state *state = opaque;
(void) chain;
if (control == IOBUFCTRL_UNDERFLOW)
{
int remaining = state->len - state->pos;
int toread = *len;
assert (toread > 0);
if (toread > remaining)
toread = remaining;
memcpy (buf, &state->buffer[state->pos], toread);
state->pos += toread;
*len = toread;
if (toread == 0)
return -1;
return 0;
}
return 0;
}
int
main (int argc, char *argv[])
{
(void) argc;
(void) argv;
/* A simple test to make sure filters work. We use a static buffer
and then add a filter in front of it that returns every other
character. */
{
char *content = "0123456789abcdefghijklm";
iobuf_t iobuf;
int c;
int n;
int rc;
iobuf = iobuf_temp_with_content (content, strlen (content));
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
assert (rc == 0);
n = 0;
while ((c = iobuf_readbyte (iobuf)) != -1)
{
/* printf ("%d: %c\n", n + 1, (char) c); */
assert (content[2 * n + 1] == c);
n ++;
}
/* printf ("Got EOF after reading %d bytes (content: %d)\n", */
/* n, strlen (content)); */
assert (n == strlen (content) / 2);
iobuf_close (iobuf);
}
/* A simple test to check buffering. Make sure that when we add a
filter to a pipeline, any buffered data gets processed by the */
{
char *content = "0123456789abcdefghijklm";
iobuf_t iobuf;
int c;
int n;
int rc;
int i;
iobuf = iobuf_temp_with_content (content, strlen (content));
n = 0;
for (i = 0; i < 10; i ++)
{
c = iobuf_readbyte (iobuf);
assert (content[i] == c);
n ++;
}
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
assert (rc == 0);
while ((c = iobuf_readbyte (iobuf)) != -1)
{
/* printf ("%d: %c\n", n + 1, (char) c); */
assert (content[2 * (n - 5) + 1] == c);
n ++;
}
assert (n == 10 + (strlen (content) - 10) / 2);
iobuf_close (iobuf);
}
/* A simple test to check that iobuf_read_line works. */
{
/* - 3 characters plus new line
- 4 characters plus new line
- 5 characters plus new line
- 5 characters, no new line
*/
char *content = "abc\ndefg\nhijkl\nmnopq";
iobuf_t iobuf;
byte *buffer;
unsigned size;
unsigned max_len;
int n;
iobuf = iobuf_temp_with_content (content, strlen(content));
/* We read a line with 3 characters plus a newline. If we
allocate a buffer that is 5 bytes long, then no reallocation
should be required. */
size = 5;
buffer = xmalloc (size);
max_len = 100;
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
assert (n == 4);
assert (strcmp (buffer, "abc\n") == 0);
assert (size == 5);
assert (max_len == 100);
free (buffer);
/* We now read a line with 4 characters plus a newline. This
requires 6 bytes of storage. We pass a buffer that is 5 bytes
large and we allow the buffer to be grown. */
size = 5;
buffer = xmalloc (size);
max_len = 100;
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
assert (n == 5);
assert (strcmp (buffer, "defg\n") == 0);
assert (size >= 6);
/* The string shouldn't have been truncated (max_len == 0). */
assert (max_len == 100);
free (buffer);
/* We now read a line with 5 characters plus a newline. This
requires 7 bytes of storage. We pass a buffer that is 5 bytes
large and we don't allow the buffer to be grown. */
size = 5;
buffer = xmalloc (size);
max_len = 5;
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
assert (n == 4);
/* Note: the string should still have a trailing \n. */
assert (strcmp (buffer, "hij\n") == 0);
assert (size == 5);
/* The string should have been truncated (max_len == 0). */
assert (max_len == 0);
free (buffer);
/* We now read a line with 6 characters without a newline. This
requires 7 bytes of storage. We pass a NULL buffer and we
don't allow the buffer to be grown larger than 5 bytes. */
size = 5;
buffer = NULL;
max_len = 5;
n = iobuf_read_line (iobuf, &buffer, &size, &max_len);
assert (n == 4);
/* Note: the string should still have a trailing \n. */
assert (strcmp (buffer, "mno\n") == 0);
assert (size == 5);
/* The string should have been truncated (max_len == 0). */
assert (max_len == 0);
free (buffer);
iobuf_close (iobuf);
}
{
/* - 10 characters, EOF
- 17 characters, EOF
*/
char *content = "abcdefghijklmnopq";
char *content2 = "0123456789";
iobuf_t iobuf;
int rc;
int c;
int n;
int lastc = 0;
struct content_filter_state *state;
iobuf = iobuf_temp_with_content (content, strlen(content));
rc = iobuf_push_filter (iobuf,
content_filter,
state=content_filter_new (content2));
assert (rc == 0);
n = 0;
while (1)
{
c = iobuf_readbyte (iobuf);
if (c == -1 && lastc == -1)
{
/* printf("Two EOFs in a row. Done.\n"); */
assert (n == 27);
break;
}
lastc = c;
if (c == -1)
{
/* printf("After %d bytes, got EOF.\n", n); */
assert (n == 10 || n == 27);
}
else
{
n ++;
/* printf ("%d: '%c' (%d)\n", n, c, c); */
}
}
iobuf_close (iobuf);
free (state);
}
/* Write some data to a temporary filter. Push a new filter. The
already written data should not be processed by the new
filter. */
{
iobuf_t iobuf;
int rc;
char *content = "0123456789";
char *content2 = "abc";
char buffer[4096];
int n;
iobuf = iobuf_temp ();
assert (iobuf);
rc = iobuf_write (iobuf, content, strlen (content));
assert (rc == 0);
rc = iobuf_push_filter (iobuf, double_filter, NULL);
assert (rc == 0);
/* Include a NUL. */
rc = iobuf_write (iobuf, content2, strlen (content2) + 1);
assert (rc == 0);
n = iobuf_temp_to_buffer (iobuf, buffer, sizeof (buffer));
#if 0
printf ("Got %d bytes\n", n);
printf ("buffer: `");
fwrite (buffer, n, 1, stdout);
fputc ('\'', stdout);
fputc ('\n', stdout);
#endif
assert (n == strlen (content) + 2 * (strlen (content2) + 1));
assert (strcmp (buffer, "0123456789aabbcc") == 0);
iobuf_close (iobuf);
}
{
iobuf_t iobuf;
int rc;
char content[] = "0123456789";
int n;
int c;
char buffer[10];
assert (sizeof buffer == sizeof content - 1);
iobuf = iobuf_temp_with_content (content, strlen (content));
assert (iobuf);
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
assert (rc == 0);
rc = iobuf_push_filter (iobuf, every_other_filter, NULL);
assert (rc == 0);
for (n = 0; (c = iobuf_get (iobuf)) != -1; n ++)
{
/* printf ("%d: `%c'\n", n, c); */
buffer[n] = c;
}
assert (n == 2);
assert (buffer[0] == '3');
assert (buffer[1] == '7');
iobuf_close (iobuf);
}
return 0;
}
diff --git a/common/tlv-builder.c b/common/tlv-builder.c
index 59e2691e0..0fa5fc2cc 100644
--- a/common/tlv-builder.c
+++ b/common/tlv-builder.c
@@ -1,388 +1,388 @@
/* tlv-builder.c - Build DER encoded objects
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gpg-error.h>
#include "util.h"
#include "tlv.h"
struct item_s
{
int class;
int tag;
unsigned int is_constructed:1; /* This is a constructed element. */
unsigned int is_stop:1; /* This is a STOP item. */
const void *value;
size_t valuelen;
char *buffer; /* Malloced space or NULL. */
};
struct tlv_builder_s
{
gpg_error_t error; /* Last error. */
int use_secure; /* Use secure memory for the result. */
size_t nallocateditems; /* Number of allocated items. */
size_t nitems; /* Number of used items. */
struct item_s *items; /* Array of items. */
int laststop; /* Used as return value of compute_length. */
};
/* Allocate a new TLV Builder instance. Returns NULL on error. If
* SECURE is set the final object is stored in secure memory. */
tlv_builder_t
tlv_builder_new (int secure)
{
tlv_builder_t tb;
tb = xtrycalloc (1, sizeof *tb);
if (tb && secure)
tb->use_secure = 1;
return tb;
}
/* Make sure the array of items is large enough for one new item.
* Records any error in TB and returns true in that case. */
static int
ensure_space (tlv_builder_t tb)
{
struct item_s *newitems;
if (!tb || tb->error)
return 1;
if (tb->nitems == tb->nallocateditems)
{
tb->nallocateditems += 32;
newitems = gpgrt_reallocarray (tb->items, tb->nitems,
tb->nallocateditems, sizeof *newitems);
if (!newitems)
tb->error = gpg_error_from_syserror ();
else
tb->items = newitems;
}
return !!tb->error;
}
/* Add a new primitive element to the builder instance TB. The
* element is described by CLASS, TAG, VALUE, and VALUEEN. CLASS and
* TAG must describe a primitive element and (VALUE,VALUELEN) specify
* its value. The value is a pointer and its object must not be
- * changed as long as the instance TB exists. For a TAG_NULL no vlaue
+ * changed as long as the instance TB exists. For a TAG_NULL no value
* is expected. Errors are not returned but recorded for later
* retrieval. */
void
tlv_builder_add_ptr (tlv_builder_t tb, int class, int tag,
void *value, size_t valuelen)
{
if (ensure_space (tb))
return;
tb->items[tb->nitems].class = class;
tb->items[tb->nitems].tag = tag;
tb->items[tb->nitems].value = value;
tb->items[tb->nitems].valuelen = valuelen;
tb->nitems++;
}
/* This is the same as tlv_builder_add_ptr but it takes a copy of the
* value and thus the caller does not need to care about it. */
void
tlv_builder_add_val (tlv_builder_t tb, int class, int tag,
const void *value, size_t valuelen)
{
void *p;
if (ensure_space (tb))
return;
if (!value || !valuelen)
{
tb->error = gpg_error (GPG_ERR_INV_VALUE);
return;
}
p = tb->use_secure? xtrymalloc_secure (valuelen) : xtrymalloc (valuelen);
if (!p)
{
tb->error = gpg_error_from_syserror ();
return;
}
memcpy (p, value, valuelen);
tb->items[tb->nitems].buffer = p;
tb->items[tb->nitems].class = class;
tb->items[tb->nitems].tag = tag;
tb->items[tb->nitems].value = p;
tb->items[tb->nitems].valuelen = valuelen;
tb->nitems++;
}
/* Add a new constructed object to the builder instance TB. The
* object is described by CLASS and TAG which must describe a
* constructed object. The elements of the constructed objects are
* added with more call to the add functions. To close a constructed
* element a call to tlv_builer_add_end is required. Errors are not
* returned but recorded for later retrieval. */
void
tlv_builder_add_tag (tlv_builder_t tb, int class, int tag)
{
if (ensure_space (tb))
return;
tb->items[tb->nitems].class = class;
tb->items[tb->nitems].tag = tag;
tb->items[tb->nitems].is_constructed = 1;
tb->nitems++;
}
/* A call to this function closes a constructed element. This must be
* called even for an empty constructed element. */
void
tlv_builder_add_end (tlv_builder_t tb)
{
if (ensure_space (tb))
return;
tb->items[tb->nitems].is_stop = 1;
tb->nitems++;
}
/* Compute and set the length of all constructed elements in the item
* array of TB starting at IDX up to the corresponding stop item. On
* error tb->error is set. */
static size_t
compute_lengths (tlv_builder_t tb, int idx)
{
size_t total = 0;
if (tb->error)
return 0;
for (; idx < tb->nitems; idx++)
{
if (tb->items[idx].is_stop)
{
tb->laststop = idx;
break;
}
if (tb->items[idx].is_constructed)
{
tb->items[idx].valuelen = compute_lengths (tb, idx+1);
if (tb->error)
return 0;
/* Note: The last processed IDX is stored at tb->LASTSTOP. */
}
total += get_tlv_length (tb->items[idx].class, tb->items[idx].tag,
tb->items[idx].is_constructed,
tb->items[idx].valuelen);
if (tb->items[idx].is_constructed)
idx = tb->laststop;
}
return total;
}
/* Return the constructed DER encoding and release this instance. On
* success the object is stored at R_OBJ and its length at R_OBJLEN.
* The caller needs to release that memory. On error NULL is stored
* at R_OBJ and an error code is returned. Note than an error may
* stem from any of the previous call made to this object or from
* constructing the the DER object. */
gpg_error_t
tlv_builder_finalize (tlv_builder_t tb, void **r_obj, size_t *r_objlen)
{
gpg_error_t err;
membuf_t mb;
int mb_initialized = 0;
int idx;
*r_obj = NULL;
*r_objlen = 0;
if (!tb)
return gpg_error (GPG_ERR_INTERNAL);
if (tb->error)
{
err = tb->error;
goto leave;
}
if (!tb->nitems || !tb->items[tb->nitems-1].is_stop)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
compute_lengths (tb, 0);
err = tb->error;
if (err)
goto leave;
/* for (idx=0; idx < tb->nitems; idx++) */
/* log_debug ("TLVB[%2d]: c=%d t=%2d %s p=%p l=%zu\n", */
/* idx, */
/* tb->items[idx].class, */
/* tb->items[idx].tag, */
/* tb->items[idx].is_stop? "stop": */
/* tb->items[idx].is_constructed? "cons":"prim", */
/* tb->items[idx].value, */
/* tb->items[idx].valuelen); */
if (tb->use_secure)
init_membuf_secure (&mb, 512);
else
init_membuf (&mb, 512);
mb_initialized = 1;
for (idx=0; idx < tb->nitems; idx++)
{
if (tb->items[idx].is_stop)
continue;
put_tlv_to_membuf (&mb, tb->items[idx].class, tb->items[idx].tag,
tb->items[idx].is_constructed,
tb->items[idx].valuelen);
if (tb->items[idx].value)
put_membuf (&mb, tb->items[idx].value, tb->items[idx].valuelen);
}
*r_obj = get_membuf (&mb, r_objlen);
if (!*r_obj)
err = gpg_error_from_syserror ();
mb_initialized = 0;
leave:
if (mb_initialized)
xfree (get_membuf (&mb, NULL));
for (idx=0; idx < tb->nitems; idx++)
xfree (tb->items[idx].buffer);
xfree (tb->items);
xfree (tb);
return err;
}
/* Write TAG of CLASS to MEMBUF. CONSTRUCTED is a flag telling
* whether the value is constructed. LENGTH gives the length of the
* value, if it is 0 undefinite length is assumed. LENGTH is ignored
* for the NULL tag. TAG must be less that 0x1f. */
void
put_tlv_to_membuf (membuf_t *membuf, int class, int tag,
int constructed, size_t length)
{
unsigned char buf[20];
int buflen = 0;
int i;
if (tag < 0x1f)
{
*buf = (class << 6) | tag;
if (constructed)
*buf |= 0x20;
buflen++;
}
else
BUG ();
if (!tag && !class)
buf[buflen++] = 0; /* end tag */
else if (tag == TAG_NULL && !class)
buf[buflen++] = 0; /* NULL tag */
else if (!length)
buf[buflen++] = 0x80; /* indefinite length */
else if (length < 128)
buf[buflen++] = length;
else
{
/* If we know the sizeof a size_t we could support larger
* objects - however this is pretty ridiculous */
i = (length <= 0xff ? 1:
length <= 0xffff ? 2:
length <= 0xffffff ? 3: 4);
buf[buflen++] = (0x80 | i);
if (i > 3)
buf[buflen++] = length >> 24;
if (i > 2)
buf[buflen++] = length >> 16;
if (i > 1)
buf[buflen++] = length >> 8;
buf[buflen++] = length;
}
put_membuf (membuf, buf, buflen);
}
/* Return the length of the to be constructed TLV. CONSTRUCTED is a
* flag telling whether the value is constructed. LENGTH gives the
* length of the value, if it is 0 undefinite length is assumed.
* LENGTH is ignored for the NULL tag. TAG must be less that 0x1f. */
size_t
get_tlv_length (int class, int tag, int constructed, size_t length)
{
size_t buflen = 0;
int i;
(void)constructed; /* Not used, but passed for uniformity of such calls. */
/* coverity[identical_branches] */
if (tag < 0x1f)
{
buflen++;
}
else
{
buflen++; /* assume one and let the actual write function bail out */
}
if (!tag && !class)
buflen++; /* end tag */
else if (tag == TAG_NULL && !class)
buflen++; /* NULL tag */
else if (!length)
buflen++; /* indefinite length */
else if (length < 128)
buflen++;
else
{
i = (length <= 0xff ? 1:
length <= 0xffff ? 2:
length <= 0xffffff ? 3: 4);
buflen++;
if (i > 3)
buflen++;
if (i > 2)
buflen++;
if (i > 1)
buflen++;
buflen++;
}
return buflen + length;
}
diff --git a/common/tlv-parser.c b/common/tlv-parser.c
index c9b33d4b6..2cafac3e9 100644
--- a/common/tlv-parser.c
+++ b/common/tlv-parser.c
@@ -1,788 +1,788 @@
/* tlv-parser.c - Parse BER encoded objects
* Copyright (C) 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: LGPL-2.1-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gpg-error.h>
#include "util.h"
#include "tlv.h"
#define TLV_MAX_DEPTH 25
struct bufferlist_s
{
struct bufferlist_s *next;
char *buffer;
};
/* An object to control the ASN.1 parsing. */
struct tlv_parser_s
{
- /* The orginal buffer with the entire pkcs#12 object and its length. */
+ /* The original buffer with the entire pkcs#12 object and its length. */
const unsigned char *origbuffer;
size_t origbufsize;
/* The current buffer we are working on and its length. */
const unsigned char *buffer;
size_t bufsize;
int in_ndef; /* Flag indicating that we are in a NDEF. */
int pending; /* The last tlv_next has not yet been processed. */
struct tag_info ti; /* The current tag. */
gpg_error_t lasterr; /* Last error from tlv function. */
const char *lastfunc;/* Name of last called function. */
int verbosity; /* Arg from tlv_parser_new. */
struct bufferlist_s *bufferlist; /* To keep track of malloced buffers. */
unsigned int stacklen; /* Used size of the stack. */
struct {
const unsigned char *buffer; /* Saved value of BUFFER. */
size_t bufsize; /* Saved value of BUFSIZE. */
size_t length; /* Length of the container (ti.length). */
int in_ndef; /* Saved IN_NDEF flag (ti.ndef). */
} stack[TLV_MAX_DEPTH];
};
static unsigned char *cram_octet_string (const unsigned char *input,
size_t length, size_t *r_newlength);
static int need_octet_string_cramming (const unsigned char *input,
size_t length);
void
_tlv_parser_dump_tag (const char *text, int lno, tlv_parser_t tlv)
{
struct tag_info *ti;
if (!tlv || tlv->verbosity < 2)
return;
ti = &tlv->ti;
log_debug ("p12_parse:%s:%d: @%04zu class=%d tag=%lu len=%zu nhdr=%zu %s%s\n",
text, lno,
(size_t)(tlv->buffer - tlv->origbuffer) - ti->nhdr,
ti->class, ti->tag, ti->length, ti->nhdr,
ti->is_constructed?" cons":"",
ti->ndef?" ndef":"");
}
void
_tlv_parser_dump_state (const char *text, const char *text2,
int lno, tlv_parser_t tlv)
{
if (!tlv || tlv->verbosity < 2)
return;
log_debug ("p12_parse:%s%s%s:%d: @%04zu lvl=%u %s\n",
text,
text2? "/":"", text2? text2:"",
lno,
(size_t)(tlv->buffer - tlv->origbuffer),
tlv->stacklen,
tlv->in_ndef? " in-ndef":"");
}
/* Parse the buffer at the address BUFFER which is of SIZE and return
* the tag and the length part from the TLV triplet. Update BUFFER
* and SIZE on success. Checks that the encoded length does not
* exhaust the length of the provided buffer. */
static int
parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti)
{
gpg_error_t err;
int tag;
err = parse_ber_header (buffer, size,
&ti->class, &tag,
&ti->is_constructed, &ti->ndef,
&ti->length, &ti->nhdr);
if (err)
return err;
if (tag < 0)
return gpg_error (GPG_ERR_EOVERFLOW);
ti->tag = tag;
if (ti->length > *size)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT); /* data larger than buffer. */
return 0;
}
/* Public version of parse_tag. */
gpg_error_t
tlv_parse_tag (unsigned char const **buffer, size_t *size, struct tag_info *ti)
{
return parse_tag (buffer, size, ti);
}
/* Create a new TLV object. */
tlv_parser_t
tlv_parser_new (const unsigned char *buffer, size_t bufsize, int verbosity)
{
tlv_parser_t tlv;
tlv = xtrycalloc (1, sizeof *tlv);
if (tlv)
{
tlv->origbuffer = buffer;
tlv->origbufsize = bufsize;
tlv->buffer = buffer;
tlv->bufsize = bufsize;
tlv->verbosity = verbosity;
}
return tlv;
}
/* This function can be used to store a malloced buffer into the TLV
* object. Ownership of BUFFER is thus transferred to TLV. This
* buffer will then only be released by tlv_release. */
static gpg_error_t
register_buffer (tlv_parser_t tlv, char *buffer)
{
struct bufferlist_s *item;
item = xtrycalloc (1, sizeof *item);
if (!item)
return gpg_error_from_syserror ();
item->buffer = buffer;
item->next = tlv->bufferlist;
tlv->bufferlist = item;
return 0;
}
void
tlv_parser_release (tlv_parser_t tlv)
{
if (!tlv)
return;
while (tlv->bufferlist)
{
struct bufferlist_s *save = tlv->bufferlist->next;
xfree (tlv->bufferlist->buffer);
xfree (tlv->bufferlist);
tlv->bufferlist = save;
}
xfree (tlv);
}
/* Helper for the tlv_peek functions. */
static gpg_error_t
_tlv_peek (tlv_parser_t tlv, struct tag_info *ti)
{
const unsigned char *p;
size_t n;
/* Note that we want to peek ahead of any current container but of
* course not beyond our entire buffer. */
p = tlv->buffer;
if ((p - tlv->origbuffer) > tlv->origbufsize)
return gpg_error (GPG_ERR_BUG);
n = tlv->origbufsize - (p - tlv->origbuffer);
return parse_tag (&p, &n, ti);
}
/* Look for the next tag and return true if it matches CLASS and TAG.
* Otherwise return false. No state is changed. */
int
_tlv_parser_peek (tlv_parser_t tlv, int class, int tag)
{
struct tag_info ti;
return (!_tlv_peek (tlv, &ti)
&& ti.class == class && ti.tag == tag);
}
/* Look for the next tag and return true if it is the Null tag.
* Otherwise return false. No state is changed. */
int
_tlv_parser_peek_null (tlv_parser_t tlv)
{
struct tag_info ti;
return (!_tlv_peek (tlv, &ti)
&& ti.class == CLASS_UNIVERSAL && ti.tag == TAG_NULL
&& !ti.is_constructed && !ti.length);
}
/* Helper for tlv_expect_sequence and tlv_expect_context_tag. */
static gpg_error_t
_tlv_push (tlv_parser_t tlv)
{
/* Right now our pointer is at the value of the current container.
* We push that info onto the stack. */
if (tlv->stacklen >= TLV_MAX_DEPTH)
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_MANY));
tlv->stack[tlv->stacklen].buffer = tlv->buffer;
tlv->stack[tlv->stacklen].bufsize = tlv->bufsize;
tlv->stack[tlv->stacklen].in_ndef = tlv->in_ndef;
tlv->stack[tlv->stacklen].length = tlv->ti.length;
tlv->stacklen++;
tlv->in_ndef = tlv->ti.ndef;
/* We set the size of the buffer to the TLV length if it is known or
* else to the size of the remaining entire buffer. */
if (tlv->in_ndef)
{
if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize)
return (tlv->lasterr = gpg_error (GPG_ERR_BUG));
tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer);
}
else
tlv->bufsize = tlv->ti.length;
_tlv_parser_dump_state (__func__, NULL, 0, tlv);
return 0;
}
/* Helper for tlv_next. */
static gpg_error_t
_tlv_pop (tlv_parser_t tlv)
{
size_t lastlen;
/* We reached the end of a container, either due to the size limit
* or due to an end tag. Now we pop the last container so that we
* are positioned at the value of the last container. */
if (!tlv->stacklen)
return gpg_error (GPG_ERR_EOF);
tlv->stacklen--;
tlv->in_ndef = tlv->stack[tlv->stacklen].in_ndef;
if (tlv->in_ndef)
{
/* We keep buffer but adjust bufsize to the end of the origbuffer. */
if ((tlv->buffer - tlv->origbuffer) > tlv->origbufsize)
return (tlv->lasterr = gpg_error (GPG_ERR_BUG));
tlv->bufsize = tlv->origbufsize - (tlv->buffer - tlv->origbuffer);
}
else
{
lastlen = tlv->stack[tlv->stacklen].length;
tlv->buffer = tlv->stack[tlv->stacklen].buffer;
tlv->bufsize = tlv->stack[tlv->stacklen].bufsize;
if (lastlen > tlv->bufsize)
{
log_debug ("%s: container length larger than buffer (%zu/%zu)\n",
__func__, lastlen, tlv->bufsize);
return gpg_error (GPG_ERR_INV_BER);
}
tlv->buffer += lastlen;
tlv->bufsize -= lastlen;
}
_tlv_parser_dump_state (__func__, NULL, 0, tlv);
return 0;
}
/* Parse the next tag and value. Also detect the end of a
* container. The caller should use the tlv_next macro. */
gpg_error_t
_tlv_parser_next (tlv_parser_t tlv, int lno)
{
gpg_error_t err;
tlv->lasterr = 0;
tlv->lastfunc = __func__;
if (tlv->pending)
{
tlv->pending = 0;
if (tlv->verbosity > 1)
log_debug ("%s: skipped\n", __func__);
return 0;
}
if (tlv->verbosity > 1)
log_debug ("%s: called\n", __func__);
/* If we are at the end of an ndef container pop the stack. */
if (!tlv->in_ndef && !tlv->bufsize)
{
do
err = _tlv_pop (tlv);
while (!err && !tlv->in_ndef && !tlv->bufsize);
if (err)
return (tlv->lasterr = err);
if (tlv->verbosity > 1)
log_debug ("%s: container(s) closed due to size\n", __func__);
}
again:
/* Get the next tag. */
err = parse_tag (&tlv->buffer, &tlv->bufsize, &tlv->ti);
if (err)
{
if (tlv->verbosity > 1)
log_debug ("%s: reading tag returned err=%d\n", __func__, err);
return err;
}
/* If there is an end tag in an ndef container pop the stack. Also
* pop other containers which are fully consumed. */
if (tlv->in_ndef && (tlv->ti.class == CLASS_UNIVERSAL
&& !tlv->ti.tag && !tlv->ti.is_constructed))
{
do
err = _tlv_pop (tlv);
while (!err && !tlv->in_ndef && !tlv->bufsize);
if (err)
return (tlv->lasterr = err);
if (tlv->verbosity > 1)
log_debug ("%s: container(s) closed due to end tag\n", __func__);
goto again;
}
_tlv_parser_dump_tag (__func__, lno, tlv);
return 0;
}
/* Return the current neting level of the TLV object. */
unsigned int
tlv_parser_level (tlv_parser_t tlv)
{
return tlv? tlv->stacklen : 0;
}
/* Returns the current offset of the parser. */
size_t
tlv_parser_offset (tlv_parser_t tlv)
{
return tlv? (size_t)(tlv->buffer - tlv->origbuffer) : 0;
}
/* Return a string with the last function used. If TLV is NULL an
* empty string is returned. */
const char *
tlv_parser_lastfunc (tlv_parser_t tlv)
{
return tlv? tlv->lastfunc:"";
}
const char *
tlv_parser_lasterrstr (tlv_parser_t tlv)
{
return tlv? gpg_strerror (tlv->lasterr) : "tlv parser not yet initialized";
}
/* Set a flag to indicate that the last tlv_next has not yet been
* consumed. */
void
tlv_parser_set_pending (tlv_parser_t tlv)
{
tlv->pending = 1;
}
/* Return the length of the last read tag. If with_header is 1 the
* lengtb of the header is added to the returned length. */
size_t
tlv_parser_tag_length (tlv_parser_t tlv, int with_header)
{
if (with_header)
return tlv->ti.length + tlv->ti.nhdr;
else
return tlv->ti.length;
}
/* Skip over the value of the current tag. Does not yet work for ndef
* containers. */
void
tlv_parser_skip (tlv_parser_t tlv)
{
tlv->lastfunc = __func__;
log_assert (tlv->bufsize >= tlv->ti.length);
tlv->buffer += tlv->ti.length;
tlv->bufsize -= tlv->ti.length;
}
/* Expect that the current tag is a sequence and setup the context for
* processing. */
gpg_error_t
tlv_expect_sequence (tlv_parser_t tlv)
{
tlv->lastfunc = __func__;
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SEQUENCE
&& tlv->ti.is_constructed))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
return _tlv_push (tlv);
}
/* Expect that the current tag is a context tag and setup the context
* for processing. The tag of the context is returned at R_TAG. */
gpg_error_t
tlv_expect_context_tag (tlv_parser_t tlv, int *r_tag)
{
tlv->lastfunc = __func__;
if (!(tlv->ti.class == CLASS_CONTEXT && tlv->ti.is_constructed))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
*r_tag = tlv->ti.tag;
return _tlv_push (tlv);
}
/* Expect that the current tag is a SET and setup the context for
* processing. */
gpg_error_t
tlv_expect_set (tlv_parser_t tlv)
{
tlv->lastfunc = __func__;
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_SET
&& tlv->ti.is_constructed))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
return _tlv_push (tlv);
}
/* Expect an object of CLASS with TAG and store its value at
* (R_DATA,R_DATALEN). Then skip over its value to the next tag.
* Note that the stored value is not allocated but points into
* TLV. */
gpg_error_t
tlv_expect_object (tlv_parser_t tlv, int class, int tag,
unsigned char const **r_data, size_t *r_datalen)
{
gpg_error_t err;
const unsigned char *p;
size_t n;
int needpush = 0;
tlv->lastfunc = __func__;
if (!(tlv->ti.class == class && tlv->ti.tag == tag))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
p = tlv->buffer;
n = tlv->ti.length;
if (!n && tlv->ti.ndef)
{
n = tlv->bufsize;
needpush = 1;
}
else if (!tlv->ti.length)
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
if (class == CLASS_CONTEXT && tag == 0 && tlv->ti.is_constructed
&& need_octet_string_cramming (p, n))
{
char *newbuffer;
newbuffer = cram_octet_string (p, n, r_datalen);
if (!newbuffer)
return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER));
err = register_buffer (tlv, newbuffer);
if (err)
{
xfree (newbuffer);
return (tlv->lasterr = err);
}
*r_data = newbuffer;
}
else
{
*r_data = p;
*r_datalen = n;
}
if (needpush)
return _tlv_push (tlv);
if (!(tlv->bufsize >= tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
tlv->buffer += tlv->ti.length;
tlv->bufsize -= tlv->ti.length;
return 0;
}
/* Expect that the current tag is an object string and store its value
* at (R_DATA,R_DATALEN). Then skip over its value to the next tag.
* Note that the stored value are not allocated but point into TLV.
* If ENCAPSULATES is set the octet string is used as a new
* container. R_DATA and R_DATALEN are optional. */
gpg_error_t
tlv_expect_octet_string (tlv_parser_t tlv, int encapsulates,
unsigned char const **r_data, size_t *r_datalen)
{
gpg_error_t err;
const unsigned char *p;
size_t n;
tlv->lastfunc = __func__;
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OCTET_STRING
&& (!tlv->ti.is_constructed || encapsulates)))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
p = tlv->buffer;
if (!(n=tlv->ti.length) && !tlv->ti.ndef)
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
if (encapsulates && tlv->ti.is_constructed
&& need_octet_string_cramming (p, n))
{
char *newbuffer;
newbuffer = cram_octet_string (p, n, r_datalen);
if (!newbuffer)
return (tlv->lasterr = gpg_error (GPG_ERR_BAD_BER));
err = register_buffer (tlv, newbuffer);
if (err)
{
xfree (newbuffer);
return (tlv->lasterr = err);
}
*r_data = newbuffer;
}
else
{
if (r_data)
*r_data = p;
if (r_datalen)
*r_datalen = tlv->ti.length;
}
if (encapsulates)
return _tlv_push (tlv);
if (!(tlv->bufsize >= tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
tlv->buffer += tlv->ti.length;
tlv->bufsize -= tlv->ti.length;
return 0;
}
/* Expect that the current tag is an integer and return its value at
* R_VALUE. Then skip over its value to the next tag. */
gpg_error_t
tlv_expect_integer (tlv_parser_t tlv, int *r_value)
{
const unsigned char *p;
size_t n;
int value;
tlv->lastfunc = __func__;
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER
&& !tlv->ti.is_constructed))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
p = tlv->buffer;
if (!(n=tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
/* We currently support only positive values. */
if ((*p & 0x80))
return (tlv->lasterr = gpg_error (GPG_ERR_ERANGE));
for (value = 0; n; n--)
{
value <<= 8;
value |= (*p++) & 0xff;
if (value < 0)
return (tlv->lasterr = gpg_error (GPG_ERR_EOVERFLOW));
}
*r_value = value;
if (!(tlv->bufsize >= tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
tlv->buffer += tlv->ti.length;
tlv->bufsize -= tlv->ti.length;
return 0;
}
/* Variant of tlv_expect_integer which returns an MPI. If IGNORE_ZERO
* is set a value of 0 is ignored and R_VALUE not changed and the
* function returns GPG_ERR_FALSE. No check for negative encoded
* integers is done because the old code here worked the same and we
* can't foreclose invalid encoded PKCS#12 stuff - after all it is
* PKCS#12 see https://www.cs.auckland.ac.nz/~pgut001/pubs/pfx.html */
#ifdef GCRYPT_VERSION
gpg_error_t
tlv_expect_mpinteger (tlv_parser_t tlv, int ignore_zero,
gcry_mpi_t *r_value)
{
const unsigned char *p;
size_t n;
tlv->lastfunc = __func__;
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_INTEGER
&& !tlv->ti.is_constructed))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
p = tlv->buffer;
if (!(n=tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
if (!(tlv->bufsize >= tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
tlv->buffer += tlv->ti.length;
tlv->bufsize -= tlv->ti.length;
if (ignore_zero && n == 1 && !*p)
return gpg_error (GPG_ERR_FALSE);
return gcry_mpi_scan (r_value, GCRYMPI_FMT_USG, p, n, NULL);
}
#endif /*GCRYPT_VERSION*/
/* Expect that the current tag is an object id and store its value at
* (R_OID,R_OIDLEN). Then skip over its value to the next tag. Note
* that the stored value is not allocated but points into TLV. */
gpg_error_t
tlv_expect_object_id (tlv_parser_t tlv,
unsigned char const **r_oid, size_t *r_oidlen)
{
const unsigned char *p;
size_t n;
tlv->lastfunc = __func__;
if (!(tlv->ti.class == CLASS_UNIVERSAL && tlv->ti.tag == TAG_OBJECT_ID
&& !tlv->ti.is_constructed))
return (tlv->lasterr = gpg_error (GPG_ERR_INV_OBJ));
p = tlv->buffer;
if (!(n=tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
*r_oid = p;
*r_oidlen = tlv->ti.length;
if (!(tlv->bufsize >= tlv->ti.length))
return (tlv->lasterr = gpg_error (GPG_ERR_TOO_SHORT));
tlv->buffer += tlv->ti.length;
tlv->bufsize -= tlv->ti.length;
return 0;
}
/* Given an ASN.1 chunk of a structure like:
*
* 24 NDEF: OCTET STRING -- This is not passed to us
* 04 1: OCTET STRING -- INPUT point s to here
* : 30
* 04 1: OCTET STRING
* : 80
* [...]
* 04 2: OCTET STRING
* : 00 00
* : } -- This denotes a Null tag and are the last
* -- two bytes in INPUT.
*
* The example is from Mozilla Firefox 1.0.4 which actually exports
* certs as single byte chunks of octet strings.
*
* Create a new buffer with the content of that octet string. INPUT
* is the original buffer with a LENGTH. Returns
* NULL on error or a new malloced buffer with its actual used length
* stored at R_NEWLENGTH. */
static unsigned char *
cram_octet_string (const unsigned char *input, size_t length,
size_t *r_newlength)
{
const unsigned char *s = input;
size_t n = length;
unsigned char *output, *d;
struct tag_info ti;
/* Allocate output buf. We know that it won't be longer than the
input buffer. */
d = output = xtrymalloc (length);
if (!output)
goto bailout;
while (n)
{
if (parse_tag (&s, &n, &ti))
goto bailout;
if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING
&& !ti.ndef && !ti.is_constructed)
{
memcpy (d, s, ti.length);
s += ti.length;
d += ti.length;
n -= ti.length;
}
else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed)
break; /* Ready */
else
goto bailout;
}
*r_newlength = d - output;
return output;
bailout:
xfree (output);
return NULL;
}
/* Return true if (INPUT,LENGTH) is a structure which should be passed
* to cram_octet_string. This is basically the same loop as in
* cram_octet_string but without any actual copying. */
static int
need_octet_string_cramming (const unsigned char *input, size_t length)
{
const unsigned char *s = input;
size_t n = length;
struct tag_info ti;
if (!length)
return 0;
while (n)
{
if (parse_tag (&s, &n, &ti))
return 0;
if (ti.class == CLASS_UNIVERSAL && ti.tag == TAG_OCTET_STRING
&& !ti.ndef && !ti.is_constructed)
{
s += ti.length;
n -= ti.length;
}
else if (ti.class == CLASS_UNIVERSAL && !ti.tag && !ti.is_constructed)
break; /* Ready */
else
return 0;
}
return 1;
}
diff --git a/common/tlv.h b/common/tlv.h
index afaa649d9..3136195a5 100644
--- a/common/tlv.h
+++ b/common/tlv.h
@@ -1,207 +1,207 @@
/* tlv.h - Tag-Length-Value Utilities
* Copyright (C) 2004 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef SCD_TLV_H
#define SCD_TLV_H 1
#include "membuf.h"
enum tlv_tag_class {
CLASS_UNIVERSAL = 0,
CLASS_APPLICATION = 1,
CLASS_CONTEXT = 2,
CLASS_PRIVATE =3
};
enum tlv_tag_type {
TAG_NONE = 0,
TAG_BOOLEAN = 1,
TAG_INTEGER = 2,
TAG_BIT_STRING = 3,
TAG_OCTET_STRING = 4,
TAG_NULL = 5,
TAG_OBJECT_ID = 6,
TAG_OBJECT_DESCRIPTOR = 7,
TAG_EXTERNAL = 8,
TAG_REAL = 9,
TAG_ENUMERATED = 10,
TAG_EMBEDDED_PDV = 11,
TAG_UTF8_STRING = 12,
TAG_REALTIVE_OID = 13,
TAG_SEQUENCE = 16,
TAG_SET = 17,
TAG_NUMERIC_STRING = 18,
TAG_PRINTABLE_STRING = 19,
TAG_TELETEX_STRING = 20,
TAG_VIDEOTEX_STRING = 21,
TAG_IA5_STRING = 22,
TAG_UTC_TIME = 23,
TAG_GENERALIZED_TIME = 24,
TAG_GRAPHIC_STRING = 25,
TAG_VISIBLE_STRING = 26,
TAG_GENERAL_STRING = 27,
TAG_UNIVERSAL_STRING = 28,
TAG_CHARACTER_STRING = 29,
TAG_BMP_STRING = 30
};
struct tag_info
{
int class;
int is_constructed;
unsigned long tag;
size_t length; /* length part of the TLV */
size_t nhdr;
int ndef; /* It is an indefinite length */
};
struct tlv_builder_s;
typedef struct tlv_builder_s *tlv_builder_t;
struct tlv_parser_s;
typedef struct tlv_parser_s *tlv_parser_t;
/*-- tlv.c --*/
/* Locate a TLV encoded data object in BUFFER of LENGTH and return a
pointer to value as well as its length in NBYTES. Return NULL if
it was not found or if the object does not fit into the buffer. */
const unsigned char *find_tlv (const unsigned char *buffer, size_t length,
int tag, size_t *nbytes);
/* Locate a TLV encoded data object in BUFFER of LENGTH and return a
pointer to value as well as its length in NBYTES. Return NULL if
it was not found. Note, that the function does not check whether
the value fits into the provided buffer.*/
const unsigned char *find_tlv_unchecked (const unsigned char *buffer,
size_t length,
int tag, size_t *nbytes);
/* ASN.1 BER parser: Parse BUFFER of length SIZE and return the tag
and the length part from the TLV triplet. Update BUFFER and SIZE
on success. See also tlv_parse_tag. */
gpg_error_t parse_ber_header (unsigned char const **buffer, size_t *size,
int *r_class, int *r_tag,
int *r_constructed,
int *r_ndef, size_t *r_length, size_t *r_nhdr);
/* Return the next token of an canonical encoded S-expression. BUF
is the pointer to the S-expression and BUFLEN is a pointer to the
length of this S-expression (used to validate the syntax). Both
are updated to reflect the new position. The token itself is
returned as a pointer into the original buffer at TOK and TOKLEN.
If a parentheses is the next token, TOK will be set to NULL.
TOKLEN is checked to be within the bounds. On error an error code
is returned and no pointer is not guaranteed to point to
a meaningful value. DEPTH should be initialized to 0 and will
reflect on return the actual depth of the tree. To detect the end
of the S-expression it is advisable to check DEPTH after a
successful return. */
gpg_error_t parse_sexp (unsigned char const **buf, size_t *buflen,
int *depth, unsigned char const **tok, size_t *toklen);
/*-- tlv-builder.c --*/
tlv_builder_t tlv_builder_new (int use_secure);
void tlv_builder_add_ptr (tlv_builder_t tb, int class, int tag,
void *value, size_t valuelen);
void tlv_builder_add_val (tlv_builder_t tb, int class, int tag,
const void *value, size_t valuelen);
void tlv_builder_add_tag (tlv_builder_t tb, int class, int tag);
void tlv_builder_add_end (tlv_builder_t tb);
gpg_error_t tlv_builder_finalize (tlv_builder_t tb,
void **r_obj, size_t *r_objlen);
-/* Wite a TLV header to MEMBUF. */
+/* Write a TLV header to MEMBUF. */
void put_tlv_to_membuf (membuf_t *membuf, int class, int tag,
int constructed, size_t length);
/* Count the length of a to be constructed TLV. */
size_t get_tlv_length (int class, int tag, int constructed, size_t length);
/*-- tlv-parser.c --*/
tlv_parser_t tlv_parser_new (const unsigned char *buffer, size_t bufsize,
int verbosity);
void tlv_parser_release (tlv_parser_t tlv);
void _tlv_parser_dump_tag (const char *text, int lno, tlv_parser_t tlv);
void _tlv_parser_dump_state (const char *text, const char *text2,
int lno, tlv_parser_t tlv);
int _tlv_parser_peek (tlv_parser_t tlv, int class, int tag);
int _tlv_parser_peek_null (tlv_parser_t tlv);
gpg_error_t _tlv_parser_next (tlv_parser_t tlv, int lno);
unsigned int tlv_parser_level (tlv_parser_t tlv);
size_t tlv_parser_offset (tlv_parser_t tlv);
const char *tlv_parser_lastfunc (tlv_parser_t tlv);
const char *tlv_parser_lasterrstr (tlv_parser_t tlv);
void tlv_parser_set_pending (tlv_parser_t tlv);
size_t tlv_parser_tag_length (tlv_parser_t tlv, int with_header);
void tlv_parser_skip (tlv_parser_t tlv);
gpg_error_t tlv_expect_sequence (tlv_parser_t tlv);
gpg_error_t tlv_expect_context_tag (tlv_parser_t tlv, int *r_tag);
gpg_error_t tlv_expect_set (tlv_parser_t tlv);
gpg_error_t tlv_expect_object (tlv_parser_t tlv, int class, int tag,
unsigned char const **r_data,
size_t *r_datalen);
gpg_error_t tlv_expect_octet_string (tlv_parser_t tlv, int encapsulates,
unsigned char const **r_data,
size_t *r_datalen);
gpg_error_t tlv_expect_integer (tlv_parser_t tlv, int *r_value);
#ifdef GCRYPT_VERSION
gpg_error_t tlv_expect_mpinteger (tlv_parser_t tlv, int ignore_zero,
gcry_mpi_t *r_value);
#endif
gpg_error_t tlv_expect_object_id (tlv_parser_t tlv,
unsigned char const **r_oid,
size_t *r_oidlen);
/* Easier to use wrapper around parse_ber_header. */
gpg_error_t tlv_parse_tag (unsigned char const **buffer,
size_t *size, struct tag_info *ti);
/* Convenience macro and macros to include the line number. */
#define tlv_parser_dump_tag(a,b) _tlv_parser_dump_tag ((a),__LINE__,(b))
#define tlv_parser_dump_state(a,b,c) \
_tlv_parser_dump_state ((a),(b),__LINE__,(c))
#define tlv_peek(a,b,c) _tlv_parser_peek ((a),(b),(c))
#define tlv_peek_null(a) _tlv_parser_peek_null ((a))
#define tlv_next(a) _tlv_parser_next ((a), __LINE__)
#endif /* SCD_TLV_H */
diff --git a/common/util.h b/common/util.h
index f8447aea7..671ffbcb7 100644
--- a/common/util.h
+++ b/common/util.h
@@ -1,424 +1,424 @@
/* util.h - Utility functions for GnuPG
* Copyright (C) 2001, 2002, 2003, 2004, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute and/or modify this
* part of GnuPG under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* 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 copies of the GNU General Public License
* and the GNU Lesser General Public License along with this program;
* if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GNUPG_COMMON_UTIL_H
#define GNUPG_COMMON_UTIL_H
#include <gcrypt.h> /* We need this for the memory function protos. */
#include <errno.h> /* We need errno. */
#include <gpg-error.h> /* We need gpg_error_t and estream. */
/* These error codes are used but not defined in the required
* libgpg-error version. Define them here.
* Example: (#if GPG_ERROR_VERSION_NUMBER < 0x011500 // 1.21)
*/
#if GPG_ERROR_VERSION_NUMBER < 0x012f00 /* 1.47 */
# define GPG_ERR_BAD_PUK 320
# define GPG_ERR_NO_RESET_CODE 321
# define GPG_ERR_BAD_RESET_CODE 322
#endif
#ifndef EXTERN_UNLESS_MAIN_MODULE
# if !defined (INCLUDED_BY_MAIN_MODULE)
# define EXTERN_UNLESS_MAIN_MODULE extern
# else
# define EXTERN_UNLESS_MAIN_MODULE
# endif
#endif
/* Hash function used with libksba. */
#define HASH_FNC ((void (*)(void *, const void*,size_t))gcry_md_write)
/* The length of the keygrip. This is a SHA-1 hash of the key
* parameters as generated by gcry_pk_get_keygrip. */
#define KEYGRIP_LEN 20
/* The length of the unique blob identifier as used by the keyboxd.
* This is the possible truncated fingerprint of the primary key. */
#define UBID_LEN 20
/* Get all the stuff from jnlib. */
#include "../common/logging.h"
#include "../common/stringhelp.h"
#include "../common/mischelp.h"
#include "../common/strlist.h"
#include "../common/dotlock.h"
#include "../common/utf8conv.h"
#include "../common/dynload.h"
#include "../common/fwddecl.h"
#include "../common/utilproto.h"
#include "gettime.h"
/* Redefine asprintf by our estream version which uses our own memory
allocator.. */
#define asprintf gpgrt_asprintf
#define vasprintf gpgrt_vasprintf
/* Due to a bug in mingw32's snprintf related to the 'l' modifier and
for increased portability we use our snprintf on all systems. */
#undef snprintf
#define snprintf gpgrt_snprintf
/* Replacements for macros not available with libgpg-error < 1.20. */
/* We need this type even if we are not using libreadline and or we
did not include libreadline in the current file. */
#ifndef GNUPG_LIBREADLINE_H_INCLUDED
typedef char **rl_completion_func_t (const char *, int, int);
#endif /*!GNUPG_LIBREADLINE_H_INCLUDED*/
/* Handy malloc macros - please use only them. */
#define xtrymalloc(a) gcry_malloc ((a))
#define xtrymalloc_secure(a) gcry_malloc_secure ((a))
#define xtrycalloc(a,b) gcry_calloc ((a),(b))
#define xtrycalloc_secure(a,b) gcry_calloc_secure ((a),(b))
#define xtryrealloc(a,b) gcry_realloc ((a),(b))
#define xtryreallocarray(a,b,c,d) gpgrt_reallocarray ((a),(b),(c),(d))
#define xtrystrdup(a) gcry_strdup ((a))
#define xfree(a) gcry_free ((a))
#define xfree_fnc gcry_free
#define xmalloc(a) gcry_xmalloc ((a))
#define xmalloc_secure(a) gcry_xmalloc_secure ((a))
#define xcalloc(a,b) gcry_xcalloc ((a),(b))
#define xcalloc_secure(a,b) gcry_xcalloc_secure ((a),(b))
#define xrealloc(a,b) gcry_xrealloc ((a),(b))
#define xstrdup(a) gcry_xstrdup ((a))
/* See also the xreallocarray prototype below. */
/* For compatibility with gpg 1.4 we also define these: */
#define xmalloc_clear(a) gcry_xcalloc (1, (a))
#define xmalloc_secure_clear(a) gcry_xcalloc_secure (1, (a))
/* The default error source of the application. This is different
from GPG_ERR_SOURCE_DEFAULT in that it does not depend on the
source file and thus is usable in code shared by applications.
Defined by init.c. */
extern gpg_err_source_t default_errsource;
/* Convenience function to return a gpg-error code for memory
allocation failures. This function makes sure that an error will
be returned even if accidentally ERRNO is not set. */
static inline gpg_error_t
out_of_core (void)
{
return gpg_error_from_syserror ();
}
/*-- yesno.c --*/
int answer_is_yes (const char *s);
int answer_is_yes_no_default (const char *s, int def_answer);
int answer_is_yes_no_quit (const char *s);
int answer_is_okay_cancel (const char *s, int def_answer);
/*-- xreadline.c --*/
ssize_t read_line (FILE *fp,
char **addr_of_buffer, size_t *length_of_buffer,
size_t *max_length);
/*-- sexputil.c */
char *canon_sexp_to_string (const unsigned char *canon, size_t canonlen);
void log_printcanon (const char *text,
const unsigned char *sexp, size_t sexplen);
void log_printsexp (const char *text, gcry_sexp_t sexp);
gpg_error_t make_canon_sexp (gcry_sexp_t sexp,
unsigned char **r_buffer, size_t *r_buflen);
gpg_error_t make_canon_sexp_pad (gcry_sexp_t sexp, int secure,
unsigned char **r_buffer, size_t *r_buflen);
gpg_error_t keygrip_from_canon_sexp (const unsigned char *key, size_t keylen,
unsigned char *grip);
int cmp_simple_canon_sexp (const unsigned char *a, const unsigned char *b);
int cmp_canon_sexp (const unsigned char *a, size_t alen,
const unsigned char *b, size_t blen,
int (*tcmp)(void *ctx, int depth,
const unsigned char *aval, size_t avallen,
const unsigned char *bval, size_t bvallen),
void *tcmpctx);
unsigned char *make_simple_sexp_from_hexstr (const char *line,
size_t *nscanned);
int hash_algo_from_sigval (const unsigned char *sigval);
unsigned char *make_canon_sexp_from_rsa_pk (const void *m, size_t mlen,
const void *e, size_t elen,
size_t *r_len);
gpg_error_t get_rsa_pk_from_canon_sexp (const unsigned char *keydata,
size_t keydatalen,
unsigned char const **r_n,
size_t *r_nlen,
unsigned char const **r_e,
size_t *r_elen);
gpg_error_t get_ecc_q_from_canon_sexp (const unsigned char *keydata,
size_t keydatalen,
unsigned char const **r_q,
size_t *r_qlen);
gpg_error_t uncompress_ecc_q_in_canon_sexp (const unsigned char *keydata,
size_t keydatalen,
unsigned char **r_newkeydata,
size_t *r_newkeydatalen);
int get_pk_algo_from_key (gcry_sexp_t key);
int get_pk_algo_from_canon_sexp (const unsigned char *keydata,
size_t keydatalen);
char *pubkey_algo_string (gcry_sexp_t s_pkey, enum gcry_pk_algos *r_algoid);
const char *pubkey_algo_to_string (int algo);
const char *hash_algo_to_string (int algo);
const char *cipher_mode_to_string (int mode);
const char *get_ecc_curve_from_key (gcry_sexp_t key);
/*-- convert.c --*/
int hex2bin (const char *string, void *buffer, size_t length);
int hexcolon2bin (const char *string, void *buffer, size_t length);
char *bin2hex (const void *buffer, size_t length, char *stringbuf);
char *bin2hexcolon (const void *buffer, size_t length, char *stringbuf);
const char *hex2str (const char *hexstring,
char *buffer, size_t bufsize, size_t *buflen);
char *hex2str_alloc (const char *hexstring, size_t *r_count);
unsigned int hex2fixedbuf (const char *hexstr, void *buffer, size_t bufsize);
/*-- percent.c --*/
char *percent_plus_escape (const char *string);
char *percent_data_escape (int plus, const char *prefix,
const void *data, size_t datalen);
char *percent_plus_unescape (const char *string, int nulrepl);
char *percent_unescape (const char *string, int nulrepl);
size_t percent_plus_unescape_inplace (char *string, int nulrepl);
size_t percent_unescape_inplace (char *string, int nulrepl);
/*-- openpgp-oid.c --*/
gpg_error_t openpgp_oid_from_str (const char *string, gcry_mpi_t *r_mpi);
char *openpgp_oidbuf_to_str (const unsigned char *buf, size_t len);
char *openpgp_oid_to_str (gcry_mpi_t a);
int openpgp_oidbuf_is_ed25519 (const void *buf, size_t len);
int openpgp_oid_is_ed25519 (gcry_mpi_t a);
int openpgp_oidbuf_is_cv25519 (const void *buf, size_t len);
int openpgp_oid_is_cv25519 (gcry_mpi_t a);
int openpgp_oid_is_cv448 (gcry_mpi_t a);
int openpgp_oid_is_ed448 (gcry_mpi_t a);
enum gcry_kem_algos openpgp_oid_to_kem_algo (const char *oidname);
const char *openpgp_curve_to_oid (const char *name,
unsigned int *r_nbits, int *r_algo);
const char *openpgp_oid_to_curve (const char *oid, int mode);
const char *openpgp_oid_or_name_to_curve (const char *oidname, int canon);
const char *openpgp_enum_curves (int *idxp);
const char *openpgp_is_curve_supported (const char *name,
int *r_algo, unsigned int *r_nbits);
const char *get_keyalgo_string (enum gcry_pk_algos algo,
unsigned int nbits, const char *curve);
/*-- homedir.c --*/
const char *standard_homedir (void);
void gnupg_set_homedir (const char *newdir);
void gnupg_maybe_make_homedir (const char *fname, int quiet);
const char *gnupg_homedir (void);
int gnupg_default_homedir_p (void);
const char *gnupg_registry_dir (void);
const char *gnupg_daemon_rootdir (void);
const char *gnupg_socketdir (void);
const char *gnupg_sysconfdir (void);
const char *gnupg_bindir (void);
const char *gnupg_libexecdir (void);
const char *gnupg_libdir (void);
const char *gnupg_datadir (void);
const char *gnupg_localedir (void);
const char *gpg_agent_socket_name (void);
const char *dirmngr_socket_name (void);
const char *keyboxd_socket_name (void);
char *_gnupg_socketdir_internal (int skip_checks, unsigned *r_info);
/* All module names. We also include gpg and gpgsm for the sake for
gpgconf. */
#define GNUPG_MODULE_NAME_AGENT 1
#define GNUPG_MODULE_NAME_PINENTRY 2
#define GNUPG_MODULE_NAME_SCDAEMON 3
#define GNUPG_MODULE_NAME_DIRMNGR 4
#define GNUPG_MODULE_NAME_PROTECT_TOOL 5
#define GNUPG_MODULE_NAME_CHECK_PATTERN 6
#define GNUPG_MODULE_NAME_GPGSM 7
#define GNUPG_MODULE_NAME_GPG 8
#define GNUPG_MODULE_NAME_CONNECT_AGENT 9
#define GNUPG_MODULE_NAME_GPGCONF 10
#define GNUPG_MODULE_NAME_DIRMNGR_LDAP 11
#define GNUPG_MODULE_NAME_GPGV 12
#define GNUPG_MODULE_NAME_KEYBOXD 13
#define GNUPG_MODULE_NAME_TPM2DAEMON 14
#define GNUPG_MODULE_NAME_CARD 15
#define GNUPG_MODULE_NAME_GPGTAR 16
const char *gnupg_module_name (int which);
void gnupg_module_name_flush_some (void);
void gnupg_set_builddir (const char *newdir);
/* A list of constants to identify protocols. This is used by tools
* which need to distinguish between the different protocols
* implemented by GnuPG. May be used as bit flags. */
#define GNUPG_PROTOCOL_OPENPGP 1 /* The one and only (gpg). */
#define GNUPG_PROTOCOL_CMS 2 /* The core of S/MIME (gpgsm) */
#define GNUPG_PROTOCOL_SSH_AGENT 4 /* Out ssh-agent implementation */
/*-- gpgrlhelp.c --*/
void gnupg_rl_initialize (void);
/*-- helpfile.c --*/
char *gnupg_get_help_string (const char *key, int only_current_locale);
/*-- localename.c --*/
const char *gnupg_messages_locale_name (void);
/*-- kem.c --*/
gpg_error_t gnupg_ecc_kem_kdf (void *kek, size_t kek_len,
int hashalgo, const void *ecdh, size_t ecdh_len,
const void *ecc_ct, size_t ecc_ct_len,
const void *ecc_pk, size_t ecc_pk_len);
gpg_error_t gnupg_kem_combiner (void *kek, size_t kek_len,
const void *ecc_ss, size_t ecc_ss_len,
const void *ecc_ct, size_t ecc_ct_len,
const void *mlkem_ss, size_t mlkem_ss_len,
const void *mlkem_ct, size_t mlkem_ct_len,
const void *fixedinfo, size_t fixedinfo_len);
/*-- miscellaneous.c --*/
/* This function is called at startup to tell libgcrypt to use our own
logging subsystem. */
void setup_libgcrypt_logging (void);
/* Print an out of core message and die. */
void xoutofcore (void);
-/* Wrapper aroung gpgrt_reallocarray. Uses the gpgrt alloc function
+/* Wrapper around gpgrt_reallocarray. Uses the gpgrt alloc function
* which redirects to the Libgcrypt versions via
* init_common_subsystems. Thus this can be used interchangeable with
* the other alloc functions. */
void *xreallocarray (void *a, size_t oldnmemb, size_t nmemb, size_t size);
/* Same as estream_asprintf but die on memory failure. */
char *xasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2);
/* This is now an alias to estream_asprintf. */
char *xtryasprintf (const char *fmt, ...) GPGRT_ATTR_PRINTF(1,2);
/* Replacement for gcry_cipher_algo_name. */
const char *gnupg_cipher_algo_name (int algo);
void obsolete_option (const char *configname, unsigned int configlineno,
const char *name);
const char *print_fname_stdout (const char *s);
const char *print_fname_stdin (const char *s);
void print_utf8_buffer3 (estream_t fp, const void *p, size_t n,
const char *delim);
void print_utf8_buffer2 (estream_t fp, const void *p, size_t n, int delim);
void print_utf8_buffer (estream_t fp, const void *p, size_t n);
void print_utf8_string (estream_t stream, const char *p);
void print_hexstring (FILE *fp, const void *buffer, size_t length,
int reserved);
char *try_make_printable_string (const void *p, size_t n, int delim);
char *make_printable_string (const void *p, size_t n, int delim);
char *decode_c_string (const char *src);
int match_multistr (const char *multistr,const char *match);
int gnupg_compare_version (const char *a, const char *b);
struct debug_flags_s
{
unsigned int flag;
const char *name;
};
int parse_debug_flag (const char *string, unsigned int *debugvar,
const struct debug_flags_s *flags);
struct compatibility_flags_s
{
unsigned int flag;
const char *name;
const char *desc;
};
int parse_compatibility_flags (const char *string, unsigned int *flagvar,
const struct compatibility_flags_s *flags);
gpg_error_t b64decode (const char *string, const char *title,
void **r_buffer, size_t *r_buflen);
/*-- Simple replacement functions. */
/* We use the gnupg_ttyname macro to be safe not to run into conflicts
with an existing but broken ttyname. */
#if !defined(HAVE_TTYNAME) || defined(HAVE_BROKEN_TTYNAME)
# define gnupg_ttyname(n) _gnupg_ttyname ((n))
/* Systems without ttyname (W32) will merely return NULL. */
static inline char *
_gnupg_ttyname (int fd)
{
(void)fd;
return NULL;
}
#else /*HAVE_TTYNAME*/
# define gnupg_ttyname(n) ttyname ((n))
#endif /*HAVE_TTYNAME */
#define gnupg_isatty(a) isatty ((a))
/*-- Macros to replace ctype ones to avoid locale problems. --*/
#define spacep(p) (*(p) == ' ' || *(p) == '\t')
#define digitp(p) (*(p) >= '0' && *(p) <= '9')
#define alphap(p) ((*(p) >= 'A' && *(p) <= 'Z') \
|| (*(p) >= 'a' && *(p) <= 'z'))
#define alnump(p) (alphap (p) || digitp (p))
#define hexdigitp(a) (digitp (a) \
|| (*(a) >= 'A' && *(a) <= 'F') \
|| (*(a) >= 'a' && *(a) <= 'f'))
/* Note this isn't identical to a C locale isspace() without \f and
\v, but works for the purposes used here. */
#define ascii_isspace(a) ((a)==' ' || (a)=='\n' || (a)=='\r' || (a)=='\t')
/* The atoi macros assume that the buffer has only valid digits. */
#define atoi_1(p) (*(p) - '0' )
#define atoi_2(p) ((atoi_1(p) * 10) + atoi_1((p)+1))
#define atoi_4(p) ((atoi_2(p) * 100) + atoi_2((p)+2))
#define xtoi_1(p) (*(p) <= '9'? (*(p)- '0'): \
*(p) <= 'F'? (*(p)-'A'+10):(*(p)-'a'+10))
#define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p)+1))
#define xtoi_4(p) ((xtoi_2(p) * 256) + xtoi_2((p)+2))
#endif /*GNUPG_COMMON_UTIL_H*/
diff --git a/configure.ac b/configure.ac
index 1f0142140..71c061481 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,2193 +1,2193 @@
# configure.ac - for GnuPG 2.1
# Copyright (C) 1998-2019 Werner Koch
# Copyright (C) 1998-2021 Free Software Foundation, Inc.
# Copyright (C) 2003-2023 g10 Code GmbH
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
# Process this file with autoconf to produce a configure script.
AC_PREREQ([2.69])
min_automake_version="1.16.3"
# To build a release you need to create a tag with the version number
# (git tag -s gnupg-2.n.m) and run "./autogen.sh --force". Please
# bump the version number immediately *after* the release and do
# another commit and push so that the git magic is able to work.
m4_define([mym4_package],[gnupg])
m4_define([mym4_major], [2])
m4_define([mym4_minor], [5])
m4_define([mym4_micro], [0])
# To start a new development series, i.e a new major or minor number
# you need to mark an arbitrary commit before the first beta release
# with an annotated tag. For example the 2.1 branch starts off with
# the tag "gnupg-2.1-base". This is used as the base for counting
# beta numbers before the first release of a series.
# Below is m4 magic to extract and compute the git revision number,
# the decimalized short revision number, a beta version string and a
# flag indicating a development version (mym4_isbeta). Note that the
# m4 processing is done by autoconf and not during the configure run.
m4_define([mym4_verslist], m4_split(m4_esyscmd([./autogen.sh --find-version] \
mym4_package mym4_major mym4_minor mym4_micro),[:]))
m4_define([mym4_isbeta], m4_argn(2, mym4_verslist))
m4_define([mym4_version], m4_argn(4, mym4_verslist))
m4_define([mym4_revision], m4_argn(7, mym4_verslist))
m4_define([mym4_revision_dec], m4_argn(8, mym4_verslist))
m4_esyscmd([echo ]mym4_version[>VERSION])
AC_INIT([mym4_package],[mym4_version],[https://bugs.gnupg.org])
# When changing the SWDB tag please also adjust the hard coded tags in
# build-aux/speedo.mk, build-aux/getswdb.sh, and Makefile.am
# As well as the source info for the man pages.
AC_DEFINE_UNQUOTED(GNUPG_SWDB_TAG, "gnupg26", [swdb tag for this branch])
NEED_GPGRT_VERSION=1.46
NEED_LIBGCRYPT_API=1
NEED_LIBGCRYPT_VERSION=1.11.0
NEED_LIBASSUAN_API=3
NEED_LIBASSUAN_VERSION=3.0.0
NEED_KSBA_API=1
NEED_KSBA_VERSION=1.6.3
NEED_NTBTLS_API=1
NEED_NTBTLS_VERSION=0.2.0
NEED_NPTH_API=1
NEED_NPTH_VERSION=1.2
NEED_GNUTLS_VERSION=3.2
NEED_SQLITE_VERSION=3.27
development_version=mym4_isbeta
PACKAGE=$PACKAGE_NAME
PACKAGE_GT=${PACKAGE_NAME}2
VERSION=$PACKAGE_VERSION
AC_CONFIG_AUX_DIR([build-aux])
AC_CONFIG_SRCDIR([sm/gpgsm.c])
AC_CONFIG_HEADERS([config.h])
AM_INIT_AUTOMAKE([serial-tests dist-bzip2 no-dist-gzip])
AC_CANONICAL_HOST
AB_INIT
AC_USE_SYSTEM_EXTENSIONS
# Some status variables.
have_gpg_error=no
have_libgcrypt=no
have_libassuan=no
have_ksba=no
have_ntbtls=no
have_gnutls=no
have_sqlite=no
have_npth=no
have_libusb=no
have_libtss=no
have_system_resolver=no
gnupg_have_ldap="n/a"
use_zip=yes
use_bzip2=yes
use_exec=yes
use_trust_models=yes
use_tofu=yes
use_libdns=yes
card_support=yes
use_ccid_driver=auto
dirmngr_auto_start=yes
use_tls_library=no
large_secmem=no
show_tor_support=no
# gpg is a required part and can't be disabled anymore.
build_gpg=yes
GNUPG_BUILD_PROGRAM(gpgsm, yes)
# The agent is a required part and can't be disabled anymore.
build_agent=yes
GNUPG_BUILD_PROGRAM(scdaemon, yes)
GNUPG_BUILD_PROGRAM(g13, no)
GNUPG_BUILD_PROGRAM(dirmngr, yes)
GNUPG_BUILD_PROGRAM(keyboxd, yes)
GNUPG_BUILD_PROGRAM(tpm2d, yes)
GNUPG_BUILD_PROGRAM(doc, yes)
# We use gpgtar to unpack test data, hence we always build it. If the
# user opts out, we simply don't install it.
GNUPG_BUILD_PROGRAM(gpgtar, yes)
# We also install the gpg-wks-server tool by default but disable it
# later for platforms where it can't be build.
GNUPG_BUILD_PROGRAM(wks-tools, yes)
AC_SUBST(PACKAGE)
AC_SUBST(PACKAGE_GT)
AC_SUBST(VERSION)
AC_DEFINE_UNQUOTED(PACKAGE, "$PACKAGE", [Name of this package])
AC_DEFINE_UNQUOTED(PACKAGE_GT, "$PACKAGE_GT",
[Name of this package for gettext])
AC_DEFINE_UNQUOTED(VERSION, "$VERSION", [Version of this package])
AC_DEFINE_UNQUOTED(PACKAGE_BUGREPORT, "$PACKAGE_BUGREPORT",
[Bug report address])
AC_DEFINE_UNQUOTED(NEED_LIBGCRYPT_VERSION, "$NEED_LIBGCRYPT_VERSION",
[Required version of Libgcrypt])
AC_DEFINE_UNQUOTED(NEED_KSBA_VERSION, "$NEED_KSBA_VERSION",
[Required version of Libksba])
AC_DEFINE_UNQUOTED(NEED_NTBTLS_VERSION, "$NEED_NTBTLS_VERSION",
[Required version of NTBTLS])
# The default is to use the modules from this package and the few
# other packages in a standard place; i.e where this package gets
# installed. With these options it is possible to override these
# ${prefix} depended values with fixed paths, which can't be replaced
# at make time. See also am/cmacros.am and the defaults in AH_BOTTOM.
AC_ARG_WITH(agent-pgm,
[ --with-agent-pgm=PATH Use PATH as the default for the agent)],
GNUPG_AGENT_PGM="$withval", GNUPG_AGENT_PGM="" )
AC_SUBST(GNUPG_AGENT_PGM)
AM_CONDITIONAL(GNUPG_AGENT_PGM, test -n "$GNUPG_AGENT_PGM")
show_gnupg_agent_pgm="(default)"
test -n "$GNUPG_AGENT_PGM" && show_gnupg_agent_pgm="$GNUPG_AGENT_PGM"
AC_ARG_WITH(pinentry-pgm,
[ --with-pinentry-pgm=PATH Use PATH as the default for the pinentry)],
GNUPG_PINENTRY_PGM="$withval", GNUPG_PINENTRY_PGM="" )
AC_SUBST(GNUPG_PINENTRY_PGM)
AM_CONDITIONAL(GNUPG_PINENTRY_PGM, test -n "$GNUPG_PINENTRY_PGM")
show_gnupg_pinentry_pgm="(default)"
test -n "$GNUPG_PINENTRY_PGM" && show_gnupg_pinentry_pgm="$GNUPG_PINENTRY_PGM"
AC_ARG_WITH(scdaemon-pgm,
[ --with-scdaemon-pgm=PATH Use PATH as the default for the scdaemon)],
GNUPG_SCDAEMON_PGM="$withval", GNUPG_SCDAEMON_PGM="" )
AC_SUBST(GNUPG_SCDAEMON_PGM)
AM_CONDITIONAL(GNUPG_SCDAEMON_PGM, test -n "$GNUPG_SCDAEMON_PGM")
show_gnupg_scdaemon_pgm="(default)"
test -n "$GNUPG_SCDAEMON_PGM" && show_gnupg_scdaemon_pgm="$GNUPG_SCDAEMON_PGM"
AC_ARG_WITH(tpm2daemon-pgm,
[ --with-tpm2daemon-pgm=PATH Use PATH as the default for the tpm2daemon)],
GNUPG_TPM2DAEMON_PGM="$withval", GNUPG_TPM2DAEMON_PGM="" )
AC_SUBST(GNUPG_TPM2DAEMON_PGM)
AM_CONDITIONAL(GNUPG_TPM2DAEMON_PGM, test -n "$GNUPG_TPM2DAEMON_PGM")
show_gnupg_tpm2daemon_pgm="(default)"
test -n "$GNUPG_TPM2DAEMON_PGM" && show_gnupg_tpm2daemon_pgm="$GNUPG_TPM2DAEMON_PGM"
AC_ARG_WITH(dirmngr-pgm,
[ --with-dirmngr-pgm=PATH Use PATH as the default for the dirmngr)],
GNUPG_DIRMNGR_PGM="$withval", GNUPG_DIRMNGR_PGM="" )
AC_SUBST(GNUPG_DIRMNGR_PGM)
AM_CONDITIONAL(GNUPG_DIRMNGR_PGM, test -n "$GNUPG_DIRMNGR_PGM")
show_gnupg_dirmngr_pgm="(default)"
test -n "$GNUPG_DIRMNGR_PGM" && show_gnupg_dirmngr_pgm="$GNUPG_DIRMNGR_PGM"
AC_ARG_WITH(keyboxd-pgm,
[ --with-keyboxd-pgm=PATH Use PATH as the default for the keyboxd)],
GNUPG_KEYBOXD_PGM="$withval", GNUPG_KEYBOXD_PGM="" )
AC_SUBST(GNUPG_KEYBOXD_PGM)
AM_CONDITIONAL(GNUPG_KEYBOXD_PGM, test -n "$GNUPG_KEYBOXD_PGM")
show_gnupg_keyboxd_pgm="(default)"
test -n "$GNUPG_KEYBOXD_PGM" && show_gnupg_keyboxd_pgm="$GNUPG_KEYBOXD_PGM"
AC_ARG_WITH(protect-tool-pgm,
[ --with-protect-tool-pgm=PATH Use PATH as the default for the protect-tool)],
GNUPG_PROTECT_TOOL_PGM="$withval", GNUPG_PROTECT_TOOL_PGM="" )
AC_SUBST(GNUPG_PROTECT_TOOL_PGM)
AM_CONDITIONAL(GNUPG_PROTECT_TOOL_PGM, test -n "$GNUPG_PROTECT_TOOL_PGM")
show_gnupg_protect_tool_pgm="(default)"
test -n "$GNUPG_PROTECT_TOOL_PGM" \
&& show_gnupg_protect_tool_pgm="$GNUPG_PROTECT_TOOL_PGM"
AC_ARG_WITH(dirmngr-ldap-pgm,
[ --with-dirmngr-ldap-pgm=PATH Use PATH as the default for the dirmngr ldap wrapper)],
GNUPG_DIRMNGR_LDAP_PGM="$withval", GNUPG_DIRMNGR_LDAP_PGM="" )
AC_SUBST(GNUPG_DIRMNGR_LDAP_PGM)
AM_CONDITIONAL(GNUPG_DIRMNGR_LDAP_PGM, test -n "$GNUPG_DIRMNGR_LDAP_PGM")
show_gnupg_dirmngr_ldap_pgm="(default)"
test -n "$GNUPG_DIRMNGR_LDAP_PGM" \
&& show_gnupg_dirmngr_ldap_pgm="$GNUPG_DIRMNGR_LDAP_PGM"
#
# For a long time gpg 2.x was installed as gpg2. This changed with
# 2.2. This option can be used to install gpg under the name gpg2.
#
AC_ARG_ENABLE(gpg-is-gpg2,
AS_HELP_STRING([--enable-gpg-is-gpg2],[Set installed name of gpg to gpg2]),
gpg_is_gpg2=$enableval)
if test "$gpg_is_gpg2" = "yes"; then
AC_DEFINE(USE_GPG2_HACK, 1, [Define to install gpg as gpg2])
fi
AM_CONDITIONAL(USE_GPG2_HACK, test "$gpg_is_gpg2" = "yes")
# SELinux support includes tracking of sensitive files to avoid
# leaking their contents through processing these files by gpg itself
AC_MSG_CHECKING([whether SELinux support is requested])
AC_ARG_ENABLE(selinux-support,
AS_HELP_STRING([--enable-selinux-support],
[enable SELinux support]),
selinux_support=$enableval, selinux_support=no)
AC_MSG_RESULT($selinux_support)
AC_MSG_CHECKING([whether to allocate extra secure memory])
AC_ARG_ENABLE(large-secmem,
AS_HELP_STRING([--enable-large-secmem],
[allocate extra secure memory]),
large_secmem=$enableval, large_secmem=no)
AC_MSG_RESULT($large_secmem)
if test "$large_secmem" = yes ; then
SECMEM_BUFFER_SIZE=65536
else
SECMEM_BUFFER_SIZE=32768
fi
AC_DEFINE_UNQUOTED(SECMEM_BUFFER_SIZE,$SECMEM_BUFFER_SIZE,
[Size of secure memory buffer])
AC_MSG_CHECKING([calibrated passphrase-stretching (s2k) duration])
AC_ARG_WITH(agent-s2k-calibration,
AS_HELP_STRING([--with-agent-s2k-calibration=MSEC],
[calibrate passphrase stretching (s2k) to MSEC milliseconds]),
agent_s2k_calibration=$withval, agent_s2k_calibration=100)
AC_MSG_RESULT($agent_s2k_calibration milliseconds)
AC_DEFINE_UNQUOTED(AGENT_S2K_CALIBRATION, $agent_s2k_calibration,
[Agent s2k calibration time (ms)])
AC_MSG_CHECKING([whether to enable trust models])
AC_ARG_ENABLE(trust-models,
AS_HELP_STRING([--disable-trust-models],
[disable all trust models except "always"]),
use_trust_models=$enableval)
AC_MSG_RESULT($use_trust_models)
if test "$use_trust_models" = no ; then
AC_DEFINE(NO_TRUST_MODELS, 1,
[Define to include only trust-model always])
fi
AC_MSG_CHECKING([whether to enable TOFU])
AC_ARG_ENABLE(tofu,
AS_HELP_STRING([--disable-tofu],
[disable the TOFU trust model]),
use_tofu=$enableval, use_tofu=$use_trust_models)
AC_MSG_RESULT($use_tofu)
if test "$use_trust_models" = no && test "$use_tofu" = yes; then
AC_MSG_ERROR([both --disable-trust-models and --enable-tofu given])
fi
AC_MSG_CHECKING([whether to enable libdns])
AC_ARG_ENABLE(libdns,
AS_HELP_STRING([--disable-libdns],
[do not build with libdns support]),
use_libdns=$enableval, use_libdns=yes)
AC_MSG_RESULT($use_libdns)
if test x"$use_libdns" = xyes ; then
AC_DEFINE(USE_LIBDNS, 1, [Build with integrated libdns support])
fi
AM_CONDITIONAL(USE_LIBDNS, test "$use_libdns" = yes)
#
# Options to disable algorithm
#
GNUPG_GPG_DISABLE_ALGO([rsa],[RSA public key])
# Elgamal is a MUST algorithm
# DSA is a MUST algorithm
GNUPG_GPG_DISABLE_ALGO([ecdh],[ECDH public key])
GNUPG_GPG_DISABLE_ALGO([ecdsa],[ECDSA public key])
GNUPG_GPG_DISABLE_ALGO([eddsa],[EdDSA public key])
GNUPG_GPG_DISABLE_ALGO([idea],[IDEA cipher])
# 3DES is a MUST algorithm
GNUPG_GPG_DISABLE_ALGO([cast5],[CAST5 cipher])
GNUPG_GPG_DISABLE_ALGO([blowfish],[BLOWFISH cipher])
GNUPG_GPG_DISABLE_ALGO([aes128],[AES128 cipher])
GNUPG_GPG_DISABLE_ALGO([aes192],[AES192 cipher])
GNUPG_GPG_DISABLE_ALGO([aes256],[AES256 cipher])
GNUPG_GPG_DISABLE_ALGO([twofish],[TWOFISH cipher])
GNUPG_GPG_DISABLE_ALGO([camellia128],[CAMELLIA128 cipher])
GNUPG_GPG_DISABLE_ALGO([camellia192],[CAMELLIA192 cipher])
GNUPG_GPG_DISABLE_ALGO([camellia256],[CAMELLIA256 cipher])
GNUPG_GPG_DISABLE_ALGO([md5],[MD5 hash])
# SHA1 is a MUST algorithm
GNUPG_GPG_DISABLE_ALGO([rmd160],[RIPE-MD160 hash])
GNUPG_GPG_DISABLE_ALGO([sha224],[SHA-224 hash])
# SHA256 is a MUST algorithm for GnuPG.
GNUPG_GPG_DISABLE_ALGO([sha384],[SHA-384 hash])
GNUPG_GPG_DISABLE_ALGO([sha512],[SHA-512 hash])
# Allow disabling of zip support.
# This is in general not a good idea because according to rfc4880 OpenPGP
# implementations SHOULD support ZLIB.
AC_MSG_CHECKING([whether to enable the ZIP and ZLIB compression algorithm])
AC_ARG_ENABLE(zip,
AS_HELP_STRING([--disable-zip],
[disable the ZIP and ZLIB compression algorithm]),
use_zip=$enableval)
AC_MSG_RESULT($use_zip)
# Allow disabling of bzib2 support.
# It is defined only after we confirm the library is available later
AC_MSG_CHECKING([whether to enable the BZIP2 compression algorithm])
AC_ARG_ENABLE(bzip2,
AS_HELP_STRING([--disable-bzip2],[disable the BZIP2 compression algorithm]),
use_bzip2=$enableval)
AC_MSG_RESULT($use_bzip2)
# Configure option to allow or disallow execution of external
# programs, like a photo viewer.
AC_MSG_CHECKING([whether to enable external program execution])
AC_ARG_ENABLE(exec,
AS_HELP_STRING([--disable-exec],[disable all external program execution]),
use_exec=$enableval)
AC_MSG_RESULT($use_exec)
if test "$use_exec" = no ; then
AC_DEFINE(NO_EXEC,1,[Define to disable all external program execution])
fi
if test "$use_exec" = yes ; then
AC_MSG_CHECKING([whether to enable photo ID viewing])
AC_ARG_ENABLE(photo-viewers,
[ --disable-photo-viewers disable photo ID viewers],
[if test "$enableval" = no ; then
AC_DEFINE(DISABLE_PHOTO_VIEWER,1,[define to disable photo viewing])
fi],enableval=yes)
gnupg_cv_enable_photo_viewers=$enableval
AC_MSG_RESULT($enableval)
if test "$gnupg_cv_enable_photo_viewers" = yes ; then
AC_MSG_CHECKING([whether to use a fixed photo ID viewer])
AC_ARG_WITH(photo-viewer,
[ --with-photo-viewer=FIXED_VIEWER set a fixed photo ID viewer],
[if test "$withval" = yes ; then
withval=no
elif test "$withval" != no ; then
AC_DEFINE_UNQUOTED(FIXED_PHOTO_VIEWER,"$withval",
[if set, restrict photo-viewer to this])
fi],withval=no)
AC_MSG_RESULT($withval)
fi
fi
#
# Check for the key/uid cache size. This can't be zero, but can be
# pretty small on embedded systems. This is used for the gpg part.
#
AC_MSG_CHECKING([for the size of the key and uid cache])
AC_ARG_ENABLE(key-cache,
AS_HELP_STRING([--enable-key-cache=SIZE],
[Set key cache to SIZE (default 4096)]),,enableval=4096)
if test "$enableval" = "no"; then
enableval=5
elif test "$enableval" = "yes" || test "$enableval" = ""; then
enableval=4096
fi
changequote(,)dnl
key_cache_size=`echo "$enableval" | sed 's/[A-Za-z]//g'`
changequote([,])dnl
if test "$enableval" != "$key_cache_size" || test "$key_cache_size" -lt 5; then
AC_MSG_ERROR([invalid key-cache size])
fi
AC_MSG_RESULT($key_cache_size)
AC_DEFINE_UNQUOTED(PK_UID_CACHE_SIZE,$key_cache_size,
[Size of the key and UID caches])
#
# Check whether we want to use Linux capabilities
#
AC_MSG_CHECKING([whether use of capabilities is requested])
AC_ARG_WITH(capabilities,
[ --with-capabilities use linux capabilities [default=no]],
[use_capabilities="$withval"],[use_capabilities=no])
AC_MSG_RESULT($use_capabilities)
#
# Check whether to disable the card support
AC_MSG_CHECKING([whether smartcard support is requested])
AC_ARG_ENABLE(card-support,
AS_HELP_STRING([--disable-card-support],
[disable smartcard support]),
card_support=$enableval)
AC_MSG_RESULT($card_support)
if test "$card_support" = yes ; then
AC_DEFINE(ENABLE_CARD_SUPPORT,1,[Define to include smartcard support])
else
build_scdaemon=no
fi
#
# Allow disabling of internal CCID support.
# It is defined only after we confirm the library is available later
#
AC_MSG_CHECKING([whether to enable the internal CCID driver])
AC_ARG_ENABLE(ccid-driver,
AS_HELP_STRING([--disable-ccid-driver],
[disable the internal CCID driver]),
use_ccid_driver=$enableval)
AC_MSG_RESULT($use_ccid_driver)
AC_MSG_CHECKING([whether to auto start dirmngr])
AC_ARG_ENABLE(dirmngr-auto-start,
AS_HELP_STRING([--disable-dirmngr-auto-start],
[disable auto starting of the dirmngr]),
dirmngr_auto_start=$enableval)
AC_MSG_RESULT($dirmngr_auto_start)
if test "$dirmngr_auto_start" = yes ; then
AC_DEFINE(USE_DIRMNGR_AUTO_START,1,
[Define to enable auto starting of the dirmngr])
fi
#
# To avoid double inclusion of config.h which might happen at some
# places, we add the usual double inclusion protection at the top of
# config.h.
#
AH_TOP([
#ifndef GNUPG_CONFIG_H_INCLUDED
#define GNUPG_CONFIG_H_INCLUDED
])
#
# Stuff which goes at the bottom of config.h.
#
AH_BOTTOM([
/* This is the major version number of GnuPG so that
source included files can test for this. Note, that
we use 2 here even for GnuPG 1.9.x. */
#define GNUPG_MAJOR_VERSION 2
/* Now to separate file name parts.
Please note that the string version must not contain more
than one character because the code assumes strlen()==1 */
#ifdef HAVE_DOSISH_SYSTEM
#define DIRSEP_C '\\'
#define DIRSEP_S "\\"
#define EXTSEP_C '.'
#define EXTSEP_S "."
#define PATHSEP_C ';'
#define PATHSEP_S ";"
#define EXEEXT_S ".exe"
#else
#define DIRSEP_C '/'
#define DIRSEP_S "/"
#define EXTSEP_C '.'
#define EXTSEP_S "."
#define PATHSEP_C ':'
#define PATHSEP_S ":"
#define EXEEXT_S ""
#endif
/* Some global constants.
* Note that the homedir must not end in a slash. */
#ifdef HAVE_DOSISH_SYSTEM
# ifdef HAVE_DRIVE_LETTERS
# define GNUPG_DEFAULT_HOMEDIR "c:/gnupg"
# else
# define GNUPG_DEFAULT_HOMEDIR "/gnupg"
# endif
#elif defined(__VMS)
#define GNUPG_DEFAULT_HOMEDIR "/SYS$LOGIN/gnupg"
#else
#define GNUPG_DEFAULT_HOMEDIR "~/.gnupg"
#endif
#define GNUPG_PRIVATE_KEYS_DIR "private-keys-v1.d"
#define GNUPG_PUBLIC_KEYS_DIR "public-keys.d"
#define GNUPG_OPENPGP_REVOC_DIR "openpgp-revocs.d"
#define GNUPG_CACHE_DIR "cache.d"
#define GNUPG_DEF_COPYRIGHT_LINE "Copyright (C) 2024 g10 Code GmbH"
/* For some systems (DOS currently), we hardcode the path here. For
POSIX systems the values are constructed by the Makefiles, so that
the values may be overridden by the make invocations; this is to
comply with the GNU coding standards. Note that these values are
only defaults. */
#ifdef HAVE_DOSISH_SYSTEM
# ifdef HAVE_DRIVE_LETTERS
# define GNUPG_BINDIR "c:\\gnupg"
# define GNUPG_LIBEXECDIR "c:\\gnupg"
# define GNUPG_LIBDIR "c:\\gnupg"
# define GNUPG_DATADIR "c:\\gnupg"
# define GNUPG_SYSCONFDIR "c:\\gnupg"
# else
# define GNUPG_BINDIR "\\gnupg"
# define GNUPG_LIBEXECDIR "\\gnupg"
# define GNUPG_LIBDIR "\\gnupg"
# define GNUPG_DATADIR "\\gnupg"
# define GNUPG_SYSCONFDIR "\\gnupg"
# endif
#endif
/* Derive some other constants. */
#if !(defined(HAVE_FORK) && defined(HAVE_PIPE) && defined(HAVE_WAITPID))
#define EXEC_TEMPFILE_ONLY
#endif
/* We didn't define endianness above, so get it from OS macros. This
is intended for making fat binary builds on OS X. */
#if !defined(BIG_ENDIAN_HOST) && !defined(LITTLE_ENDIAN_HOST)
#if defined(__BIG_ENDIAN__)
#define BIG_ENDIAN_HOST 1
#elif defined(__LITTLE_ENDIAN__)
#define LITTLE_ENDIAN_HOST 1
#else
#error "No endianness found"
#endif
#endif
/* Hack used for W32: ldap.m4 also tests for the ASCII version of
ldap_start_tls_s because that is the actual symbol used in the
library. winldap.h redefines it to our commonly used value,
thus we define our usual macro here. */
#ifdef HAVE_LDAP_START_TLS_SA
# ifndef HAVE_LDAP_START_TLS_S
# define HAVE_LDAP_START_TLS_S 1
# endif
#endif
/* Enable the es_ macros from gpgrt. */
#define GPGRT_ENABLE_ES_MACROS 1
/* Enable the log_ macros from gpgrt. */
#define GPGRT_ENABLE_LOG_MACROS 1
/* We want the argparse macros from gpgrt. */
#define GPGRT_ENABLE_ARGPARSE_MACROS 1
/* Tell libgcrypt not to use its own libgpg-error implementation. */
#define USE_LIBGPG_ERROR 1
/* Tell Libgcrypt not to include deprecated definitions. */
#define GCRYPT_NO_DEPRECATED 1
/* Our HTTP code is used in estream mode. */
#define HTTP_USE_ESTREAM 1
/* Under W32 we do an explicit socket initialization, thus we need to
avoid the on-demand initialization which would also install an atexit
handler. */
#define HTTP_NO_WSASTARTUP
/* Under Windows we use the gettext code from libgpg-error. */
#define GPG_ERR_ENABLE_GETTEXT_MACROS
/* Under WindowsCE we use the strerror replacement from libgpg-error. */
#define GPG_ERR_ENABLE_ERRNO_MACROS
#endif /*GNUPG_CONFIG_H_INCLUDED*/
])
AM_MAINTAINER_MODE
AC_ARG_VAR(SYSROOT,[locate config scripts also below that directory])
# Checks for programs.
AC_MSG_NOTICE([checking for programs])
AC_PROG_MAKE_SET
AM_SANITY_CHECK
missing_dir=`cd $ac_aux_dir && pwd`
AM_MISSING_PROG(ACLOCAL, aclocal, $missing_dir)
AM_MISSING_PROG(AUTOCONF, autoconf, $missing_dir)
AM_MISSING_PROG(AUTOMAKE, automake, $missing_dir)
AM_MISSING_PROG(AUTOHEADER, autoheader, $missing_dir)
AM_MISSING_PROG(MAKEINFO, makeinfo, $missing_dir)
AM_SILENT_RULES
AC_PROG_AWK
AC_PROG_CC
AC_PROG_CPP
AM_PROG_CC_C_O
if test "x$ac_cv_prog_cc_c89" = "xno" ; then
AC_MSG_ERROR([[No C-89 compiler found]])
fi
AC_PROG_INSTALL
AC_PROG_LN_S
AC_PROG_RANLIB
AC_CHECK_TOOL(AR, ar, :)
AC_PATH_PROG(PERL,"perl")
AC_CHECK_TOOL(WINDRES, windres, :)
AC_PATH_PROG(YAT2M, "yat2m", "./yat2m" )
AC_ARG_VAR(YAT2M, [tool to convert texi to man pages])
AM_CONDITIONAL(HAVE_YAT2M, test -n "$ac_cv_path_YAT2M")
AC_SEARCH_LIBS([strerror],[cposix])
AC_SYS_LARGEFILE
# GNU AWK requires -n option to interpret "0xHH" as a number
if $AWK 'BEGIN { if (PROCINFO@<:@"version"@:>@) exit 1 }'; then
AWK_HEX_NUMBER_OPTION=''
AC_MSG_NOTICE([awk with no option for hexadecimal])
else
AWK_HEX_NUMBER_OPTION='-n'
AC_MSG_NOTICE([awk with an option -n for hexadecimal])
fi
AC_SUBST(AWK_HEX_NUMBER_OPTION)
# We need to compile and run a program on the build machine. A
# comment in libgpg-error says that the AC_PROG_CC_FOR_BUILD macro in
# the AC archive is broken for autoconf 2.57. Given that there is no
# newer version of that macro, we assume that it is also broken for
# autoconf 2.61 and thus we use a simple but usually sufficient
# approach.
AC_MSG_CHECKING(for cc for build)
if test "$cross_compiling" = "yes"; then
CC_FOR_BUILD="${CC_FOR_BUILD-cc}"
else
CC_FOR_BUILD="${CC_FOR_BUILD-$CC}"
fi
AC_MSG_RESULT($CC_FOR_BUILD)
AC_ARG_VAR(CC_FOR_BUILD,[build system C compiler])
# We need to call this macro because other pkg-config macros are
# not always used.
PKG_PROG_PKG_CONFIG
try_gettext=yes
require_iconv=yes
have_dosish_system=no
have_w32_system=no
have_android_system=no
use_simple_gettext=no
mmap_needed=yes
require_pipe_to_unblock_pselect=yes
case "${host}" in
*-mingw32*)
# special stuff for Windoze NT
ac_cv_have_dev_random=no
AC_DEFINE(USE_ONLY_8DOT3,1,
[Set this to limit filenames to the 8.3 format])
AC_DEFINE(USE_SIMPLE_GETTEXT,1,
[Because the Unix gettext has too much overhead on
MingW32 systems and these systems lack Posix functions,
we use a simplified version of gettext])
have_dosish_system=yes
have_w32_system=yes
require_iconv=no
require_pipe_to_unblock_pselect=no
AC_DEFINE(HAVE_DRIVE_LETTERS,1, [Defined if the OS supports drive letters.])
try_gettext="no"
use_simple_gettext=yes
mmap_needed=no
build_wks_tools=no
;;
i?86-emx-os2 | i?86-*-os2*emx )
# OS/2 with the EMX environment
ac_cv_have_dev_random=no
AC_DEFINE(HAVE_DRIVE_LETTERS)
have_dosish_system=yes
try_gettext="no"
build_wks_tools=no
;;
i?86-*-msdosdjgpp*)
# DOS with the DJGPP environment
ac_cv_have_dev_random=no
AC_DEFINE(HAVE_DRIVE_LETTERS)
have_dosish_system=yes
try_gettext="no"
build_wks_tools=no
;;
*-*-hpux*)
if test -z "$GCC" ; then
CFLAGS="-Ae -D_HPUX_SOURCE $CFLAGS"
fi
;;
*-dec-osf4*)
if test -z "$GCC" ; then
# Suppress all warnings
# to get rid of the unsigned/signed char mismatch warnings.
CFLAGS="-w $CFLAGS"
fi
;;
*-dec-osf5*)
if test -z "$GCC" ; then
# Use the newer compiler `-msg_disable ptrmismatch1' to
# get rid of the unsigned/signed char mismatch warnings.
# Using this may hide other pointer mismatch warnings, but
# it at least lets other warning classes through
CFLAGS="-msg_disable ptrmismatch1 $CFLAGS"
fi
;;
m68k-atari-mint)
;;
*-linux-android*)
have_android_system=yes
# Android is fully utf-8 and we do not want to use iconv to
# keeps things simple
require_iconv=no
build_wks_tools=no
;;
*-apple-darwin*)
AC_DEFINE(_DARWIN_C_SOURCE, 1,
Expose all libc features (__DARWIN_C_FULL).)
;;
*-*-netbsd*)
require_pipe_to_unblock_pselect=yes
;;
*)
;;
esac
if test "$require_pipe_to_unblock_pselect" = yes; then
AC_DEFINE(HAVE_PSELECT_NO_EINTR, 1,
[Defined if we run on systems like NetBSD, where
pselect cannot be unblocked by signal from a thread
within the same process. We use pipe in this case, instead.])
fi
if test "$have_dosish_system" = yes; then
AC_DEFINE(HAVE_DOSISH_SYSTEM,1,
[Defined if we run on some of the PCDOS like systems
(DOS, Windoze. OS/2) with special properties like
no file modes, case insensitive file names and preferred
use of backslashes as directory name separators.])
fi
AM_CONDITIONAL(HAVE_DOSISH_SYSTEM, test "$have_dosish_system" = yes)
AM_CONDITIONAL(USE_SIMPLE_GETTEXT, test x"$use_simple_gettext" = xyes)
if test "$have_w32_system" = yes; then
AC_DEFINE(HAVE_W32_SYSTEM,1, [Defined if we run on a W32 API based system])
AC_CHECK_HEADERS([winsock2.h])
fi
AM_CONDITIONAL(HAVE_W32_SYSTEM, test "$have_w32_system" = yes)
if test "$have_android_system" = yes; then
AC_DEFINE(HAVE_ANDROID_SYSTEM,1, [Defined if we build for an Android system])
fi
AM_CONDITIONAL(HAVE_ANDROID_SYSTEM, test "$have_android_system" = yes)
# (These need to go after AC_PROG_CC so that $EXEEXT is defined)
AC_DEFINE_UNQUOTED(EXEEXT,"$EXEEXT",[The executable file extension, if any])
#
# Checks for libraries.
#
AC_MSG_NOTICE([checking for libraries])
#
# gpgrt (aka libgpg-error) is a library with error codes shared
# between GnuPG related projects.
#
AM_PATH_GPG_ERROR("$NEED_GPGRT_VERSION",
have_gpg_error=yes,have_gpg_error=no)
#
# Libgcrypt is our generic crypto library
#
AM_PATH_LIBGCRYPT("$NEED_LIBGCRYPT_API:$NEED_LIBGCRYPT_VERSION",
have_libgcrypt=yes,have_libgcrypt=no)
# And, then, check if it's newer than 1.9.0 so that we can
# conditionally build some programs.
# Note: This is not anymore needed but keep the code commented in case
# we need it again with some future libgcrypt.
#have_libgcrypt_newer=no
#if test $ok = yes; then
# if test "$major" -gt 1; then
# have_libgcrypt_newer=yes
# else
# if test "$major" -eq 1; then
# if test "$minor" -gt 9; then
# have_libgcrypt_newer=yes
# else
# if test "$minor" -eq 9; then
# if test "$micro" -ge 0; then
# have_libgcrypt_newer=yes
# fi
# fi
# fi
# fi
# fiy
#fi
#AM_CONDITIONAL(HAVE_NEWER_LIBGCRYPT, [test $have_libgcrypt_newer = yes])
#
# libassuan is used for IPC
#
AM_PATH_LIBASSUAN("$NEED_LIBASSUAN_API:$NEED_LIBASSUAN_VERSION",
have_libassuan=yes,have_libassuan=no)
if test "$have_libassuan" = "yes"; then
AC_DEFINE_UNQUOTED(GNUPG_LIBASSUAN_VERSION, "$libassuan_version",
[version of the libassuan library])
show_tor_support="only .onion"
fi
#
# libksba is our X.509 support library
#
AM_PATH_KSBA("$NEED_KSBA_API:$NEED_KSBA_VERSION",have_ksba=yes,have_ksba=no)
#
# libusb allows us to use the integrated CCID smartcard reader driver.
#
# FiXME: Use GNUPG_CHECK_LIBUSB and modify to use separate AC_SUBSTs.
if test "$use_ccid_driver" = auto || test "$use_ccid_driver" = yes; then
case "${host}" in
*-mingw32*)
LIBUSB_NAME=
LIBUSB_LIBS=
LIBUSB_CPPFLAGS=
;;
*-*-darwin*)
LIBUSB_NAME=usb-1.0
LIBUSB_LIBS="-Wl,-framework,CoreFoundation -Wl,-framework,IOKit"
;;
*-*-freebsd*)
# FreeBSD has a native 1.0 compatible library by -lusb.
LIBUSB_NAME=usb
LIBUSB_LIBS=
;;
*)
LIBUSB_NAME=usb-1.0
LIBUSB_LIBS=
;;
esac
fi
if test x"$LIBUSB_NAME" != x ; then
AC_CHECK_LIB($LIBUSB_NAME, libusb_init,
[ LIBUSB_LIBS="-l$LIBUSB_NAME $LIBUSB_LIBS"
have_libusb=yes ])
AC_MSG_CHECKING([libusb include dir])
usb_incdir_found="no"
for _incdir in "" "/usr/include/libusb-1.0" \
"/usr/local/include/libusb-1.0" "/usr/pkg/include/libusb-1.0"; do
_libusb_save_cppflags=$CPPFLAGS
if test -n "${_incdir}"; then
CPPFLAGS="-I${_incdir} ${CPPFLAGS}"
fi
AC_PREPROC_IFELSE([AC_LANG_SOURCE([[@%:@include <libusb.h>]])],
[usb_incdir=${_incdir}; usb_incdir_found="yes"], [])
CPPFLAGS=${_libusb_save_cppflags}
if test "$usb_incdir_found" = "yes"; then
break
fi
done
if test "$usb_incdir_found" = "yes"; then
AC_MSG_RESULT([${usb_incdir}])
else
AC_MSG_RESULT([not found])
usb_incdir=""
have_libusb=no
if test "$use_ccid_driver" != yes; then
use_ccid_driver=no
fi
LIBUSB_LIBS=""
fi
if test "$have_libusb" = yes; then
AC_DEFINE(HAVE_LIBUSB,1, [defined if libusb is available])
fi
if test x"$usb_incdir" = x; then
LIBUSB_CPPFLAGS=""
else
LIBUSB_CPPFLAGS="-I${usb_incdir}"
fi
fi
AC_SUBST(LIBUSB_LIBS)
AC_SUBST(LIBUSB_CPPFLAGS)
#
# Check whether it is necessary to link against libdl.
# (For example to load libpcsclite)
#
gnupg_dlopen_save_libs="$LIBS"
LIBS=""
AC_SEARCH_LIBS(dlopen, c dl,,,)
DL_LIBS=$LIBS
AC_SUBST(DL_LIBS)
LIBS="$gnupg_dlopen_save_libs"
# Checks for g10
AC_ARG_ENABLE(sqlite,
AS_HELP_STRING([--disable-sqlite],
[disable the use of SQLITE]),
try_sqlite=$enableval, try_sqlite=yes)
if test x"$use_tofu" = xyes ; then
if test x"$try_sqlite" = xyes ; then
PKG_CHECK_MODULES([SQLITE3], [sqlite3 >= $NEED_SQLITE_VERSION],
[have_sqlite=yes],
[have_sqlite=no])
fi
if test "$have_sqlite" = "yes"; then
:
AC_SUBST([SQLITE3_CFLAGS])
AC_SUBST([SQLITE3_LIBS])
else
use_tofu=no
tmp=$(echo "$SQLITE3_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g')
AC_MSG_WARN([[
***
*** Building without SQLite support - TOFU and Keyboxd disabled
***
*** $tmp]])
fi
fi
if test "$have_sqlite" != "yes"; then
build_keyboxd=no
fi
AM_CONDITIONAL(SQLITE3, test "$have_sqlite" = "yes")
if test x"$use_tofu" = xyes ; then
AC_DEFINE(USE_TOFU, 1, [Enable to build the TOFU code])
fi
# Checks for g13
AC_PATH_PROG(ENCFS, encfs, /usr/bin/encfs)
AC_DEFINE_UNQUOTED(ENCFS,
"${ENCFS}", [defines the filename of the encfs program])
AC_PATH_PROG(FUSERMOUNT, fusermount, /usr/bin/fusermount)
AC_DEFINE_UNQUOTED(FUSERMOUNT,
"${FUSERMOUNT}", [defines the filename of the fusermount program])
#
# Check whether the nPth library is available
#
AM_PATH_NPTH("$NEED_NPTH_API:$NEED_NPTH_VERSION",have_npth=yes,have_npth=no)
if test "$have_npth" = "yes"; then
AC_DEFINE(HAVE_NPTH, 1,
[Defined if the New Portable Thread Library is available])
AC_DEFINE(USE_NPTH, 1,
[Defined if support for nPth is requested and nPth is available])
else
AC_MSG_WARN([[
***
*** To support concurrent access for example in gpg-agent and the SCdaemon
*** we need the support of the New Portable Threads Library.
***]])
fi
#
# Enable debugging of nPth
#
AC_ARG_ENABLE(npth-debug,
AS_HELP_STRING([--enable-npth-debug],
[build with debug version of npth]),
[if test $enableval = yes ; then
AC_DEFINE(NPTH_ENABLE_DEBUG,1,
[Build with debug version of nPth])
fi])
#
# NTBTLS is our TLS library. If it is not available we fall back to
# GNUTLS.
#
AC_ARG_ENABLE(ntbtls,
AS_HELP_STRING([--disable-ntbtls],
[disable the use of NTBTLS as TLS library]),
try_ntbtls=$enableval, try_ntbtls=yes)
if test x"$try_ntbtls" = xyes ; then
AM_PATH_NTBTLS("$NEED_NTBTLS_API:$NEED_NTBTLS_VERSION",
[have_ntbtls=yes],[have_ntbtls=no])
fi
if test "$have_ntbtls" = yes ; then
use_tls_library=ntbtls
AC_DEFINE(HTTP_USE_NTBTLS, 1, [Enable NTBTLS support in http.c])
else
AC_ARG_ENABLE(gnutls,
AS_HELP_STRING([--disable-gnutls],
[disable GNUTLS as fallback TLS library]),
try_gnutls=$enableval, try_gnutls=yes)
if test x"$try_gnutls" = xyes ; then
PKG_CHECK_MODULES([LIBGNUTLS], [gnutls >= $NEED_GNUTLS_VERSION],
[have_gnutls=yes],
[have_gnutls=no])
fi
if test "$have_gnutls" = "yes"; then
AC_SUBST([LIBGNUTLS_CFLAGS])
AC_SUBST([LIBGNUTLS_LIBS])
use_tls_library=gnutls
AC_DEFINE(HTTP_USE_GNUTLS, 1, [Enable GNUTLS support in http.c])
else
tmp=$(echo "$LIBGNUTLS_PKG_ERRORS" | tr '\n' '\v' | sed 's/\v/\n*** /g')
build_dirmngr=no
AC_MSG_WARN([[
***
*** Neither NTBTLS nor GNUTLS available - not building dirmngr.
***
*** $tmp]])
fi
fi
#
# Allow to set a fixed trust store file for system provided certificates.
#
AC_ARG_WITH([default-trust-store-file],
[AS_HELP_STRING([--with-default-trust-store-file=FILE],
[Use FILE as system trust store])],
default_trust_store_file="$withval",
default_trust_store_file="")
if test x"$default_trust_store_file" = xno;then
default_trust_store_file=""
fi
if test x"$default_trust_store_file" != x ; then
AC_DEFINE_UNQUOTED([DEFAULT_TRUST_STORE_FILE],
["$default_trust_store_file"], [Use as default system trust store file])
fi
AC_MSG_NOTICE([checking for networking options])
#
# Must check for network library requirements before doing link tests
# for ldap, for example. If ldap libs are static (or dynamic and without
# ELF runtime link paths), then link will fail and LDAP support won't
# be detected.
#
AC_CHECK_FUNC(gethostbyname, , AC_CHECK_LIB(nsl, gethostbyname,
[NETLIBS="-lnsl $NETLIBS"]))
AC_CHECK_FUNC(setsockopt, , AC_CHECK_LIB(socket, setsockopt,
[NETLIBS="-lsocket $NETLIBS"]))
#
# Check standard resolver functions.
#
if test "$build_dirmngr" = "yes"; then
_dns_save_libs=$LIBS
LIBS=""
# Find the system resolver which can always be enabled with
# the dirmngr option --standard-resolver.
# the double underscore thing is a glibc-ism?
AC_SEARCH_LIBS(res_query,resolv bind,,
AC_SEARCH_LIBS(__res_query,resolv bind,,have_resolver=no))
AC_SEARCH_LIBS(dn_expand,resolv bind,,
AC_SEARCH_LIBS(__dn_expand,resolv bind,,have_resolver=no))
# macOS renames dn_skipname into res_9_dn_skipname in <resolv.h>,
# and for some reason fools us into believing we don't need
# -lresolv even if we do. Since the test program checking for the
# symbol does not include <resolv.h>, we need to check for the
# renamed symbol explicitly.
AC_SEARCH_LIBS(res_9_dn_skipname,resolv bind,,
AC_SEARCH_LIBS(dn_skipname,resolv bind,,
AC_SEARCH_LIBS(__dn_skipname,resolv bind,,have_resolver=no)))
if test x"$have_resolver" != xno ; then
# Make sure that the BIND 4 resolver interface is workable before
# enabling any code that calls it. At some point I'll rewrite the
# code to use the BIND 8 resolver API.
# We might also want to use libdns instead.
AC_MSG_CHECKING([whether the resolver is usable])
AC_LINK_IFELSE([AC_LANG_PROGRAM([[#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>]],
[[unsigned char answer[PACKETSZ];
res_query("foo.bar",C_IN,T_A,answer,PACKETSZ);
dn_skipname(0,0);
dn_expand(0,0,0,0,0);
]])],have_resolver=yes,have_resolver=no)
AC_MSG_RESULT($have_resolver)
# This is Apple-specific and somewhat bizarre as they changed the
# define in bind 8 for some reason.
if test x"$have_resolver" != xyes ; then
AC_MSG_CHECKING(
[whether I can make the resolver usable with BIND_8_COMPAT])
AC_LINK_IFELSE([AC_LANG_PROGRAM([[#define BIND_8_COMPAT
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/nameser.h>
#include <resolv.h>]],
[[unsigned char answer[PACKETSZ];
res_query("foo.bar",C_IN,T_A,answer,PACKETSZ);
dn_skipname(0,0); dn_expand(0,0,0,0,0);
]])],[have_resolver=yes ; need_compat=yes])
AC_MSG_RESULT($have_resolver)
fi
fi
if test x"$have_resolver" = xyes ; then
AC_DEFINE(HAVE_SYSTEM_RESOLVER,1,[The system's resolver is usable.])
DNSLIBS="$DNSLIBS $LIBS"
if test x"$need_compat" = xyes ; then
AC_DEFINE(BIND_8_COMPAT,1,[an Apple OSXism])
fi
if test "$use_libdns" = yes; then
show_tor_support=yes
fi
elif test "$use_libdns" = yes; then
show_tor_support=yes
else
AC_MSG_WARN([[
***
*** The system's DNS resolver is not usable.
*** Dirmngr functionality is limited.
***]])
show_tor_support="${show_tor_support} (no system resolver)"
fi
if test "$have_w32_system" = yes; then
if test "$use_libdns" = yes; then
DNSLIBS="$DNSLIBS -liphlpapi"
fi
fi
LIBS=$_dns_save_libs
fi
AC_SUBST(DNSLIBS)
#
# Check for LDAP
#
# Note that running the check changes the variable
# gnupg_have_ldap from "n/a" to "no" or "yes".
AC_ARG_ENABLE(ldap,
AS_HELP_STRING([--disable-ldap],
[disable LDAP support]),
[if test "$enableval" = "no"; then gnupg_have_ldap=no; fi])
if test "$gnupg_have_ldap" != "no" ; then
if test "$build_dirmngr" = "yes" ; then
GNUPG_CHECK_LDAP($NETLIBS)
AC_CHECK_LIB(lber, ber_free,
[ LBER_LIBS="$LBER_LIBS -llber"
AC_DEFINE(HAVE_LBER,1,
[defined if liblber is available])
have_lber=yes
])
fi
fi
AC_SUBST(LBER_LIBS)
if test "$gnupg_have_ldap" = "no"; then
AC_MSG_WARN([[
***
*** Building without LDAP support.
*** No CRL access or X.509 certificate search available.
***]])
fi
AM_CONDITIONAL(USE_LDAP, [test "$gnupg_have_ldap" = yes])
if test "$gnupg_have_ldap" = yes ; then
AC_DEFINE(USE_LDAP,1,[Defined if LDAP is support])
fi
#
# Check for sendmail
#
# This isn't necessarily sendmail itself, but anything that gives a
# sendmail-ish interface to the outside world. That includes Exim,
# Postfix, etc. Basically, anything that can handle "sendmail -t".
AC_ARG_WITH(mailprog,
AS_HELP_STRING([--with-mailprog=NAME],
[use "NAME -t" for mail transport]),
,with_mailprog=yes)
if test x"$with_mailprog" = xyes ; then
AC_PATH_PROG(SENDMAIL,sendmail,,"$PATH":/usr/sbin:/usr/libexec:/usr/lib)
elif test x"$with_mailprog" != xno ; then
AC_MSG_CHECKING([for a mail transport program])
AC_SUBST(SENDMAIL,$with_mailprog)
AC_MSG_RESULT($with_mailprog)
fi
AC_DEFINE_UNQUOTED(NAME_OF_SENDMAIL,"$SENDMAIL",
[Tool with sendmail -t interface])
#
# Construct a printable name of the OS
#
case "${host}" in
*-mingw32*)
PRINTABLE_OS_NAME="MingW32"
;;
*-*-cygwin*)
PRINTABLE_OS_NAME="Cygwin"
;;
i?86-emx-os2 | i?86-*-os2*emx )
PRINTABLE_OS_NAME="OS/2"
;;
i?86-*-msdosdjgpp*)
PRINTABLE_OS_NAME="MSDOS/DJGPP"
try_dynload=no
;;
*-linux*)
PRINTABLE_OS_NAME="GNU/Linux"
;;
*)
PRINTABLE_OS_NAME=`uname -s || echo "Unknown"`
;;
esac
AC_DEFINE_UNQUOTED(PRINTABLE_OS_NAME, "$PRINTABLE_OS_NAME",
[A human readable text with the name of the OS])
#
# Checking for iconv
#
if test "$require_iconv" = yes; then
AM_ICONV
else
LIBICONV=
LTLIBICONV=
AC_SUBST(LIBICONV)
AC_SUBST(LTLIBICONV)
fi
#
# Check for gettext
#
# This is "GNU gnupg" - The project-id script from gettext
# needs this string
#
AC_MSG_NOTICE([checking for gettext])
AM_PO_SUBDIRS
AM_GNU_GETTEXT_VERSION([0.17])
if test "$try_gettext" = yes; then
AM_GNU_GETTEXT([external],[need-ngettext])
# gettext requires some extra checks. These really should be part of
# the basic AM_GNU_GETTEXT macro. TODO: move other gettext-specific
# function checks to here.
AC_CHECK_FUNCS(strchr)
else
USE_NLS=no
USE_INCLUDED_LIBINTL=no
BUILD_INCLUDED_LIBINTL=no
POSUB=po
AC_SUBST(USE_NLS)
AC_SUBST(USE_INCLUDED_LIBINTL)
AC_SUBST(BUILD_INCLUDED_LIBINTL)
AC_SUBST(POSUB)
fi
# We use HAVE_LANGINFO_CODESET in a couple of places.
AM_LANGINFO_CODESET
# Checks required for our use of locales
gt_LC_MESSAGES
#
# SELinux support
#
if test "$selinux_support" = yes ; then
AC_DEFINE(ENABLE_SELINUX_HACKS,1,[Define to enable SELinux support])
fi
#
# Checks for header files.
#
AC_MSG_NOTICE([checking for header files])
AC_CHECK_HEADERS([unistd.h langinfo.h termio.h locale.h \
pwd.h inttypes.h signal.h sys/select.h sys/time.h \
stdint.h signal.h termios.h \
ucred.h sys/ucred.h sys/sysmacros.h sys/mkdev.h])
#
# Checks for typedefs, structures, and compiler characteristics.
#
AC_MSG_NOTICE([checking for system characteristics])
AC_C_CONST
AC_C_INLINE
AC_C_VOLATILE
AC_TYPE_SIZE_T
AC_TYPE_MODE_T
AC_CHECK_FUNCS([sigdescr_np])
AC_CHECK_DECLS([sys_siglist],[],[],[#include <signal.h>
/* NetBSD declares sys_siglist in unistd.h. */
#ifdef HAVE_UNISTD_H
# include <unistd.h>
#endif
])
gl_TYPE_SOCKLEN_T
AC_SEARCH_LIBS([inet_addr], [nsl])
AC_ARG_ENABLE(endian-check,
AS_HELP_STRING([--disable-endian-check],
[disable the endian check and trust the OS provided macros]),
endiancheck=$enableval,endiancheck=yes)
if test x"$endiancheck" = xyes ; then
GNUPG_CHECK_ENDIAN
fi
# fixme: we should get rid of the byte type
AC_CHECK_TYPES([byte, ushort, ulong, u16, u32])
AC_CHECK_SIZEOF(unsigned short)
AC_CHECK_SIZEOF(unsigned int)
AC_CHECK_SIZEOF(unsigned long)
AC_CHECK_SIZEOF(unsigned long long)
AC_CHECK_SIZEOF(size_t)
AC_CHECK_SIZEOF(time_t,,[[
#include <stdio.h>
#ifdef HAVE_SYS_TIME_H
# include <sys/time.h>
#else
# include <time.h>
#endif
]])
GNUPG_TIME_T_UNSIGNED
# Check SOCKET type for Windows.
AC_CHECK_TYPES([SOCKET], [], [], [[#include "winsock2.h"]])
if test "$ac_cv_sizeof_unsigned_short" = "0" \
|| test "$ac_cv_sizeof_unsigned_int" = "0" \
|| test "$ac_cv_sizeof_unsigned_long" = "0"; then
AC_MSG_WARN([Hmmm, something is wrong with the sizes - using defaults]);
fi
#
# Checks for library functions.
#
AC_MSG_NOTICE([checking for library functions])
AC_CHECK_DECLS(getpagesize)
AC_FUNC_FSEEKO
AC_FUNC_VPRINTF
AC_FUNC_FORK
AC_CHECK_FUNCS([atexit canonicalize_file_name clock_gettime ctermid \
explicit_bzero fcntl flockfile fsync ftello \
ftruncate funlockfile getaddrinfo getenv getpagesize \
getpwnam getpwuid getrlimit getrusage gettimeofday \
gmtime_r inet_ntop inet_pton isascii lstat memicmp \
memmove memrchr mmap nl_langinfo pipe raise rand \
setenv setlocale setrlimit sigaction sigprocmask \
stat stpcpy strcasecmp strerror strftime stricmp \
strlwr strncasecmp strpbrk strsep strtol strtoul \
strtoull tcgetattr timegm times ttyname unsetenv \
wait4 waitpid ])
# On some systems (e.g. Solaris) nanosleep requires linking to librl.
# Given that we use nanosleep only as an optimization over a select
# based wait function we want it only if it is available in libc.
_save_libs="$LIBS"
AC_SEARCH_LIBS([nanosleep], [],
[AC_DEFINE(HAVE_NANOSLEEP,1,
[Define to 1 if you have the `nanosleep' function in libc.])])
LIBS="$_save_libs"
# See whether libc supports the Linux inotify interface
case "${host}" in
*-*-linux*)
AC_CHECK_FUNCS([inotify_init])
;;
esac
if test "$have_android_system" = yes; then
# On Android ttyname is a stub but prints an error message.
AC_DEFINE(HAVE_BROKEN_TTYNAME,1,
[Defined if ttyname does not work properly])
fi
AC_CHECK_TYPES([struct sigaction, sigset_t],,,[#include <signal.h>])
# Dirmngr requires mmap on Unix systems.
if test $ac_cv_func_mmap != yes -a $mmap_needed = yes; then
AC_MSG_ERROR([[Sorry, the current implementation requires mmap.]])
fi
#
# Check for the getsockopt SO_PEERCRED, etc.
#
AC_CHECK_MEMBERS([struct ucred.pid, struct ucred.cr_pid, struct sockpeercred.pid], [], [], [#include <sys/types.h>
#include <sys/socket.h> ])
# (Open)Solaris
AC_CHECK_FUNCS([getpeerucred])
#
# W32 specific test
#
#
# Do we have zlib? Must do it here because Solaris failed
# when compiling a conftest (due to the "-lz" from LIBS).
# Note that we combine zlib and bzlib2 in ZLIBS.
#
if test "$use_zip" = yes ; then
_cppflags="${CPPFLAGS}"
_ldflags="${LDFLAGS}"
AC_ARG_WITH(zlib,
[ --with-zlib=DIR use libz in DIR],[
if test -d "$withval"; then
CPPFLAGS="${CPPFLAGS} -I$withval/include"
LDFLAGS="${LDFLAGS} -L$withval/lib"
fi
])
AC_CHECK_HEADER(zlib.h,
AC_CHECK_LIB(z, deflateInit2_,
[
ZLIBS="-lz"
AC_DEFINE(HAVE_ZIP,1, [Defined if ZIP and ZLIB are supported])
],
CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}),
CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags})
fi
#
# Check whether we can support bzip2
#
if test "$use_bzip2" = yes ; then
_cppflags="${CPPFLAGS}"
_ldflags="${LDFLAGS}"
AC_ARG_WITH(bzip2,
AS_HELP_STRING([--with-bzip2=DIR],[look for bzip2 in DIR]),
[
if test -d "$withval" ; then
CPPFLAGS="${CPPFLAGS} -I$withval/include"
LDFLAGS="${LDFLAGS} -L$withval/lib"
fi
],withval="")
# Checking alongside stdio.h as an early version of bzip2 (1.0)
# required stdio.h to be included before bzlib.h, and Solaris 9 is
# woefully out of date.
if test "$withval" != no ; then
AC_CHECK_HEADER(bzlib.h,
AC_CHECK_LIB(bz2,BZ2_bzCompressInit,
[
have_bz2=yes
ZLIBS="$ZLIBS -lbz2"
AC_DEFINE(HAVE_BZIP2,1,
[Defined if the bz2 compression library is available])
],
CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags}),
CPPFLAGS=${_cppflags} LDFLAGS=${_ldflags},[#include <stdio.h>])
fi
fi
AM_CONDITIONAL(ENABLE_BZIP2_SUPPORT,test x"$have_bz2" = "xyes")
AC_SUBST(ZLIBS)
# Check for readline support
GNUPG_CHECK_READLINE
if test "$development_version" = yes; then
AC_DEFINE(IS_DEVELOPMENT_VERSION,1,
[Defined if this is not a regular release])
fi
if test "$USE_MAINTAINER_MODE" = "yes"; then
AC_DEFINE(MAINTAINER_MODE,1,
[Defined if this build is in maintainer mode])
fi
AM_CONDITIONAL(CROSS_COMPILING, test x$cross_compiling = xyes)
GNUPG_CHECK_GNUMAKE
# Add some extra libs here so that previous tests don't fail for
# mysterious reasons - the final link step should bail out.
# W32SOCKLIBS is also defined so that if can be used for tools not
# requiring any network stuff but linking to code in libcommon which
# tracks in winsock stuff (e.g. init_common_subsystems).
if test "$have_w32_system" = yes; then
W32SOCKLIBS="-lws2_32"
NETLIBS="${NETLIBS} ${W32SOCKLIBS}"
fi
AC_SUBST(NETLIBS)
AC_SUBST(W32SOCKLIBS)
#
# TPM libtss library .. don't compile TPM support if we don't have it
#
AC_ARG_WITH([tss],
[AS_HELP_STRING([--with-tss=TSS],
[use the specified TPM Software Stack (ibm, intel, or autodetect)])],
[with_tss=$withval],
[with_tss=autodetect])
LIBTSS_LIBS=
LIBTSS_CFLAGS=
if test "$build_tpm2d" = "yes"; then
_save_libs="$LIBS"
_save_cflags="$CFLAGS"
LIBS=""
if test "$with_tss" = autodetect; then
AC_SEARCH_LIBS([TSS_Create],[tss ibmtss],have_libtss=IBM,
AC_SEARCH_LIBS([Esys_Initialize],[tss2-esys],have_libtss=Intel,have_libtss=no))
elif test "$with_tss" = ibm; then
AC_SEARCH_LIBS([TSS_Create],[tss ibmtss],have_libtss=IBM,
[AC_MSG_ERROR([IBM TPM Software Stack requested but not found])])
elif test "$with_tss" = intel; then
AC_SEARCH_LIBS([Esys_Initialize],[tss2-esys],have_libtss=Intel,
[AC_MSG_ERROR([Intel TPM Software Stack requested but not found])])
else
AC_MSG_ERROR([Invalid TPM Software Stack requested: $with_tss])
fi
if test "$have_libtss" = IBM; then
LIBTSS_CFLAGS="-DTPM_POSIX"
CFLAGS="$CFLAGS ${LIBTSS_CFLAGS}"
AC_CHECK_HEADER([tss2/tss.h],
[AC_DEFINE(TSS_INCLUDE,tss2, [tss2 include location])], [
AC_CHECK_HEADER([ibmtss/tss.h],[AC_DEFINE(TSS_INCLUDE,ibmtss,
[ibmtss include location])], [
AC_MSG_WARN([No TSS2 include directory found, disabling TPM support])
have_libtss=no
])
])
LIBTSS_LIBS=$LIBS
AC_SUBST(TSS_INCLUDE)
elif test "$have_libtss" = Intel; then
##
# Intel TSS has an API issue: Esys_TR_GetTpmHandle wasn't introduced
# until version 2.4.0.
#
# Note: the missing API is fairly serious and is also easily backportable
- # so keep the check below as is intead of going by library version number.
+ # so keep the check below as is instead of going by library version number.
##
AC_CHECK_LIB(tss2-esys, Esys_TR_GetTpmHandle, [], [
AC_MSG_WARN([Need Esys_TR_GetTpmHandle API (usually requires Intel TSS 2.4.0 or later, disabling TPM support)])
have_libtss=no
])
LIBTSS_LIBS="$LIBS -ltss2-mu -ltss2-rc -ltss2-tctildr"
AC_DEFINE(HAVE_INTEL_TSS, 1, [Defined if we have the Intel TSS])
fi
LIBS="$_save_libs"
CFLAGS="$_save_cflags"
if test "$have_libtss" != no; then
AC_DEFINE(HAVE_LIBTSS, 1, [Defined if we have TPM2 support library])
# look for a TPM emulator for testing
AC_PATH_PROG(TPMSERVER, tpm_server)
AC_PATH_PROG(TSSSTARTUP, tssstartup)
AC_PATH_PROG(SWTPM, swtpm)
fi
fi
if test "$have_libtss" = no; then
build_tpm2d=no
fi
AC_SUBST(LIBTSS_LIBS)
AC_SUBST(LIBTSS_CFLAGS)
AM_CONDITIONAL(HAVE_LIBTSS, test "$have_libtss" != no)
AM_CONDITIONAL(TEST_LIBTSS, test -n "$SWTPM" -o -n "$TPMSERVER" -a -n "$TSSSTARTUP")
AC_SUBST(HAVE_LIBTSS)
#
# Setup gcc specific options
#
USE_C99_CFLAGS=
AC_MSG_NOTICE([checking for cc features])
if test "$GCC" = yes; then
mycflags=
mycflags_save=$CFLAGS
- # Check whether gcc does not emit a diagnositc for unknown -Wno-*
+ # Check whether gcc does not emit a diagnostic for unknown -Wno-*
# options. This is the case for gcc >= 4.6
AC_MSG_CHECKING([if gcc ignores unknown -Wno-* options])
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([[
#if __GNUC__ < 4 || (__GNUC__ == 4 && __GNUC_MINOR__ < 6 )
#kickerror
#endif]],[])],[_gcc_silent_wno=yes],[_gcc_silent_wno=no])
AC_MSG_RESULT($_gcc_silent_wno)
# Note that it is okay to use CFLAGS here because these are just
# warning options and the user should have a chance of overriding
# them.
if test "$USE_MAINTAINER_MODE" = "yes"; then
mycflags="$mycflags -O3 -Wall -Wcast-align -Wshadow -Wstrict-prototypes"
mycflags="$mycflags -Wformat -Wno-format-y2k -Wformat-security"
if test x"$_gcc_silent_wno" = xyes ; then
_gcc_wopt=yes
else
AC_MSG_CHECKING([if gcc supports -Wno-missing-field-initializers])
CFLAGS="-Wno-missing-field-initializers"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],
[_gcc_wopt=yes],[_gcc_wopt=no])
AC_MSG_RESULT($_gcc_wopt)
fi
if test x"$_gcc_wopt" = xyes ; then
mycflags="$mycflags -W -Wno-sign-compare -Wno-format-zero-length"
mycflags="$mycflags -Wno-missing-field-initializers"
fi
AC_MSG_CHECKING([if gcc supports -Wdeclaration-after-statement])
CFLAGS="-Wdeclaration-after-statement"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no)
AC_MSG_RESULT($_gcc_wopt)
if test x"$_gcc_wopt" = xyes ; then
mycflags="$mycflags -Wdeclaration-after-statement"
fi
AC_MSG_CHECKING([if gcc supports -Wlogical-op])
CFLAGS="-Wlogical-op -Werror"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no)
AC_MSG_RESULT($_gcc_wopt)
if test x"$_gcc_wopt" = xyes ; then
mycflags="$mycflags -Wlogical-op"
fi
AC_MSG_CHECKING([if gcc supports -Wvla])
CFLAGS="-Wvla"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_wopt=yes,_gcc_wopt=no)
AC_MSG_RESULT($_gcc_wopt)
if test x"$_gcc_wopt" = xyes ; then
mycflags="$mycflags -Wvla"
fi
else
mycflags="$mycflags -Wall"
if test x"$_gcc_silent_wno" = xyes ; then
mycflags="$mycflags -Wno-format-zero-length"
fi
fi
if test x"$_gcc_silent_wno" = xyes ; then
_gcc_psign=yes
else
AC_MSG_CHECKING([if gcc supports -Wno-pointer-sign])
CFLAGS="-Wno-pointer-sign"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],
[_gcc_psign=yes],[_gcc_psign=no])
AC_MSG_RESULT($_gcc_psign)
fi
if test x"$_gcc_psign" = xyes ; then
mycflags="$mycflags -Wno-pointer-sign"
fi
AC_MSG_CHECKING([if gcc supports -Wpointer-arith])
CFLAGS="-Wpointer-arith"
AC_COMPILE_IFELSE([AC_LANG_PROGRAM([],[])],_gcc_psign=yes,_gcc_psign=no)
AC_MSG_RESULT($_gcc_psign)
if test x"$_gcc_psign" = xyes ; then
mycflags="$mycflags -Wpointer-arith"
fi
CFLAGS="$mycflags $mycflags_save"
if test "$use_libdns" = yes; then
# dirmngr/dns.{c,h} require C99 and GNU extensions. */
USE_C99_CFLAGS="-std=gnu99"
fi
fi
AC_SUBST(USE_C99_CFLAGS)
#
# This is handy for debugging so the compiler doesn't rearrange
# things and eliminate variables.
#
AC_ARG_ENABLE(optimization,
AS_HELP_STRING([--disable-optimization],
[disable compiler optimization]),
[if test $enableval = no ; then
CFLAGS=`echo $CFLAGS | sed s/-O[[1-9]]\ /-O0\ /g`
fi])
#
# log_debug has certain requirements which might hamper portability.
# Thus we use an option to enable it.
#
AC_MSG_CHECKING([whether to enable log_clock])
AC_ARG_ENABLE(log_clock,
AS_HELP_STRING([--enable-log-clock],
[enable log_clock timestamps]),
enable_log_clock=$enableval, enable_log_clock=no)
AC_MSG_RESULT($enable_log_clock)
if test "$enable_log_clock" = yes ; then
AC_DEFINE(ENABLE_LOG_CLOCK,1,[Defined to use log_clock timestamps])
fi
# Add -Werror to CFLAGS. This hack can be used to avoid problems with
# misbehaving autoconf tests in case the user supplied -Werror.
#
AC_ARG_ENABLE(werror,
AS_HELP_STRING([--enable-werror],
[append -Werror to CFLAGS]),
[if test $enableval = yes ; then
CFLAGS="$CFLAGS -Werror"
fi])
#
# Configure option --enable-all-tests
#
AC_MSG_CHECKING([whether "make check" shall run all tests])
AC_ARG_ENABLE(all-tests,
AS_HELP_STRING([--enable-all-tests],
[let "make check" run all tests]),
run_all_tests=$enableval, run_all_tests=no)
AC_MSG_RESULT($run_all_tests)
if test "$run_all_tests" = "yes"; then
AC_DEFINE(RUN_ALL_TESTS,1,
[Defined if "make check" shall run all tests])
fi
#
# Configure option --disable-tests
#
AC_MSG_CHECKING([whether tests should be run])
AC_ARG_ENABLE(tests,
AS_HELP_STRING([--disable-tests],
[do not run any tests]),
run_tests=$enableval, run_tests=yes)
AC_MSG_RESULT($run_tests)
#
# We do not want support for the GNUPG_BUILDDIR environment variable
# in a released version. However, our regression tests suite requires
# this and thus we build with support for it during "make distcheck".
# This configure option implements this along with the top Makefile's
# AM_DISTCHECK_CONFIGURE_FLAGS.
#
gnupg_builddir_envvar=no
AC_ARG_ENABLE(gnupg-builddir-envvar,,
gnupg_builddir_envvar=$enableval)
if test x"$gnupg_builddir_envvar" = x"yes"; then
AC_DEFINE(ENABLE_GNUPG_BUILDDIR_ENVVAR, 1,
[This is only used with "make distcheck"])
fi
#
# To avoid problems with systemd cleaning up the /run/user directory,
# this option will make GnuPG try to use /run/gnupg/user as socket dir
# before /run/user
#
AC_ARG_ENABLE(run-gnupg-user-socket,
AS_HELP_STRING([--enable-run-gnupg-user-socket],
[try /run/gnupg/user for sockets prior to /run/user]),
use_run_gnupg_user_socket=$enableval)
if test x"$use_run_gnupg_user_socket" = x"yes"; then
AC_DEFINE(USE_RUN_GNUPG_USER_SOCKET, 1,
[If defined try /run/gnupg/user before /run/user])
fi
#
# Decide what to build
#
build_scdaemon_extra=""
if test "$build_scdaemon" = "yes"; then
if test $have_libusb = no; then
build_scdaemon_extra="without internal CCID driver"
fi
if test -n "$build_scdaemon_extra"; then
build_scdaemon_extra="(${build_scdaemon_extra})"
fi
fi
#
# Set variables for use by automake makefiles.
#
AM_CONDITIONAL(BUILD_GPG, test "$build_gpg" = "yes")
AM_CONDITIONAL(BUILD_GPGSM, test "$build_gpgsm" = "yes")
AM_CONDITIONAL(BUILD_AGENT, test "$build_agent" = "yes")
AM_CONDITIONAL(BUILD_SCDAEMON, test "$build_scdaemon" = "yes")
AM_CONDITIONAL(BUILD_G13, test "$build_g13" = "yes")
AM_CONDITIONAL(BUILD_DIRMNGR, test "$build_dirmngr" = "yes")
AM_CONDITIONAL(BUILD_KEYBOXD, test "$build_keyboxd" = "yes")
AM_CONDITIONAL(BUILD_TPM2D, test "$build_tpm2d" = "yes")
AM_CONDITIONAL(BUILD_DOC, test "$build_doc" = "yes")
AM_CONDITIONAL(BUILD_GPGTAR, test "$build_gpgtar" = "yes")
AM_CONDITIONAL(BUILD_WKS_TOOLS, test "$build_wks_tools" = "yes")
AM_CONDITIONAL(DISABLE_TESTS, test "$run_tests" != yes)
AM_CONDITIONAL(ENABLE_CARD_SUPPORT, test "$card_support" = yes)
AM_CONDITIONAL(NO_TRUST_MODELS, test "$use_trust_models" = no)
AM_CONDITIONAL(USE_TOFU, test "$use_tofu" = yes)
#
# Set some defines for use gpgconf.
#
if test "$build_gpg" = yes ; then
AC_DEFINE(BUILD_WITH_GPG,1,[Defined if GPG is to be build])
fi
if test "$build_gpgsm" = yes ; then
AC_DEFINE(BUILD_WITH_GPGSM,1,[Defined if GPGSM is to be build])
fi
if test "$build_agent" = yes ; then
AC_DEFINE(BUILD_WITH_AGENT,1,[Defined if GPG-AGENT is to be build])
fi
if test "$build_scdaemon" = yes ; then
AC_DEFINE(BUILD_WITH_SCDAEMON,1,[Defined if SCDAEMON is to be build])
fi
if test "$build_dirmngr" = yes ; then
AC_DEFINE(BUILD_WITH_DIRMNGR,1,[Defined if DIRMNGR is to be build])
fi
if test "$build_keyboxd" = yes ; then
AC_DEFINE(BUILD_WITH_KEYBOXD,1,[Defined if KEYBOXD is to be build])
fi
if test "$build_tpm2d" = yes ; then
AC_DEFINE(BUILD_WITH_TPM2D,1,[Defined if TPM2D to be build])
fi
if test "$build_g13" = yes ; then
AC_DEFINE(BUILD_WITH_G13,1,[Defined if G13 is to be build])
fi
#
# Define Name strings
#
AC_DEFINE_UNQUOTED(GNUPG_NAME, "GnuPG", [The name of the project])
AC_DEFINE_UNQUOTED(GPG_NAME, "gpg", [The name of the OpenPGP tool])
AC_DEFINE_UNQUOTED(GPG_DISP_NAME, "GnuPG", [The displayed name of gpg])
AC_DEFINE_UNQUOTED(GPGSM_NAME, "gpgsm", [The name of the S/MIME tool])
AC_DEFINE_UNQUOTED(GPGSM_DISP_NAME, "GPGSM", [The displayed name of gpgsm])
AC_DEFINE_UNQUOTED(GPG_AGENT_NAME, "gpg-agent", [The name of the agent])
AC_DEFINE_UNQUOTED(GPG_AGENT_DISP_NAME, "GPG Agent",
[The displayed name of gpg-agent])
AC_DEFINE_UNQUOTED(TPM2DAEMON_NAME, "tpm2daemon", [The name of the TPM2 daemon])
AC_DEFINE_UNQUOTED(TPM2DAEMON_DISP_NAME, "TPM2 Daemon",
[The displayed name of TPM2 daemon])
AC_DEFINE_UNQUOTED(SCDAEMON_NAME, "scdaemon", [The name of the scdaemon])
AC_DEFINE_UNQUOTED(SCDAEMON_DISP_NAME, "SCDaemon",
[The displayed name of scdaemon])
AC_DEFINE_UNQUOTED(DIRMNGR_NAME, "dirmngr", [The name of the dirmngr])
AC_DEFINE_UNQUOTED(DIRMNGR_DISP_NAME, "DirMngr",
[The displayed name of dirmngr])
AC_DEFINE_UNQUOTED(KEYBOXD_NAME, "keyboxd", [The name of the keyboxd])
AC_DEFINE_UNQUOTED(KEYBOXD_DISP_NAME, "Keyboxd",
[The displayed name of keyboxd])
AC_DEFINE_UNQUOTED(G13_NAME, "g13", [The name of the g13 tool])
AC_DEFINE_UNQUOTED(G13_DISP_NAME, "G13", [The displayed name of g13])
AC_DEFINE_UNQUOTED(GPGCONF_NAME, "gpgconf", [The name of the gpgconf tool])
AC_DEFINE_UNQUOTED(GPGCONF_DISP_NAME, "GPGConf",
[The displayed name of gpgconf])
AC_DEFINE_UNQUOTED(GPGTAR_NAME, "gpgtar", [The name of the gpgtar tool])
AC_DEFINE_UNQUOTED(GPG_AGENT_SOCK_NAME, "S.gpg-agent",
[The name of the agent socket])
AC_DEFINE_UNQUOTED(GPG_AGENT_EXTRA_SOCK_NAME, "S.gpg-agent.extra",
[The name of the agent socket for remote access])
AC_DEFINE_UNQUOTED(GPG_AGENT_BROWSER_SOCK_NAME, "S.gpg-agent.browser",
[The name of the agent socket for browsers])
AC_DEFINE_UNQUOTED(GPG_AGENT_SSH_SOCK_NAME, "S.gpg-agent.ssh",
[The name of the agent socket for ssh])
AC_DEFINE_UNQUOTED(DIRMNGR_INFO_NAME, "DIRMNGR_INFO",
[The name of the dirmngr info envvar])
AC_DEFINE_UNQUOTED(SCDAEMON_SOCK_NAME, "S.scdaemon",
[The name of the SCdaemon socket])
AC_DEFINE_UNQUOTED(KEYBOXD_SOCK_NAME, "S.keyboxd",
[The name of the keyboxd socket])
AC_DEFINE_UNQUOTED(TPM2DAEMON_SOCK_NAME, "S.tpm2daemon",
[The name of the TPM2 daemon socket])
AC_DEFINE_UNQUOTED(DIRMNGR_SOCK_NAME, "S.dirmngr",
[The name of the dirmngr socket])
AC_DEFINE_UNQUOTED(DIRMNGR_DEFAULT_KEYSERVER,
"hkps://keyserver.ubuntu.com",
[The default keyserver for dirmngr to use, if none is explicitly given])
AC_DEFINE_UNQUOTED(GPGEXT_GPG, "gpg", [The standard binary file suffix])
if test "$have_w32_system" = yes; then
AC_DEFINE_UNQUOTED(GNUPG_REGISTRY_DIR, "Software\\\\GNU\\\\GnuPG",
[The directory part of the W32 registry keys])
fi
#
# Provide information about the build.
#
BUILD_REVISION="mym4_revision"
AC_SUBST(BUILD_REVISION)
AC_DEFINE_UNQUOTED(BUILD_REVISION, "$BUILD_REVISION",
[GIT commit id revision used to build this package])
changequote(,)dnl
BUILD_VERSION=`echo "$VERSION" | sed 's/\([0-9.]*\).*/\1./'`
changequote([,])dnl
BUILD_VERSION="${BUILD_VERSION}mym4_revision_dec"
BUILD_FILEVERSION=`echo "${BUILD_VERSION}" | tr . ,`
AC_SUBST(BUILD_VERSION)
AC_SUBST(BUILD_FILEVERSION)
AC_ARG_ENABLE([build-timestamp],
AS_HELP_STRING([--enable-build-timestamp],
[set an explicit build timestamp for reproducibility.
(default is the current time in ISO-8601 format)]),
[if test "$enableval" = "yes"; then
BUILD_TIMESTAMP=`date -u +%Y-%m-%dT%H:%M+0000 2>/dev/null || date`
else
BUILD_TIMESTAMP="$enableval"
fi
BUILD_HOSTNAME="$ac_hostname"],
[BUILD_TIMESTAMP="<none>"
BUILD_HOSTNAME="<anon>"])
AC_SUBST(BUILD_TIMESTAMP)
AC_DEFINE_UNQUOTED(BUILD_TIMESTAMP, "$BUILD_TIMESTAMP",
[The time this package was configured for a build])
AC_SUBST(BUILD_HOSTNAME)
#
# Print errors here so that they are visible all
# together and the user can acquire them all together.
#
die=no
if test "$have_gpg_error" = "no"; then
die=yes
AC_MSG_NOTICE([[
***
*** You need libgpg-error to build this program.
** This library is for example available at
*** https://gnupg.org/ftp/gcrypt/gpgrt
*** (at least version $NEED_GPGRT_VERSION is required.)
***]])
fi
if test "$have_libgcrypt" = "no"; then
die=yes
AC_MSG_NOTICE([[
***
*** You need libgcrypt to build this program.
** This library is for example available at
*** https://gnupg.org/ftp/gcrypt/libgcrypt/
*** (at least version $NEED_LIBGCRYPT_VERSION (API $NEED_LIBGCRYPT_API) is required.)
***]])
fi
if test "$have_libassuan" = "no"; then
die=yes
AC_MSG_NOTICE([[
***
*** You need libassuan to build this program.
*** This library is for example available at
*** https://gnupg.org/ftp/gcrypt/libassuan/
*** (at least version $NEED_LIBASSUAN_VERSION (API $NEED_LIBASSUAN_API) is required).
***]])
fi
if test "$have_ksba" = "no"; then
die=yes
AC_MSG_NOTICE([[
***
*** You need libksba to build this program.
*** This library is for example available at
*** https://gnupg.org/ftp/gcrypt/libksba/
*** (at least version $NEED_KSBA_VERSION using API $NEED_KSBA_API is required).
***]])
fi
if test "$have_npth" = "no"; then
die=yes
AC_MSG_NOTICE([[
***
*** It is now required to build with support for the
*** New Portable Threads Library (nPth). Please install this
*** library first. The library is for example available at
*** https://gnupg.org/ftp/gcrypt/npth/
*** (at least version $NEED_NPTH_VERSION (API $NEED_NPTH_API) is required).
***]])
fi
if test "$require_iconv" = yes; then
if test "$am_func_iconv" != yes; then
die=yes
AC_MSG_NOTICE([[
***
*** The system does not provide a working iconv function. Please
*** install a suitable library; for example GNU Libiconv which is
*** available at:
*** https://ftp.gnu.org/gnu/libiconv/
***]])
fi
fi
if test "$use_ccid_driver" = yes; then
if test "$have_libusb" != yes; then
die=yes
AC_MSG_NOTICE([[
***
*** You need libusb to build the internal ccid driver. Please
*** install a libusb suitable for your system.
***]])
fi
fi
if test "$die" = "yes"; then
AC_MSG_ERROR([[
***
*** Required libraries not found. Please consult the above messages
*** and install them before running configure again.
***]])
fi
AC_CONFIG_FILES([ m4/Makefile
Makefile
po/Makefile.in
common/Makefile
common/w32info-rc.h
regexp/Makefile
kbx/Makefile
g10/Makefile
sm/Makefile
agent/Makefile
scd/Makefile
tpm2d/Makefile
g13/Makefile
dirmngr/Makefile
tools/Makefile
doc/Makefile
tests/Makefile
tests/gpgscm/Makefile
tests/openpgp/Makefile
tests/cms/Makefile
tests/migrations/Makefile
tests/tpm2dtests/Makefile
tests/gpgme/Makefile
tests/pkits/Makefile
g10/gpg.w32-manifest
g10/gpgv.w32-manifest
sm/gpgsm.w32-manifest
kbx/keyboxd.w32-manifest
agent/gpg-agent.w32-manifest
scd/scdaemon.w32-manifest
dirmngr/dirmngr.w32-manifest
dirmngr/dirmngr_ldap.w32-manifest
dirmngr/dirmngr-client.w32-manifest
tools/gpg-connect-agent.w32-manifest
tools/gpgconf.w32-manifest
tools/gpgtar.w32-manifest
tools/gpg-check-pattern.w32-manifest
tools/gpg-wks-client.w32-manifest
tools/gpg-card.w32-manifest
])
AC_OUTPUT
show_tss_type=
if test "$build_tpm2d" = "yes"; then
show_tss_type="($have_libtss)"
fi
echo "
GnuPG v${VERSION} has been configured as follows:
Revision: mym4_revision (mym4_revision_dec)
Platform: $PRINTABLE_OS_NAME ($host)
OpenPGP: $build_gpg
S/MIME: $build_gpgsm
Agent: $build_agent
Smartcard: $build_scdaemon $build_scdaemon_extra
TPM: $build_tpm2d $show_tss_type
G13: $build_g13
Dirmngr: $build_dirmngr
Keyboxd: $build_keyboxd
Gpgtar: $build_gpgtar
WKS tools: $build_wks_tools
Protect tool: $show_gnupg_protect_tool_pgm
LDAP wrapper: $show_gnupg_dirmngr_ldap_pgm
Default agent: $show_gnupg_agent_pgm
Default pinentry: $show_gnupg_pinentry_pgm
Default scdaemon: $show_gnupg_scdaemon_pgm
Default keyboxd: $show_gnupg_keyboxd_pgm
Default tpm2daemon: $show_gnupg_tpm2daemon_pgm
Default dirmngr: $show_gnupg_dirmngr_pgm
Dirmngr auto start: $dirmngr_auto_start
Readline support: $gnupg_cv_have_readline
LDAP support: $gnupg_have_ldap
TLS support: $use_tls_library
TOFU support: $use_tofu
Tor support: $show_tor_support
"
if test "$have_libtss" != no -a -z "$TPMSERVER" -a -z "$SWTPM"; then
cat <<G10EOF
Warning: TPM support is compiled in but no software TPM
for testing was discovered. TPM tests are disabled
G10EOF
fi
if test "x${gpg_config_script_warn}" != x; then
cat <<G10EOF
Warning: Mismatches between the target platform and the
to be used libraries have been detected for:
${gpg_config_script_warn}
Please check above for more warning messages.
G10EOF
fi
if test "${build_gpg}" != "yes"; then
cat <<G10EOF
Warning: The component "gpg" is used by other components as
well as for the test suite. You have disabled
this component and thus other things won't work.
G10EOF
fi
if test "${run_tests}" != "yes"; then
cat <<G10EOF
Warning: The use of the test suite has been disabled!
This is in almost all cases a bad idea. Take care.
G10EOF
fi
diff --git a/dirmngr/ONEWS b/dirmngr/ONEWS
index cb2050748..154d8e0bf 100644
--- a/dirmngr/ONEWS
+++ b/dirmngr/ONEWS
@@ -1,240 +1,240 @@
These are NEWS entries from the old separate dirmngr package
Noteworthy changes in version 1.1.0 (unreleased)
------------------------------------------------
* Fixed a resource problem with LDAP CRLs.
* Fixed a bad EOF detection with HTTP CRLs.
* Made "dirmngr-client --url --load-crl URL" work.
* New option --ignore-cert-extension.
* Make use of libassuan 2.0 which is available as a DSO.
Noteworthy changes in version 1.0.3 (2009-06-17)
------------------------------------------------
* Client based trust anchors are now supported.
* Configured certificates with the suffix ".der" are now also used.
* Libgcrypt 1.4 is now required.
Noteworthy changes in version 1.0.2 (2008-07-31)
------------------------------------------------
* New option --url for the LOOKUP command and dirmngr-client.
* The LOOKUP command does now also consults the local cache. New
option --cache-only for it and --local for dirmngr-client.
* Port to Windows completed.
* Improved certificate chain construction.
* Support loading of PEM encoded CRLs via HTTP.
Noteworthy changes in version 1.0.1 (2007-08-16)
------------------------------------------------
* The option --ocsp-signer may now take a filename to allow several
certificates to be valid signers for the default responder.
* New option --ocsp-max-period and improved the OCSP time checks.
* New option --force-default-signer for dirmngr-client.
* Ported to Windows.
Noteworthy changes in version 1.0.0 (2006-11-29)
------------------------------------------------
- * Bumbed the version number.
+ * Bumped the version number.
* Removed included gettext. We now require the system to provide a
suitable installation.
Noteworthy changes in version 0.9.7 (2006-11-17)
------------------------------------------------
* Internal cleanups.
* Fixed updating of DIR.txt. Add additional diagnostics.
* Updated gettext package.
Noteworthy changes in version 0.9.6 (2006-09-04)
------------------------------------------------
* A couple of bug fixes for OCSP.
* OCSP does now make use of the responder ID and optionally included
certificates in the response to locate certificates.
* No more lost file descriptors when loading CRLs via HTTP.
* HTTP redirection for CRL and OCSP has been implemented.
* Man pages are now build and installed from the texinfo source.
Noteworthy changes in version 0.9.5 (2006-06-27)
------------------------------------------------
* Fixed a problems with the CRL caching and CRL certificate
validation.
* Improved diagnostics.
Noteworthy changes in version 0.9.4 (2006-05-16)
------------------------------------------------
* Try all names of each crlDP.
* Don't shutdown the socket after sending the HTTP request.
Noteworthy changes in version 0.9.3 (2005-10-26)
------------------------------------------------
* Minor bug fixes.
Noteworthy changes in version 0.9.2 (2005-04-21)
------------------------------------------------
* Make use of authorityKeyidentifier.keyIdentifier.
* Fixed a possible hang on exit.
Noteworthy changes in version 0.9.1 (2005-02-08)
------------------------------------------------
* New option --pem for dirmngr-client to allow requesting service
using a PEM encoded certificate.
* New option --squid-mode to allow using dirmngr-client directly as a
Squid helper.
* Bug fixes.
Noteworthy changes in version 0.9.0 (2004-12-17)
------------------------------------------------
* New option --daemon to start dirmngr as a system daemon. This
switches to the use of different directories and also does
CRL signing certificate validation on its own.
* New tool dirmngr-client.
* New options: --ldap-wrapper-program, --http-wrapper-program,
--disable-ldap, --disable-http, --honor-http-proxy, --http-proxy,
--ldap-proxy, --only-ldap-proxy, --ignore-ldap-dp and
--ignore-http-dp.
* Uses an external ldap wrapper to cope with timeouts and general
LDAP problems.
* SIGHUP may be used to reread the configuration and to flush the
certificate cache.
* An authorithyKeyIdentifier in a CRL is now handled correctly.
Noteworthy changes in version 0.5.6 (2004-09-28)
------------------------------------------------
* LDAP fix.
* Logging fixes.
* Updated some configuration files.
Noteworthy changes in version 0.5.5 (2004-05-13)
------------------------------------------------
* Fixed the growing-dir.txt bug.
* Better LDAP error logging.
Noteworthy changes in version 0.5.4 (2004-04-29)
------------------------------------------------
* New commands --ocsp-responder and --ocsp-signer to define a default
- OCSP reponder if a certificate does not contain an assigned OCSP
+ OCSP responder if a certificate does not contain an assigned OCSP
responder.
Noteworthy changes in version 0.5.3 (2004-04-06)
------------------------------------------------
* Basic OCSP support.
Noteworthy changes in version 0.5.2 (2004-03-06)
------------------------------------------------
* New Assuan command LISTCRLS.
* A couple of minor bug fixes.
Noteworthy changes in version 0.5.1 (2003-12-23)
------------------------------------------------
* New options --faked-system-time and --force.
* Changed the name of the cache directory to $HOMEDIR/dirmngr-cache.d
and renamed the dbcontents file. You may delete the now obsolete
cache/ directory and the dbcontents file.
* Dropped DB2 or DB4 use. There is no need for it because a constant
database fits our needs far better.
* Experimental support for retrieving CRLs via http.
* The --log-file option may now be used to print logs to a socket.
Prefix the socket name with "socket://" to enable this. This does
not work on all systems and falls back to stderr if there is a
problem with the socket.
Noteworthy changes in version 0.5.0 (2003-11-17)
------------------------------------------------
* Revamped the entire thing.
* Does now require Libgcrypt 1.1.90 or higher, as well as the latest
libksba and libassuan.
* Fixed a bug in the assuan inquire processing.
Noteworthy changes as of 2002-08-21
------------------------------------
* The default home directory is now .gnupg
Copyright 2003, 2004, 2005 g10 Code GmbH
This file is free software; as a special exception the author gives
unlimited permission to copy and/or distribute it, with or without
modifications, as long as this notice is preserved.
This file is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
diff --git a/dirmngr/certcache.c b/dirmngr/certcache.c
index b7b5b3d15..07a47a525 100644
--- a/dirmngr/certcache.c
+++ b/dirmngr/certcache.c
@@ -1,1925 +1,1925 @@
/* certcache.c - Certificate caching
* Copyright (C) 2004, 2005, 2007, 2008, 2017 g10 Code GmbH
*
* This file is part of DirMngr.
*
* DirMngr 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 2 of the License, or
* (at your option) any later version.
*
* DirMngr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#include <dirent.h>
#include <npth.h>
#include "dirmngr.h"
#include "misc.h"
#include "../common/ksba-io-support.h"
#include "crlfetch.h"
#include "certcache.h"
#define MAX_NONPERM_CACHED_CERTS 1000
/* Constants used to classify search patterns. */
enum pattern_class
{
PATTERN_UNKNOWN = 0,
PATTERN_EMAIL,
PATTERN_EMAIL_SUBSTR,
PATTERN_FINGERPRINT16,
PATTERN_FINGERPRINT20,
PATTERN_SHORT_KEYID,
PATTERN_LONG_KEYID,
PATTERN_SUBJECT,
PATTERN_SERIALNO,
PATTERN_SERIALNO_ISSUER,
PATTERN_ISSUER,
PATTERN_SUBSTR
};
/* A certificate cache item. This consists of a the KSBA cert object
and some meta data for easier lookup. We use a hash table to keep
track of all items and use the (randomly distributed) first byte of
the fingerprint directly as the hash which makes it pretty easy. */
struct cert_item_s
{
struct cert_item_s *next; /* Next item with the same hash value. */
ksba_cert_t cert; /* The KSBA cert object or NULL is this is
not a valid item. */
unsigned char fpr[20]; /* The fingerprint of this object. */
char *issuer_dn; /* The malloced issuer DN. */
ksba_sexp_t sn; /* The malloced serial number */
char *subject_dn; /* The malloced subject DN - maybe NULL. */
/* If this field is set the certificate has been taken from some
* configuration and shall not be flushed from the cache. */
unsigned int permanent:1;
/* If this field is set the certificate is trusted. The actual
* value is a (possible) combination of CERTTRUST_CLASS values. */
unsigned int trustclasses:4;
};
typedef struct cert_item_s *cert_item_t;
/* The actual cert cache consisting of 256 slots for items indexed by
the first byte of the fingerprint. */
static cert_item_t cert_cache[256];
/* This is the global cache_lock variable. In general locking is not
needed but it would take extra efforts to make sure that no
indirect use of npth functions is done, so we simply lock it
always. Note: We can't use static initialization, as that is not
available through w32-pth. */
static npth_rwlock_t cert_cache_lock;
/* Flag to track whether the cache has been initialized. */
static int initialization_done;
/* Total number of non-permanent certificates. */
static unsigned int total_nonperm_certificates;
/* For each cert class the corresponding bit is set if at least one
* certificate of that class is loaded permanetly. */
static unsigned int any_cert_of_class;
#ifdef HAVE_W32_SYSTEM
#include <wincrypt.h>
/* We load some functions dynamically. Provide typedefs for these
* functions. */
typedef HCERTSTORE (WINAPI *CERTOPENSYSTEMSTORE)
(HCRYPTPROV hProv, LPCSTR szSubsystemProtocol);
typedef PCCERT_CONTEXT (WINAPI *CERTENUMCERTIFICATESINSTORE)
(HCERTSTORE hCertStore, PCCERT_CONTEXT pPrevCertContext);
typedef WINBOOL (WINAPI *CERTCLOSESTORE)
(HCERTSTORE hCertStore,DWORD dwFlags);
#endif /*HAVE_W32_SYSTEM*/
/* Helper to do the cache locking. */
static void
init_cache_lock (void)
{
int err;
err = npth_rwlock_init (&cert_cache_lock, NULL);
if (err)
log_fatal (_("can't initialize certificate cache lock: %s\n"),
strerror (err));
}
static void
acquire_cache_read_lock (void)
{
int err;
err = npth_rwlock_rdlock (&cert_cache_lock);
if (err)
log_fatal (_("can't acquire read lock on the certificate cache: %s\n"),
strerror (err));
}
static void
acquire_cache_write_lock (void)
{
int err;
err = npth_rwlock_wrlock (&cert_cache_lock);
if (err)
log_fatal (_("can't acquire write lock on the certificate cache: %s\n"),
strerror (err));
}
static void
release_cache_lock (void)
{
int err;
err = npth_rwlock_unlock (&cert_cache_lock);
if (err)
log_fatal (_("can't release lock on the certificate cache: %s\n"),
strerror (err));
}
/* Return false if both serial numbers match. Can't be used for
sorting. */
static int
compare_serialno (ksba_sexp_t serial1, ksba_sexp_t serial2 )
{
unsigned char *a = serial1;
unsigned char *b = serial2;
return cmp_simple_canon_sexp (a, b);
}
/* Return a malloced canonical S-Expression with the serial number
* converted from the hex string HEXSN. Return NULL on memory
* error. */
ksba_sexp_t
hexsn_to_sexp (const char *hexsn)
{
char *buffer, *p;
size_t len;
char numbuf[40];
len = unhexify (NULL, hexsn);
snprintf (numbuf, sizeof numbuf, "(%u:", (unsigned int)len);
buffer = xtrymalloc (strlen (numbuf) + len + 2 );
if (!buffer)
return NULL;
p = stpcpy (buffer, numbuf);
len = unhexify (p, hexsn);
p[len] = ')';
p[len+1] = 0;
return buffer;
}
/* Compute the fingerprint of the certificate CERT and put it into
the 20 bytes large buffer DIGEST. Return address of this buffer. */
unsigned char *
cert_compute_fpr (ksba_cert_t cert, unsigned char *digest)
{
gpg_error_t err;
gcry_md_hd_t md;
err = gcry_md_open (&md, GCRY_MD_SHA1, 0);
if (err)
log_fatal ("gcry_md_open failed: %s\n", gpg_strerror (err));
err = ksba_cert_hash (cert, 0, HASH_FNC, md);
if (err)
{
log_error ("oops: ksba_cert_hash failed: %s\n", gpg_strerror (err));
memset (digest, 0xff, 20); /* Use a dummy value. */
}
else
{
gcry_md_final (md);
memcpy (digest, gcry_md_read (md, GCRY_MD_SHA1), 20);
}
gcry_md_close (md);
return digest;
}
-/* Cleanup one slot. This releases all resourses but keeps the actual
+/* Cleanup one slot. This releases all resources but keeps the actual
slot in the cache marked for reuse. */
static void
clean_cache_slot (cert_item_t ci)
{
ksba_cert_t cert;
if (!ci->cert)
return; /* Already cleaned. */
ksba_free (ci->sn);
ci->sn = NULL;
ksba_free (ci->issuer_dn);
ci->issuer_dn = NULL;
ksba_free (ci->subject_dn);
ci->subject_dn = NULL;
cert = ci->cert;
ci->cert = NULL;
ci->permanent = 0;
ci->trustclasses = 0;
ksba_cert_release (cert);
}
/* Put the certificate CERT into the cache. It is assumed that the
* cache is locked while this function is called.
*
* FROM_CONFIG indicates that CERT is a permanent certificate and
* should stay in the cache. IS_TRUSTED requests that the trusted
* flag is set for the certificate; a value of 1 indicates the
* cert is trusted due to GnuPG mechanisms, a value of 2 indicates
* that it is trusted because it has been taken from the system's
* store of trusted certificates. If FPR_BUFFER is not NULL the
* fingerprint of the certificate will be stored there. FPR_BUFFER
* needs to point to a buffer of at least 20 bytes. The fingerprint
* will be stored on success or when the function returns
* GPG_ERR_DUP_VALUE or GPG_ERR_NOT_ENABLED. */
static gpg_error_t
put_cert (ksba_cert_t cert, int permanent, unsigned int trustclass,
void *fpr_buffer)
{
unsigned char help_fpr_buffer[20], *fpr;
cert_item_t ci;
fingerprint_list_t ignored;
/* Do not keep expired certificates in the permanent cache. */
if (permanent && !opt.debug_cache_expired_certs)
{
ksba_isotime_t not_after;
ksba_isotime_t current_time;
if (ksba_cert_get_validity (cert, 1, not_after))
return gpg_error (GPG_ERR_BAD_CERT);
gnupg_get_isotime (current_time);
if (*not_after && strcmp (current_time, not_after) > 0)
return gpg_error (GPG_ERR_CERT_EXPIRED);
}
fpr = fpr_buffer? fpr_buffer : &help_fpr_buffer;
/* If we already reached the caching limit, drop a couple of certs
* from the cache. Our dropping strategy is simple: We keep a
* static index counter and use this to start looking for
* certificates, then we drop 5 percent of the oldest certificates
* starting at that index. For a large cache this is a fair way of
* removing items. An LRU strategy would be better of course.
* Because we append new entries to the head of the list and we want
* to remove old ones first, we need to do this from the tail. The
* implementation is not very efficient but compared to the long
* time it takes to retrieve a certificate from an external resource
* it seems to be reasonable. */
if (!permanent && total_nonperm_certificates >= MAX_NONPERM_CACHED_CERTS)
{
static int idx;
cert_item_t ci_mark;
int i;
unsigned int drop_count;
drop_count = MAX_NONPERM_CACHED_CERTS / 20;
if (drop_count < 2)
drop_count = 2;
log_info (_("dropping %u certificates from the cache\n"), drop_count);
assert (idx < 256);
for (i=idx; drop_count; i = ((i+1)%256))
{
ci_mark = NULL;
for (ci = cert_cache[i]; ci; ci = ci->next)
if (ci->cert && !ci->permanent)
ci_mark = ci;
if (ci_mark)
{
clean_cache_slot (ci_mark);
drop_count--;
total_nonperm_certificates--;
}
}
if (i==idx)
idx++;
else
idx = i;
idx %= 256;
}
cert_compute_fpr (cert, fpr);
/* Compare against the list of to be ignored certificates. */
for (ignored = opt.ignored_certs; ignored; ignored = ignored->next)
if (ignored->binlen == 20 && !memcmp (fpr, ignored->hexfpr, 20))
{
/* We are configured not to use this certificate. */
return gpg_error (GPG_ERR_NOT_ENABLED);
}
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (ci->cert && !memcmp (ci->fpr, fpr, 20))
return gpg_error (GPG_ERR_DUP_VALUE);
/* Try to reuse an existing entry. */
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (!ci->cert)
break;
if (!ci)
{ /* No: Create a new entry. */
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
return gpg_error_from_errno (errno);
ci->next = cert_cache[*fpr];
cert_cache[*fpr] = ci;
}
ksba_cert_ref (cert);
ci->cert = cert;
memcpy (ci->fpr, fpr, 20);
ci->sn = ksba_cert_get_serial (cert);
ci->issuer_dn = ksba_cert_get_issuer (cert, 0);
if (!ci->issuer_dn || !ci->sn)
{
clean_cache_slot (ci);
return gpg_error (GPG_ERR_INV_CERT_OBJ);
}
ci->subject_dn = ksba_cert_get_subject (cert, 0);
ci->permanent = !!permanent;
ci->trustclasses = trustclass;
if (permanent)
any_cert_of_class |= trustclass;
else
total_nonperm_certificates++;
return 0;
}
/* Load certificates from the directory DIRNAME. All certificates
matching the pattern "*.crt" or "*.der" are loaded. We assume that
certificates are DER encoded and not PEM encapsulated. The cache
should be in a locked state when calling this function. */
static gpg_error_t
load_certs_from_dir (const char *dirname, unsigned int trustclass)
{
gpg_error_t err;
gnupg_dir_t dir;
gnupg_dirent_t ep;
char *p;
size_t n;
estream_t fp;
ksba_reader_t reader;
ksba_cert_t cert;
char *fname = NULL;
dir = gnupg_opendir (dirname);
if (!dir)
{
return 0; /* We do not consider this a severe error. */
}
while ( (ep = gnupg_readdir (dir)) )
{
p = ep->d_name;
if (*p == '.' || !*p)
continue; /* Skip any hidden files and invalid entries. */
n = strlen (p);
if ( n < 5 || (strcmp (p+n-4,".crt") && strcmp (p+n-4,".der")))
continue; /* Not the desired "*.crt" or "*.der" pattern. */
xfree (fname);
fname = make_filename (dirname, p, NULL);
fp = es_fopen (fname, "rb");
if (!fp)
{
log_error (_("can't open '%s': %s\n"),
fname, strerror (errno));
continue;
}
err = create_estream_ksba_reader (&reader, fp);
if (err)
{
es_fclose (fp);
continue;
}
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_read_der (cert, reader);
ksba_reader_release (reader);
es_fclose (fp);
if (err)
{
log_error (_("can't parse certificate '%s': %s\n"),
fname, gpg_strerror (err));
ksba_cert_release (cert);
continue;
}
err = put_cert (cert, 1, trustclass, NULL);
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
log_info (_("certificate '%s' already cached\n"), fname);
else if (!err)
{
if ((trustclass & CERTTRUST_CLASS_CONFIG))
http_register_cfg_ca (fname);
if (trustclass)
log_info (_("trusted certificate '%s' loaded\n"), fname);
else
log_info (_("certificate '%s' loaded\n"), fname);
if (opt.verbose)
{
p = get_fingerprint_hexstring_colon (cert);
log_info (_(" SHA1 fingerprint = %s\n"), p);
xfree (p);
cert_log_name (_(" issuer ="), cert);
cert_log_subject (_(" subject ="), cert);
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_ENABLED)
log_info ("certificate '%s' skipped due to configuration\n", fname);
else
log_error (_("error loading certificate '%s': %s\n"),
fname, gpg_strerror (err));
ksba_cert_release (cert);
}
xfree (fname);
gnupg_closedir (dir);
return 0;
}
/* Load certificates from FILE. The certificates are expected to be
* PEM encoded so that it is possible to load several certificates.
* TRUSTCLASSES is used to mark the certificates as trusted. The
* cache should be in a locked state when calling this function.
* NO_ERROR repalces an error message when FNAME was not found by an
* information message. */
static gpg_error_t
load_certs_from_file (const char *fname, unsigned int trustclasses,
int no_error)
{
gpg_error_t err;
estream_t fp = NULL;
gnupg_ksba_io_t ioctx = NULL;
ksba_reader_t reader;
ksba_cert_t cert = NULL;
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENONET && no_error)
log_info (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
else
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
err = gnupg_ksba_create_reader (&ioctx,
(GNUPG_KSBA_IO_AUTODETECT
| GNUPG_KSBA_IO_MULTIPEM),
fp, &reader);
if (err)
{
log_error ("can't create reader: %s\n", gpg_strerror (err));
goto leave;
}
/* Loop to read all certificates from the file. */
do
{
ksba_cert_release (cert);
cert = NULL;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_read_der (cert, reader);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
else
log_error (_("can't parse certificate '%s': %s\n"),
fname, gpg_strerror (err));
goto leave;
}
err = put_cert (cert, 1, trustclasses, NULL);
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
log_info (_("certificate '%s' already cached\n"), fname);
else if (gpg_err_code (err) == GPG_ERR_NOT_ENABLED)
log_info ("certificate '%s' skipped due to configuration\n", fname);
else if (err)
log_error (_("error loading certificate '%s': %s\n"),
fname, gpg_strerror (err));
else if (opt.verbose > 1)
{
char *p;
log_info (_("trusted certificate '%s' loaded\n"), fname);
p = get_fingerprint_hexstring_colon (cert);
log_info (_(" SHA1 fingerprint = %s\n"), p);
xfree (p);
cert_log_name (_(" issuer ="), cert);
cert_log_subject (_(" subject ="), cert);
}
ksba_reader_clear (reader, NULL, NULL);
}
while (!gnupg_ksba_reader_eof_seen (ioctx));
leave:
ksba_cert_release (cert);
gnupg_ksba_destroy_reader (ioctx);
es_fclose (fp);
return err;
}
#ifdef HAVE_W32_SYSTEM
/* Load all certificates from the Windows store named STORENAME. All
* certificates are considered to be system provided trusted
* certificates. The cache should be in a locked state when calling
* this function. */
static void
load_certs_from_w32_store (const char *storename)
{
static int init_done;
static CERTOPENSYSTEMSTORE pCertOpenSystemStore;
static CERTENUMCERTIFICATESINSTORE pCertEnumCertificatesInStore;
static CERTCLOSESTORE pCertCloseStore;
gpg_error_t err;
HCERTSTORE w32store;
const CERT_CONTEXT *w32cert;
ksba_cert_t cert = NULL;
unsigned int count = 0;
/* Initialize on the first use. */
if (!init_done)
{
static HANDLE hCrypt32;
init_done = 1;
hCrypt32 = LoadLibrary ("Crypt32.dll");
if (!hCrypt32)
{
log_error ("can't load Crypt32.dll: %s\n", w32_strerror (-1));
return;
}
pCertOpenSystemStore = (CERTOPENSYSTEMSTORE)
(void*)GetProcAddress (hCrypt32, "CertOpenSystemStoreA");
pCertEnumCertificatesInStore = (CERTENUMCERTIFICATESINSTORE)
(void*)GetProcAddress (hCrypt32, "CertEnumCertificatesInStore");
pCertCloseStore = (CERTCLOSESTORE)
(void*)GetProcAddress (hCrypt32, "CertCloseStore");
if ( !pCertOpenSystemStore
|| !pCertEnumCertificatesInStore
|| !pCertCloseStore)
{
log_error ("can't load crypt32.dll: %s\n", "missing function");
pCertOpenSystemStore = NULL;
}
}
if (!pCertOpenSystemStore)
return; /* Not initialized. */
w32store = pCertOpenSystemStore (0, storename);
if (!w32store)
{
log_error ("can't open certificate store '%s': %s\n",
storename, w32_strerror (-1));
return;
}
w32cert = NULL;
while ((w32cert = pCertEnumCertificatesInStore (w32store, w32cert)))
{
if (w32cert->dwCertEncodingType == X509_ASN_ENCODING)
{
ksba_cert_release (cert);
cert = NULL;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert,
w32cert->pbCertEncoded,
w32cert->cbCertEncoded);
if (err)
{
log_error (_("can't parse certificate '%s': %s\n"),
storename, gpg_strerror (err));
break;
}
err = put_cert (cert, 1, CERTTRUST_CLASS_SYSTEM, NULL);
if (!err)
count++;
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
{
if (DBG_X509)
log_debug (_("certificate '%s' already cached\n"), storename);
}
else if (gpg_err_code (err) == GPG_ERR_NOT_ENABLED)
log_info ("certificate '%s' skipped due to configuration\n",
storename);
else if (err)
log_error (_("error loading certificate '%s': %s\n"),
storename, gpg_strerror (err));
else if (opt.verbose > 1)
{
char *p;
log_info (_("trusted certificate '%s' loaded\n"), storename);
p = get_fingerprint_hexstring_colon (cert);
log_info (_(" SHA1 fingerprint = %s\n"), p);
xfree (p);
cert_log_name (_(" issuer ="), cert);
cert_log_subject (_(" subject ="), cert);
}
}
}
ksba_cert_release (cert);
pCertCloseStore (w32store, 0);
if (DBG_X509)
log_debug ("number of certs loaded from store '%s': %u\n",
storename, count);
}
#endif /*HAVE_W32_SYSTEM*/
/* Load the trusted certificates provided by the system. */
static gpg_error_t
load_certs_from_system (void)
{
#ifdef HAVE_W32_SYSTEM
load_certs_from_w32_store ("ROOT");
load_certs_from_w32_store ("CA");
return 0;
#else /*!HAVE_W32_SYSTEM*/
/* A list of certificate bundles to try. */
static struct {
const char *name;
} table[] = {
#ifdef DEFAULT_TRUST_STORE_FILE
{ DEFAULT_TRUST_STORE_FILE }
#else
{ "/etc/ssl/ca-bundle.pem" },
{ "/etc/ssl/certs/ca-certificates.crt" },
{ "/etc/pki/tls/cert.pem" },
{ "/usr/local/share/certs/ca-root-nss.crt" },
{ "/etc/ssl/cert.pem" }
#endif /*!DEFAULT_TRUST_STORE_FILE*/
};
int idx;
gpg_error_t err = 0;
for (idx=0; idx < DIM (table); idx++)
if (!gnupg_access (table[idx].name, F_OK))
{
/* Take the first available bundle. */
err = load_certs_from_file (table[idx].name, CERTTRUST_CLASS_SYSTEM, 0);
break;
}
return err;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Initialize the certificate cache if not yet done. */
void
cert_cache_init (strlist_t hkp_cacerts)
{
char *fname;
strlist_t sl;
if (initialization_done)
return;
init_cache_lock ();
acquire_cache_write_lock ();
load_certs_from_system ();
fname = make_filename_try (gnupg_sysconfdir (), "trusted-certs", NULL);
if (fname)
load_certs_from_dir (fname, CERTTRUST_CLASS_CONFIG);
xfree (fname);
fname = make_filename_try (gnupg_sysconfdir (), "extra-certs", NULL);
if (fname)
load_certs_from_dir (fname, 0);
xfree (fname);
/* Put the special pool certificate into our store. This is
* currently only used with ntbtls. For GnuTLS http_session_new
* unfortunately loads that certificate directly from the file. */
/* Disabled for 2.3.2 because the service had to be shutdown. */
/* fname = make_filename_try (gnupg_datadir (), */
/* "sks-keyservers.netCA.pem", NULL); */
/* if (fname) */
/* load_certs_from_file (fname, CERTTRUST_CLASS_HKPSPOOL, 1); */
/* xfree (fname); */
for (sl = hkp_cacerts; sl; sl = sl->next)
load_certs_from_file (sl->d, CERTTRUST_CLASS_HKP, 0);
initialization_done = 1;
release_cache_lock ();
cert_cache_print_stats (NULL);
}
/* Deinitialize the certificate cache. With FULL set to true even the
unused certificate slots are released. */
void
cert_cache_deinit (int full)
{
cert_item_t ci, ci2;
int i;
if (!initialization_done)
return;
acquire_cache_write_lock ();
for (i=0; i < 256; i++)
for (ci=cert_cache[i]; ci; ci = ci->next)
clean_cache_slot (ci);
if (full)
{
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci2)
{
ci2 = ci->next;
xfree (ci);
}
cert_cache[i] = NULL;
}
}
http_register_cfg_ca (NULL);
total_nonperm_certificates = 0;
any_cert_of_class = 0;
initialization_done = 0;
release_cache_lock ();
}
/* Print some statistics to the log file. */
void
cert_cache_print_stats (ctrl_t ctrl)
{
cert_item_t ci;
int idx;
unsigned int n_nonperm = 0;
unsigned int n_permanent = 0;
unsigned int n_trusted = 0;
unsigned int n_trustclass_system = 0;
unsigned int n_trustclass_config = 0;
unsigned int n_trustclass_hkp = 0;
unsigned int n_trustclass_hkpspool = 0;
acquire_cache_read_lock ();
for (idx = 0; idx < 256; idx++)
for (ci=cert_cache[idx]; ci; ci = ci->next)
if (ci->cert)
{
if (ci->permanent)
n_permanent++;
else
n_nonperm++;
if (ci->trustclasses)
{
n_trusted++;
if ((ci->trustclasses & CERTTRUST_CLASS_SYSTEM))
n_trustclass_system++;
if ((ci->trustclasses & CERTTRUST_CLASS_CONFIG))
n_trustclass_config++;
if ((ci->trustclasses & CERTTRUST_CLASS_HKP))
n_trustclass_hkp++;
if ((ci->trustclasses & CERTTRUST_CLASS_HKPSPOOL))
n_trustclass_hkpspool++;
}
}
release_cache_lock ();
dirmngr_status_helpf (ctrl,
_("permanently loaded certificates: %u\n"),
n_permanent);
dirmngr_status_helpf (ctrl,
_(" runtime cached certificates: %u\n"),
n_nonperm);
dirmngr_status_helpf (ctrl,
_(" trusted certificates: %u (%u,%u,%u,%u)\n"),
n_trusted,
n_trustclass_system,
n_trustclass_config,
n_trustclass_hkp,
n_trustclass_hkpspool);
}
/* Return true if any cert of a class in MASK is permanently
* loaded. */
int
cert_cache_any_in_class (unsigned int mask)
{
return !!(any_cert_of_class & mask);
}
/* Put CERT into the certificate cache. */
gpg_error_t
cache_cert (ksba_cert_t cert)
{
gpg_error_t err;
acquire_cache_write_lock ();
err = put_cert (cert, 0, 0, NULL);
release_cache_lock ();
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
log_info (_("certificate already cached\n"));
else if (!err)
log_info (_("certificate cached\n"));
else if (gpg_err_code (err) == GPG_ERR_NOT_ENABLED)
log_info ("certificate skipped due to configuration\n");
else
log_error (_("error caching certificate: %s\n"), gpg_strerror (err));
return err;
}
/* Put CERT into the certificate cache and store the fingerprint of
the certificate into FPR_BUFFER. If the certificate is already in
the cache do not print a warning; just store the
fingerprint. FPR_BUFFER needs to be at least 20 bytes. */
gpg_error_t
cache_cert_silent (ksba_cert_t cert, void *fpr_buffer)
{
gpg_error_t err;
acquire_cache_write_lock ();
err = put_cert (cert, 0, 0, fpr_buffer);
release_cache_lock ();
if (gpg_err_code (err) == GPG_ERR_DUP_VALUE)
err = 0;
if (gpg_err_code (err) == GPG_ERR_NOT_ENABLED)
log_info ("certificate skipped due to configuration\n");
else if (err)
log_error (_("error caching certificate: %s\n"), gpg_strerror (err));
return err;
}
/* Return a certificate object for the given fingerprint. FPR is
expected to be a 20 byte binary SHA-1 fingerprint. If no matching
certificate is available in the cache NULL is returned. The caller
must release a returned certificate. Note that although we are
using reference counting the caller should not just compare the
pointers to check for identical certificates. */
ksba_cert_t
get_cert_byfpr (const unsigned char *fpr)
{
cert_item_t ci;
acquire_cache_read_lock ();
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (ci->cert && !memcmp (ci->fpr, fpr, 20))
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
release_cache_lock ();
return NULL;
}
/* Return a certificate object for the given fingerprint. STRING is
expected to be a SHA-1 fingerprint in standard hex notation with or
without colons. If no matching certificate is available in the
cache NULL is returned. The caller must release a returned
certificate. Note that although we are using reference counting
the caller should not just compare the pointers to check for
identical certificates. */
ksba_cert_t
get_cert_byhexfpr (const char *string)
{
unsigned char fpr[20];
const char *s;
int i;
if (strchr (string, ':'))
{
for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1);)
{
if (s[2] && s[2] != ':')
break; /* Invalid string. */
fpr[i++] = xtoi_2 (s);
s += 2;
if (i!= 20 && *s == ':')
s++;
}
}
else
{
for (s=string,i=0; i < 20 && hexdigitp (s) && hexdigitp(s+1); s+=2 )
fpr[i++] = xtoi_2 (s);
}
if (i!=20 || *s)
{
log_error (_("invalid SHA1 fingerprint string '%s'\n"), string);
return NULL;
}
return get_cert_byfpr (fpr);
}
/* Return the certificate matching ISSUER_DN and SERIALNO. */
ksba_cert_t
get_cert_bysn (const char *issuer_dn, ksba_sexp_t serialno)
{
/* Simple and inefficient implementation. fixme! */
cert_item_t ci;
int i;
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn)
&& !compare_serialno (ci->sn, serialno))
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
}
release_cache_lock ();
return NULL;
}
/* Return the certificate matching ISSUER_DN. SEQ should initially be
set to 0 and bumped up to get the next issuer with that DN. */
ksba_cert_t
get_cert_byissuer (const char *issuer_dn, unsigned int seq)
{
/* Simple and very inefficient implementation and API. fixme! */
cert_item_t ci;
int i;
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && !strcmp (ci->issuer_dn, issuer_dn))
if (!seq--)
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
}
release_cache_lock ();
return NULL;
}
/* Return the certificate matching SUBJECT_DN. SEQ should initially be
set to 0 and bumped up to get the next subject with that DN. */
ksba_cert_t
get_cert_bysubject (const char *subject_dn, unsigned int seq)
{
/* Simple and very inefficient implementation and API. fixme! */
cert_item_t ci;
int i;
if (!subject_dn)
return NULL;
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
{
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && ci->subject_dn
&& !strcmp (ci->subject_dn, subject_dn))
if (!seq--)
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
return ci->cert;
}
}
release_cache_lock ();
return NULL;
}
/* Return a value describing the class of PATTERN. The offset of
the actual string to be used for the comparison is stored at
R_OFFSET. The offset of the serialnumer is stored at R_SN_OFFSET. */
static enum pattern_class
classify_pattern (const char *pattern, size_t *r_offset, size_t *r_sn_offset)
{
enum pattern_class result;
const char *s;
int hexprefix = 0;
int hexlength;
*r_offset = *r_sn_offset = 0;
/* Skip leading spaces. */
for(s = pattern; *s && spacep (s); s++ )
;
switch (*s)
{
case 0: /* Empty string is an error. */
result = PATTERN_UNKNOWN;
break;
case '.': /* An email address, compare from end. */
result = PATTERN_UNKNOWN; /* Not implemented. */
break;
case '<': /* An email address. */
result = PATTERN_EMAIL;
s++;
break;
case '@': /* Part of an email address. */
result = PATTERN_EMAIL_SUBSTR;
s++;
break;
case '=': /* Exact compare. */
result = PATTERN_UNKNOWN; /* Does not make sense for X.509. */
break;
case '*': /* Case insensitive substring search. */
result = PATTERN_SUBSTR;
s++;
break;
case '+': /* Compare individual words. */
result = PATTERN_UNKNOWN; /* Not implemented. */
break;
case '/': /* Subject's DN. */
s++;
if (!*s || spacep (s))
result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */
else
result = PATTERN_SUBJECT;
break;
case '#': /* Serial number or issuer DN. */
{
const char *si;
s++;
if ( *s == '/')
{
/* An issuer's DN is indicated by "#/" */
s++;
if (!*s || spacep (s))
result = PATTERN_UNKNOWN; /* No DN or prefixed with a space. */
else
result = PATTERN_ISSUER;
}
else
{ /* Serialnumber + optional issuer ID. */
for (si=s; *si && *si != '/'; si++)
if (!strchr("01234567890abcdefABCDEF", *si))
break;
if (*si && *si != '/')
result = PATTERN_UNKNOWN; /* Invalid digit in serial number. */
else
{
*r_sn_offset = s - pattern;
if (!*si)
result = PATTERN_SERIALNO;
else
{
s = si+1;
if (!*s || spacep (s))
result = PATTERN_UNKNOWN; /* No DN or prefixed
with a space. */
else
result = PATTERN_SERIALNO_ISSUER;
}
}
}
}
break;
case ':': /* Unified fingerprint. */
{
const char *se, *si;
int i;
se = strchr (++s, ':');
if (!se)
result = PATTERN_UNKNOWN;
else
{
for (i=0, si=s; si < se; si++, i++ )
if (!strchr("01234567890abcdefABCDEF", *si))
break;
if ( si < se )
result = PATTERN_UNKNOWN; /* Invalid digit. */
else if (i == 32)
result = PATTERN_FINGERPRINT16;
else if (i == 40)
result = PATTERN_FINGERPRINT20;
else
result = PATTERN_UNKNOWN; /* Invalid length for a fingerprint. */
}
}
break;
case '&': /* Keygrip. */
result = PATTERN_UNKNOWN; /* Not implemented. */
break;
default:
if (s[0] == '0' && s[1] == 'x')
{
hexprefix = 1;
s += 2;
}
hexlength = strspn(s, "0123456789abcdefABCDEF");
/* Check if a hexadecimal number is terminated by EOS or blank. */
if (hexlength && s[hexlength] && !spacep (s+hexlength))
{
/* If the "0x" prefix is used a correct termination is required. */
if (hexprefix)
{
result = PATTERN_UNKNOWN;
break; /* switch */
}
hexlength = 0; /* Not a hex number. */
}
if (hexlength == 8 || (!hexprefix && hexlength == 9 && *s == '0'))
{
if (hexlength == 9)
s++;
result = PATTERN_SHORT_KEYID;
}
else if (hexlength == 16 || (!hexprefix && hexlength == 17 && *s == '0'))
{
if (hexlength == 17)
s++;
result = PATTERN_LONG_KEYID;
}
else if (hexlength == 32 || (!hexprefix && hexlength == 33 && *s == '0'))
{
if (hexlength == 33)
s++;
result = PATTERN_FINGERPRINT16;
}
else if (hexlength == 40 || (!hexprefix && hexlength == 41 && *s == '0'))
{
if (hexlength == 41)
s++;
result = PATTERN_FINGERPRINT20;
}
else if (!hexprefix)
{
/* The fingerprints used with X.509 are often delimited by
colons, so we try to single this case out. */
result = PATTERN_UNKNOWN;
hexlength = strspn (s, ":0123456789abcdefABCDEF");
if (hexlength == 59 && (!s[hexlength] || spacep (s+hexlength)))
{
int i, c;
for (i=0; i < 20; i++, s += 3)
{
c = hextobyte(s);
if (c == -1 || (i < 19 && s[2] != ':'))
break;
}
if (i == 20)
result = PATTERN_FINGERPRINT20;
}
if (result == PATTERN_UNKNOWN) /* Default to substring match. */
{
result = PATTERN_SUBSTR;
}
}
else /* A hex number with a prefix but with a wrong length. */
result = PATTERN_UNKNOWN;
}
if (result != PATTERN_UNKNOWN)
*r_offset = s - pattern;
return result;
}
/* Given PATTERN, which is a string as used by GnuPG to specify a
certificate, return all matching certificates by calling the
supplied function RETFNC. */
gpg_error_t
get_certs_bypattern (const char *pattern,
gpg_error_t (*retfnc)(void*,ksba_cert_t),
void *retfnc_data)
{
gpg_error_t err = GPG_ERR_BUG;
enum pattern_class class;
size_t offset, sn_offset;
const char *hexserialno;
ksba_sexp_t serialno = NULL;
ksba_cert_t cert = NULL;
unsigned int seq;
if (!pattern || !retfnc)
return gpg_error (GPG_ERR_INV_ARG);
class = classify_pattern (pattern, &offset, &sn_offset);
hexserialno = pattern + sn_offset;
pattern += offset;
switch (class)
{
case PATTERN_UNKNOWN:
err = gpg_error (GPG_ERR_INV_NAME);
break;
case PATTERN_FINGERPRINT20:
cert = get_cert_byhexfpr (pattern);
err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND);
break;
case PATTERN_SERIALNO_ISSUER:
serialno = hexsn_to_sexp (hexserialno);
if (!serialno)
err = gpg_error_from_syserror ();
else
{
cert = get_cert_bysn (pattern, serialno);
err = cert? 0 : gpg_error (GPG_ERR_NOT_FOUND);
}
break;
case PATTERN_ISSUER:
for (seq=0,err=0; !err && (cert = get_cert_byissuer (pattern, seq)); seq++)
{
err = retfnc (retfnc_data, cert);
ksba_cert_release (cert);
cert = NULL;
}
if (!err && !seq)
err = gpg_error (GPG_ERR_NOT_FOUND);
break;
case PATTERN_SUBJECT:
for (seq=0,err=0; !err && (cert = get_cert_bysubject (pattern, seq));seq++)
{
err = retfnc (retfnc_data, cert);
ksba_cert_release (cert);
cert = NULL;
}
if (!err && !seq)
err = gpg_error (GPG_ERR_NOT_FOUND);
break;
case PATTERN_EMAIL:
case PATTERN_EMAIL_SUBSTR:
case PATTERN_FINGERPRINT16:
case PATTERN_SHORT_KEYID:
case PATTERN_LONG_KEYID:
case PATTERN_SUBSTR:
case PATTERN_SERIALNO:
/* Not supported. */
err = gpg_error (GPG_ERR_INV_NAME);
}
if (!err && cert)
err = retfnc (retfnc_data, cert);
ksba_cert_release (cert);
xfree (serialno);
return err;
}
/* Return the certificate matching ISSUER_DN and SERIALNO; if it is
* not already in the cache, try to find it from other resources. */
ksba_cert_t
find_cert_bysn (ctrl_t ctrl, const char *issuer_dn, ksba_sexp_t serialno)
{
gpg_error_t err;
ksba_cert_t cert;
cert_fetch_context_t context = NULL;
char *hexsn, *buf;
/* First check whether it has already been cached. */
cert = get_cert_bysn (issuer_dn, serialno);
if (cert)
return cert;
/* Ask back to the service requester to return the certificate.
* This is because we can assume that he already used the
* certificate while checking for the CRL. */
hexsn = serial_hex (serialno);
if (!hexsn)
{
log_error ("serial_hex() failed\n");
return NULL;
}
buf = strconcat ("#", hexsn, "/", issuer_dn, NULL);
if (!buf)
{
log_error ("can't allocate enough memory: %s\n", strerror (errno));
xfree (hexsn);
return NULL;
}
xfree (hexsn);
cert = get_cert_local (ctrl, buf);
xfree (buf);
if (cert)
{
cache_cert (cert);
return cert; /* Done. */
}
if (DBG_LOOKUP)
log_debug ("find_cert_bysn: certificate not returned by caller"
" - doing lookup\n");
/* Retrieve the certificate from external resources. */
while (!cert)
{
ksba_sexp_t sn;
char *issdn;
if (!context)
{
err = ca_cert_fetch (ctrl, &context, issuer_dn);
if (err)
{
log_error (_("error fetching certificate by S/N: %s\n"),
gpg_strerror (err));
break;
}
}
err = fetch_next_ksba_cert (context, &cert);
if (err)
{
log_error (_("error fetching certificate by S/N: %s\n"),
gpg_strerror (err) );
break;
}
issdn = ksba_cert_get_issuer (cert, 0);
if (strcmp (issuer_dn, issdn))
{
log_debug ("find_cert_bysn: Ooops: issuer DN does not match\n");
ksba_cert_release (cert);
cert = NULL;
ksba_free (issdn);
break;
}
sn = ksba_cert_get_serial (cert);
if (DBG_LOOKUP)
{
log_debug (" considering certificate (#");
dump_serial (sn);
log_printf ("/");
dump_string (issdn);
log_printf (")\n");
}
if (!compare_serialno (serialno, sn))
{
ksba_free (sn);
ksba_free (issdn);
cache_cert (cert);
if (DBG_LOOKUP)
log_debug (" found\n");
break; /* Ready. */
}
ksba_free (sn);
ksba_free (issdn);
ksba_cert_release (cert);
cert = NULL;
}
end_cert_fetch (context);
return cert;
}
/* Return the certificate matching SUBJECT_DN and (if not NULL)
* KEYID. If it is not already in the cache, try to find it from other
* resources. Note, that the external search does not work for user
* certificates because the LDAP lookup is on the caCertificate
* attribute. For our purposes this is just fine. */
ksba_cert_t
find_cert_bysubject (ctrl_t ctrl, const char *subject_dn, ksba_sexp_t keyid)
{
gpg_error_t err;
int seq;
ksba_cert_t cert = NULL;
ksba_cert_t first; /* The first certificate found. */
cert_fetch_context_t context = NULL;
ksba_sexp_t subj;
/* If we have certificates from an OCSP request we first try to use
* them. This is because these certificates will really be the
* required ones and thus even in the case that they can't be
* uniquely located by the following code we can use them. This is
* for example required by Telesec certificates where a keyId is
* used but the issuer certificate comes without a subject keyId! */
if (ctrl->ocsp_certs && subject_dn)
{
cert_item_t ci;
cert_ref_t cr;
int i;
/* For efficiency reasons we won't use get_cert_bysubject here. */
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && ci->subject_dn
&& !strcmp (ci->subject_dn, subject_dn))
for (cr=ctrl->ocsp_certs; cr; cr = cr->next)
if (!memcmp (ci->fpr, cr->fpr, 20))
{
ksba_cert_ref (ci->cert);
release_cache_lock ();
if (DBG_LOOKUP)
log_debug ("%s: certificate found in the cache"
" via ocsp_certs\n", __func__);
return ci->cert; /* We use this certificate. */
}
release_cache_lock ();
if (DBG_LOOKUP)
log_debug ("find_cert_bysubject: certificate not in ocsp_certs\n");
}
/* Now check whether the certificate is cached. */
first = NULL;
subj = NULL;
for (seq=0; (cert = get_cert_bysubject (subject_dn, seq)); seq++)
{
if (!keyid
|| (!ksba_cert_get_subj_key_id (cert, NULL, &subj)
&& !cmp_simple_canon_sexp (keyid, subj)))
{
xfree (subj);
subj = NULL;
if (DBG_LOOKUP)
log_debug ("%s: certificate found in the cache"
" %sby subject DN\n", __func__, !keyid?"only ":"");
/* If this a trusted cert - then prefer it. */
if (!is_trusted_cert (cert, (CERTTRUST_CLASS_SYSTEM
| CERTTRUST_CLASS_CONFIG)))
{
ksba_cert_release (first);
first = cert;
cert = NULL;
/* We stop at the first trusted certificate and ignore
* any yet found non-trusted certificates. */
break;
}
else if (!first)
{
/* Not trusted. Save only the first one but continue
* the loop in case there is also a trusted one. */
ksba_cert_release (first);
first = cert;
cert = NULL;
}
}
xfree (subj);
subj = NULL;
ksba_cert_release (cert);
}
if (first)
return first; /* Return the first found certificate. */
/* If we do not have a subject DN but have a keyid, try to locate it
* by keyid. */
if (!subject_dn && keyid)
{
int i;
cert_item_t ci;
ksba_sexp_t ski;
acquire_cache_read_lock ();
for (i=0; i < 256; i++)
for (ci=cert_cache[i]; ci; ci = ci->next)
if (ci->cert && !ksba_cert_get_subj_key_id (ci->cert, NULL, &ski))
{
if (!cmp_simple_canon_sexp (keyid, ski))
{
ksba_free (ski);
ksba_cert_ref (ci->cert);
release_cache_lock ();
if (DBG_LOOKUP)
log_debug ("%s: certificate found in the cache"
" via ski\n", __func__);
return ci->cert;
}
ksba_free (ski);
}
release_cache_lock ();
}
if (DBG_LOOKUP)
log_debug ("find_cert_bysubject: certificate not in cache\n");
/* Ask back to the service requester to return the certificate.
* This is because we can assume that he already used the
* certificate while checking for the CRL. */
if (keyid)
cert = get_cert_local_ski (ctrl, subject_dn, keyid);
else
{
/* In contrast to get_cert_local_ski, get_cert_local uses any
* passed pattern, so we need to make sure that an exact subject
* search is done. */
char *buf;
buf = strconcat ("/", subject_dn, NULL);
if (!buf)
{
log_error ("can't allocate enough memory: %s\n", strerror (errno));
return NULL;
}
cert = get_cert_local (ctrl, buf);
xfree (buf);
}
if (cert)
{
cache_cert (cert);
return cert; /* Done. */
}
if (DBG_LOOKUP)
log_debug ("find_cert_bysubject: certificate not returned by caller"
" - doing lookup\n");
/* Locate the certificate using external resources. */
while (!cert)
{
char *subjdn;
if (!context)
{
err = ca_cert_fetch (ctrl, &context, subject_dn);
if (err)
{
log_error (_("error fetching certificate by subject: %s\n"),
gpg_strerror (err));
break;
}
}
err = fetch_next_ksba_cert (context, &cert);
if (err)
{
log_error (_("error fetching certificate by subject: %s\n"),
gpg_strerror (err) );
break;
}
subjdn = ksba_cert_get_subject (cert, 0);
if (strcmp (subject_dn, subjdn))
{
log_info ("find_cert_bysubject: subject DN does not match\n");
ksba_cert_release (cert);
cert = NULL;
ksba_free (subjdn);
continue;
}
if (DBG_LOOKUP)
{
log_debug (" considering certificate (/");
dump_string (subjdn);
log_printf (")\n");
}
ksba_free (subjdn);
/* If no key ID has been provided, we return the first match. */
if (!keyid)
{
cache_cert (cert);
if (DBG_LOOKUP)
log_debug (" found\n");
break; /* Ready. */
}
/* With the key ID given we need to compare it. */
if (!ksba_cert_get_subj_key_id (cert, NULL, &subj))
{
if (!cmp_simple_canon_sexp (keyid, subj))
{
ksba_free (subj);
cache_cert (cert);
if (DBG_LOOKUP)
log_debug (" found\n");
break; /* Ready. */
}
}
ksba_free (subj);
ksba_cert_release (cert);
cert = NULL;
}
end_cert_fetch (context);
return cert;
}
/* Return 0 if the certificate is a trusted certificate. Returns
* GPG_ERR_NOT_TRUSTED if it is not trusted or other error codes in
* case of systems errors. TRUSTCLASSES are the bitwise ORed
* CERTTRUST_CLASS values to use for the check. */
gpg_error_t
is_trusted_cert (ksba_cert_t cert, unsigned int trustclasses)
{
unsigned char fpr[20];
cert_item_t ci;
cert_compute_fpr (cert, fpr);
acquire_cache_read_lock ();
for (ci=cert_cache[*fpr]; ci; ci = ci->next)
if (ci->cert && !memcmp (ci->fpr, fpr, 20))
{
if ((ci->trustclasses & trustclasses))
{
/* The certificate is trusted in one of the given
* TRUSTCLASSES. */
release_cache_lock ();
return 0; /* Yes, it is trusted. */
}
break;
}
release_cache_lock ();
return gpg_error (GPG_ERR_NOT_TRUSTED);
}
/* Given the certificate CERT locate the issuer for this certificate
* and return it at R_CERT. Returns 0 on success or
* GPG_ERR_NOT_FOUND. */
gpg_error_t
find_issuing_cert (ctrl_t ctrl, ksba_cert_t cert, ksba_cert_t *r_cert)
{
gpg_error_t err;
char *issuer_dn;
ksba_cert_t issuer_cert = NULL;
ksba_name_t authid;
ksba_sexp_t authidno;
ksba_sexp_t keyid;
*r_cert = NULL;
issuer_dn = ksba_cert_get_issuer (cert, 0);
if (!issuer_dn)
{
log_error (_("no issuer found in certificate\n"));
err = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
/* First we need to check whether we can return that certificate
using the authorithyKeyIdentifier. */
err = ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno);
if (err)
{
log_info (_("error getting authorityKeyIdentifier: %s\n"),
gpg_strerror (err));
}
else
{
const char *s = ksba_name_enum (authid, 0);
if (s && *authidno)
{
issuer_cert = find_cert_bysn (ctrl, s, authidno);
}
if (!issuer_cert && keyid)
{
/* Not found by issuer+s/n. Now that we have an AKI
* keyIdentifier look for a certificate with a matching
* SKI. */
issuer_cert = find_cert_bysubject (ctrl, issuer_dn, keyid);
}
/* Print a note so that the user does not feel too helpless when
* an issuer certificate was found and gpgsm prints BAD
* signature because it is not the correct one. */
if (!issuer_cert)
{
log_info ("issuer certificate ");
if (keyid)
{
log_printf ("{");
dump_serial (keyid);
log_printf ("} ");
}
if (authidno)
{
log_printf ("(#");
dump_serial (authidno);
log_printf ("/");
dump_string (s);
log_printf (") ");
}
log_printf ("not found using authorityKeyIdentifier\n");
}
ksba_name_release (authid);
xfree (authidno);
xfree (keyid);
}
/* If this did not work, try just with the issuer's name and assume
* that there is only one such certificate. We only look into our
* cache then. */
if (err || !issuer_cert)
{
issuer_cert = get_cert_bysubject (issuer_dn, 0);
if (issuer_cert)
err = 0;
}
leave:
if (!err && !issuer_cert)
err = gpg_error (GPG_ERR_NOT_FOUND);
xfree (issuer_dn);
if (err)
ksba_cert_release (issuer_cert);
else
*r_cert = issuer_cert;
return err;
}
/* Read a list of certificates in PEM format from stream FP and store
* them on success at R_CERTLIST. On error NULL is stored at R_CERT
* list and an error code returned. Note that even on success an
* empty list of certificates can be returned (i.e. NULL stored at
* R_CERTLIST) iff the input stream has no certificates. */
gpg_error_t
read_certlist_from_stream (certlist_t *r_certlist, estream_t fp)
{
gpg_error_t err;
gnupg_ksba_io_t ioctx = NULL;
ksba_reader_t reader;
ksba_cert_t cert = NULL;
certlist_t certlist = NULL;
certlist_t cl, *cltail;
*r_certlist = NULL;
err = gnupg_ksba_create_reader (&ioctx,
(GNUPG_KSBA_IO_PEM | GNUPG_KSBA_IO_MULTIPEM),
fp, &reader);
if (err)
goto leave;
/* Loop to read all certificates from the stream. */
cltail = &certlist;
do
{
ksba_cert_release (cert);
cert = NULL;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_read_der (cert, reader);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
goto leave;
}
/* Append the certificate to the list. We also store the
* fingerprint and check whether we have a cached certificate;
* in that case the cached certificate is put into the list to
* take advantage of a validation result which might be stored
* in the cached certificate. */
cl = xtrycalloc (1, sizeof *cl);
if (!cl)
{
err = gpg_error_from_syserror ();
goto leave;
}
cert_compute_fpr (cert, cl->fpr);
cl->cert = get_cert_byfpr (cl->fpr);
if (!cl->cert)
{
cl->cert = cert;
cert = NULL;
}
*cltail = cl;
cltail = &cl->next;
ksba_reader_clear (reader, NULL, NULL);
}
while (!gnupg_ksba_reader_eof_seen (ioctx));
leave:
ksba_cert_release (cert);
gnupg_ksba_destroy_reader (ioctx);
if (err)
release_certlist (certlist);
else
*r_certlist = certlist;
return err;
}
/* Release the certificate list CL. */
void
release_certlist (certlist_t cl)
{
while (cl)
{
certlist_t next = cl->next;
ksba_cert_release (cl->cert);
cl = next;
}
}
diff --git a/dirmngr/dirmngr.c b/dirmngr/dirmngr.c
index d58a27372..5a2a45bc1 100644
--- a/dirmngr/dirmngr.c
+++ b/dirmngr/dirmngr.c
@@ -1,2590 +1,2590 @@
/* dirmngr.c - Keyserver and X.509 LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003-2004, 2006-2007, 2008, 2010-2011, 2020 g10 Code GmbH
* 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 <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif
#include <sys/stat.h>
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#ifdef HAVE_INOTIFY_INIT
# include <sys/inotify.h>
#endif /*HAVE_INOTIFY_INIT*/
#include <npth.h>
#include "dirmngr-err.h"
#if HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h>
#endif /*HTTP_USE_GNUTLS*/
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "dirmngr.h"
#include <assuan.h>
#include "certcache.h"
#include "crlcache.h"
#include "crlfetch.h"
#include "misc.h"
#if USE_LDAP
# include "ldapserver.h"
#endif
#include "../common/asshelp.h"
#if USE_LDAP
# include "ldap-wrapper.h"
#endif
#include "../common/comopt.h"
#include "../common/init.h"
#include "../common/gc-opt-flags.h"
#include "dns-stuff.h"
#include "http-common.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values {
aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aServer,
aDaemon,
aSupervised,
aListCRLs,
aLoadCRL,
aFetchCRL,
aShutdown,
aFlush,
aGPGConfList,
aGPGConfTest,
aGPGConfVersions,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oDebugLevel,
oGnutlsDebug,
oDebugCacheExpiredCerts,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oLogFile,
oBatch,
oDisableHTTP,
oDisableLDAP,
oDisableIPv4,
oDisableIPv6,
oIgnoreLDAPDP,
oIgnoreHTTPDP,
oIgnoreOCSPSvcUrl,
oHonorHTTPProxy,
oHTTPProxy,
oLDAPProxy,
oOnlyLDAPProxy,
oLDAPServer,
oLDAPFile,
oLDAPTimeout,
oLDAPAddServers,
oOCSPResponder,
oOCSPSigner,
oOCSPMaxClockSkew,
oOCSPMaxPeriod,
oOCSPCurrentPeriod,
oMaxReplies,
oHkpCaCert,
oFakedSystemTime,
oForce,
oAllowOCSP,
oAllowVersionCheck,
oStealSocket,
oSocketName,
oLDAPWrapperProgram,
oHTTPWrapperProgram,
oIgnoreCert,
oIgnoreCertExtension,
oIgnoreCRLExtension,
oUseTor,
oNoUseTor,
oKeyServer,
oNameServer,
oDisableCheckOwnSocket,
oStandardResolver,
oRecursiveResolver,
oResolverTimeout,
oConnectTimeout,
oConnectQuickTimeout,
oListenBacklog,
oFakeCRL,
oCompatibilityFlags,
aTest
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aGPGConfVersions, "gpgconf-versions", "@"),
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aServer, "server", N_("run in server mode (foreground)") ),
ARGPARSE_c (aDaemon, "daemon", N_("run in daemon mode (background)") ),
#ifndef HAVE_W32_SYSTEM
ARGPARSE_c (aSupervised, "supervised", "@"),
#endif
ARGPARSE_c (aListCRLs, "list-crls", N_("list the contents of the CRL cache")),
ARGPARSE_c (aLoadCRL, "load-crl", N_("|FILE|load CRL from FILE into cache")),
ARGPARSE_c (aFetchCRL, "fetch-crl", N_("|URL|fetch a CRL from URL")),
ARGPARSE_c (aShutdown, "shutdown", N_("shutdown the dirmngr")),
ARGPARSE_c (aFlush, "flush", N_("flush the cache")),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_n (oStealSocket, "steal-socket", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oGnutlsDebug, "gnutls-debug", "@"),
ARGPARSE_s_i (oGnutlsDebug, "tls-debug", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_n (oAllowVersionCheck, "allow-version-check",
N_("allow online software version check")),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_s_i (oMaxReplies, "max-replies",
N_("|N|do not return more than N items in one query")),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oIgnoreCert,"ignore-cert", "@"),
ARGPARSE_s_s (oIgnoreCertExtension,"ignore-cert-extension", "@"),
ARGPARSE_s_s (oIgnoreCRLExtension,"ignore-crl-extension", "@"),
ARGPARSE_header ("Network", N_("Network related options")),
ARGPARSE_s_n (oUseTor, "use-tor", N_("route all network traffic via Tor")),
ARGPARSE_s_n (oNoUseTor, "no-use-tor", "@"),
ARGPARSE_s_n (oDisableIPv4, "disable-ipv4", "@"),
ARGPARSE_s_n (oDisableIPv6, "disable-ipv6", "@"),
ARGPARSE_s_n (oStandardResolver, "standard-resolver", "@"),
ARGPARSE_s_n (oRecursiveResolver, "recursive-resolver", "@"),
ARGPARSE_s_i (oResolverTimeout, "resolver-timeout", "@"),
ARGPARSE_s_s (oNameServer, "nameserver", "@"),
ARGPARSE_s_i (oConnectTimeout, "connect-timeout", "@"),
ARGPARSE_s_i (oConnectQuickTimeout, "connect-quick-timeout", "@"),
ARGPARSE_header ("HTTP", N_("Configuration for HTTP servers")),
ARGPARSE_s_n (oDisableHTTP, "disable-http", N_("inhibit the use of HTTP")),
ARGPARSE_s_n (oIgnoreHTTPDP,"ignore-http-dp",
N_("ignore HTTP CRL distribution points")),
ARGPARSE_s_s (oHTTPProxy, "http-proxy",
N_("|URL|redirect all HTTP requests to URL")),
ARGPARSE_s_n (oHonorHTTPProxy, "honor-http-proxy",
N_("use system's HTTP proxy setting")),
ARGPARSE_s_s (oLDAPWrapperProgram, "ldap-wrapper-program", "@"),
ARGPARSE_header ("Keyserver", N_("Configuration for OpenPGP servers")),
ARGPARSE_s_s (oKeyServer, "keyserver",
N_("|URL|use keyserver at URL")),
ARGPARSE_s_s (oHkpCaCert, "hkp-cacert",
N_("|FILE|use the CA certificates in FILE for HKP over TLS")),
ARGPARSE_header ("LDAP", N_("Configuration for X.509 servers")),
ARGPARSE_s_n (oDisableLDAP, "disable-ldap", N_("inhibit the use of LDAP")),
ARGPARSE_s_n (oIgnoreLDAPDP,"ignore-ldap-dp",
N_("ignore LDAP CRL distribution points")),
ARGPARSE_s_s (oLDAPProxy, "ldap-proxy",
N_("|HOST|use HOST for LDAP queries")),
ARGPARSE_s_n (oOnlyLDAPProxy, "only-ldap-proxy",
N_("do not use fallback hosts with --ldap-proxy")),
ARGPARSE_s_s (oLDAPServer, "ldapserver",
N_("|SPEC|use this keyserver to lookup keys")),
ARGPARSE_s_s (oLDAPFile, "ldapserverlist-file",
N_("|FILE|read LDAP server list from FILE")),
ARGPARSE_s_n (oLDAPAddServers, "add-servers",
N_("add new servers discovered in CRL distribution"
" points to serverlist")),
ARGPARSE_s_i (oLDAPTimeout, "ldaptimeout",
N_("|N|set LDAP timeout to N seconds")),
ARGPARSE_s_s (oFakeCRL, "fake-crl", "@"),
ARGPARSE_header ("OCSP", N_("Configuration for OCSP")),
ARGPARSE_s_n (oAllowOCSP, "allow-ocsp", N_("allow sending OCSP requests")),
ARGPARSE_s_n (oIgnoreOCSPSvcUrl, "ignore-ocsp-service-url",
N_("ignore certificate contained OCSP service URLs")),
ARGPARSE_s_s (oOCSPResponder, "ocsp-responder",
N_("|URL|use OCSP responder at URL")),
ARGPARSE_s_s (oOCSPSigner, "ocsp-signer",
N_("|FPR|OCSP response signed by FPR")),
ARGPARSE_s_i (oOCSPMaxClockSkew, "ocsp-max-clock-skew", "@"),
ARGPARSE_s_i (oOCSPMaxPeriod, "ocsp-max-period", "@"),
ARGPARSE_s_i (oOCSPCurrentPeriod, "ocsp-current-period", "@"),
ARGPARSE_header (NULL, N_("Other options")),
ARGPARSE_s_n (oForce, "force", N_("force loading of outdated CRLs")),
ARGPARSE_s_s (oSocketName, "socket-name", "@"), /* Only for debugging. */
ARGPARSE_s_n (oDebugCacheExpiredCerts, "debug-cache-expired-certs", "@"),
ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"),
ARGPARSE_header (NULL, ""), /* Stop the header group. */
/* Not yet used options. */
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_s (oHTTPWrapperProgram, "http-wrapper-program", "@"),
ARGPARSE_group (302,N_("@\n(See the \"info\" manual for a complete listing "
"of all commands and options)\n")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_DNS_VALUE , "dns" },
{ DBG_NETWORK_VALUE, "network" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ DBG_KEEPTMP_VALUE, "keeptmp" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
/* The list of compatibility flags. */
static struct compatibility_flags_s compatibility_flags [] =
{
{ COMPAT_RESTRICT_HTTP_REDIR, "restrict-http-redir" },
{ 0, NULL }
};
#define DEFAULT_MAX_REPLIES 10
#define DEFAULT_LDAP_TIMEOUT 15 /* seconds */
#define DEFAULT_CONNECT_TIMEOUT (15*1000) /* 15 seconds */
#define DEFAULT_CONNECT_QUICK_TIMEOUT ( 2*1000) /* 2 seconds */
/* For the cleanup handler we need to keep track of the socket's name. */
static const char *socket_name;
/* If the socket has been redirected, this is the name of the
redirected socket.. */
static const char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument.
* Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
/* Only if this flag has been set will we remove the socket file. */
static int cleanup_socket;
/* Keep track of the current log file so that we can avoid updating
the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* Helper to implement --debug-level. */
static const char *debug_level;
/* Helper to set the NTBTLS or GNUTLS log level. */
static int opt_gnutls_debug = -1;
/* Flag indicating that a shutdown has been requested. */
static volatile int shutdown_pending;
/* Flags to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag indicating to start the daemon even if one already runs. */
static int steal_socket;
/* Flag to control the Tor mode. */
static enum
{ TOR_MODE_AUTO = 0, /* Switch to NO or YES */
TOR_MODE_NEVER, /* Never use Tor. */
TOR_MODE_NO, /* Do not use Tor */
TOR_MODE_YES, /* Use Tor */
TOR_MODE_FORCE /* Force using Tor */
} tor_mode;
/* Flag indicating that we are in supervised mode. */
static int is_supervised;
/* Counter for the active connections. */
static int active_connections;
/* This flag is set by any network access and used by the housekeeping
* thread to run background network tasks. */
static int network_activity_seen;
/* A list of filenames registered with --hkp-cacert. */
static strlist_t hkp_cacert_filenames;
/* A flag used to clear the list of ldapservers iff --ldapserver is
* given on the command line or one of the conf files. In this case we
* want to clear all old specifications through the legacy
* dirmngr_ldapservers.conf. */
static int ldapserver_list_needs_reset;
/* The timer tick used for housekeeping stuff. The second constant is used when a shutdown is pending. */
#define TIMERTICK_INTERVAL (60)
#define TIMERTICK_INTERVAL_SHUTDOWN (4)
/* How oft to run the housekeeping. */
#define HOUSEKEEPING_INTERVAL (600)
/* This union is used to avoid compiler warnings in case a pointer is
64 bit and an int 32 bit. We store an integer in a pointer and get
it back later (npth_getspecific et al.). */
union int_and_ptr_u
{
int aint;
assuan_fd_t afd;
void *aptr;
};
/* The key used to store the current file descriptor in the thread
local storage. We use this in conjunction with the
log_set_pid_suffix_cb feature. */
#ifndef HAVE_W32_SYSTEM
static npth_key_t my_tlskey_current_fd;
#endif
/* Prototypes. */
static void cleanup (void);
#if USE_LDAP
static ldap_server_t parse_ldapserver_file (const char* filename, int ienoent);
#endif /*USE_LDAP*/
static fingerprint_list_t parse_fingerprint_item (const char *string,
const char *optionname,
int want_binary);
static void netactivity_action (void);
static void handle_connections (assuan_fd_t listen_fd);
static void gpgconf_versions (void);
static const char *
my_strusage( int level )
{
const char *p;
switch ( level )
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@DIRMNGR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 49: p = PACKAGE_BUGREPORT; break;
case 1:
case 40: p = _("Usage: @DIRMNGR@ [options] (-h for help)");
break;
case 41: p = _("Syntax: @DIRMNGR@ [options] [command [args]]\n"
"Keyserver, CRL, and OCSP access for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Callback from libksba to hash a provided buffer. Our current
implementation does only allow SHA-1 for hashing. This may be
extended by mapping the name, testing for algorithm availability
and adjust the length checks accordingly. */
static gpg_error_t
my_ksba_hash_buffer (void *arg, const char *oid,
const void *buffer, size_t length, size_t resultsize,
unsigned char *result, size_t *resultlen)
{
(void)arg;
if (oid && strcmp (oid, "1.3.14.3.2.26"))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (resultsize < 20)
return gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
gcry_md_hash_buffer (2, result, buffer, length);
*resultlen = 20;
return 0;
}
/* GNUTLS log function callback. */
#ifdef HTTP_USE_GNUTLS
static void
my_gnutls_log (int level, const char *text)
{
int n;
n = strlen (text);
while (n && text[n-1] == '\n')
n--;
log_debug ("gnutls:L%d: %.*s\n", level, n, text);
}
#endif /*HTTP_USE_GNUTLS*/
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE);
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE|DBG_LOOKUP_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE|DBG_KEEPTMP_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
log_info (_("valid debug levels are: %s\n"),
"none, basic, advanced, expert, guru");
opt.debug = 0; /* Reset debugging, so that prior debug
statements won't have an undesired effect. */
}
if (opt.debug && !opt.verbose)
{
opt.verbose = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
}
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
#if HTTP_USE_NTBTLS
if (opt_gnutls_debug >= 0)
{
ntbtls_set_debug (opt_gnutls_debug, NULL, NULL);
}
#elif HTTP_USE_GNUTLS
if (opt_gnutls_debug >= 0)
{
gnutls_global_set_log_function (my_gnutls_log);
gnutls_global_set_log_level (opt_gnutls_debug);
}
#endif /*HTTP_USE_GNUTLS*/
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_tor_mode (void)
{
if (dirmngr_use_tor ())
{
/* Enable Tor mode and when called again force a new circuit
* (e.g. on SIGHUP). */
enable_dns_tormode (1);
if (assuan_sock_set_flag (ASSUAN_INVALID_FD, "tor-mode", 1))
{
log_error ("error enabling Tor mode: %s\n", strerror (errno));
log_info ("(is your Libassuan recent enough?)\n");
}
}
else
disable_dns_tormode ();
}
/* Return true if Tor shall be used. */
int
dirmngr_use_tor (void)
{
if (tor_mode == TOR_MODE_AUTO)
{
/* Figure out whether Tor is running. */
assuan_fd_t sock;
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
tor_mode = TOR_MODE_NO;
else
{
tor_mode = TOR_MODE_YES;
assuan_sock_close (sock);
}
}
if (tor_mode == TOR_MODE_FORCE)
return 2; /* Use Tor (using 2 to indicate force mode) */
else if (tor_mode == TOR_MODE_YES)
return 1; /* Use Tor */
else
return 0; /* Do not use Tor. */
}
/* This is somewhat similar to dirmngr_use_tor but avoids a trial
* connect and may thus be faster for this special case. */
int
dirmngr_never_use_tor_p (void)
{
return tor_mode == TOR_MODE_NEVER;
}
static void
wrong_args (const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] "), DIRMNGR_NAME);
es_fputs (text, es_stderr);
es_putc ('\n', es_stderr);
dirmngr_exit (2);
}
/* Helper to stop the reaper thread for the ldap wrapper. */
static void
shutdown_reaper (void)
{
#if USE_LDAP
ldap_wrapper_wait_connections ();
#endif
}
/* Handle options which are allowed to be reset after program start.
Return true if the current option in PARGS could be handled and
false if not. As a special feature, passing a value of NULL for
PARGS, resets the options to the default. REREAD should be set
true if it is not the initial option parsing. */
static int
parse_rereadable_options (gpgrt_argparse_t *pargs, int reread)
{
if (!pargs)
{ /* Reset mode. */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
opt.ldap_wrapper_program = NULL;
opt.disable_http = 0;
opt.disable_ldap = 0;
opt.honor_http_proxy = 0;
opt.http_proxy = NULL;
opt.ldap_proxy = NULL;
opt.only_ldap_proxy = 0;
opt.ignore_http_dp = 0;
opt.ignore_ldap_dp = 0;
opt.ignore_ocsp_service_url = 0;
opt.allow_ocsp = 0;
opt.allow_version_check = 0;
opt.ocsp_responder = NULL;
opt.ocsp_max_clock_skew = 10 * 60; /* 10 minutes. */
opt.ocsp_max_period = 90 * 86400; /* 90 days. */
opt.ocsp_current_period = 3 * 60 * 60; /* 3 hours. */
opt.max_replies = DEFAULT_MAX_REPLIES;
while (opt.ocsp_signer)
{
fingerprint_list_t tmp = opt.ocsp_signer->next;
xfree (opt.ocsp_signer);
opt.ocsp_signer = tmp;
}
while (opt.ignored_certs)
{
fingerprint_list_t tmp = opt.ignored_certs->next;
xfree (opt.ignored_certs);
opt.ignored_certs = tmp;
}
FREE_STRLIST (opt.ignored_cert_extensions);
FREE_STRLIST (opt.ignored_crl_extensions);
http_register_tls_ca (NULL);
FREE_STRLIST (hkp_cacert_filenames);
FREE_STRLIST (opt.keyserver);
/* Note: We do not allow resetting of TOR_MODE_FORCE at runtime. */
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_AUTO;
disable_check_own_socket = 0;
enable_standard_resolver (0);
set_dns_timeout (0);
opt.connect_timeout = 0;
opt.connect_quick_timeout = 0;
opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
ldapserver_list_needs_reset = 1;
opt.debug_cache_expired_certs = 0;
xfree (opt.fake_crl);
opt.fake_crl = NULL;
opt.compat_flags = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs->r.ret_str; break;
case oGnutlsDebug: opt_gnutls_debug = pargs->r.ret_int; break;
case oLogFile:
if (!reread)
return 0; /* Not handled. */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
case oLDAPWrapperProgram:
opt.ldap_wrapper_program = pargs->r.ret_str;
break;
case oHTTPWrapperProgram:
opt.http_wrapper_program = pargs->r.ret_str;
break;
case oDisableHTTP: opt.disable_http = 1; break;
case oDisableLDAP: opt.disable_ldap = 1; break;
case oDisableIPv4: opt.disable_ipv4 = 1; break;
case oDisableIPv6: opt.disable_ipv6 = 1; break;
case oHonorHTTPProxy: opt.honor_http_proxy = 1; break;
case oHTTPProxy: opt.http_proxy = pargs->r.ret_str; break;
case oLDAPProxy: opt.ldap_proxy = pargs->r.ret_str; break;
case oOnlyLDAPProxy: opt.only_ldap_proxy = 1; break;
case oIgnoreHTTPDP: opt.ignore_http_dp = 1; break;
case oIgnoreLDAPDP: opt.ignore_ldap_dp = 1; break;
case oIgnoreOCSPSvcUrl: opt.ignore_ocsp_service_url = 1; break;
case oAllowOCSP: opt.allow_ocsp = 1; break;
case oAllowVersionCheck: opt.allow_version_check = 1; break;
case oOCSPResponder: opt.ocsp_responder = pargs->r.ret_str; break;
case oOCSPSigner:
opt.ocsp_signer = parse_fingerprint_item (pargs->r.ret_str,
"--ocsp-signer", 0);
break;
case oOCSPMaxClockSkew: opt.ocsp_max_clock_skew = pargs->r.ret_int; break;
case oOCSPMaxPeriod: opt.ocsp_max_period = pargs->r.ret_int; break;
case oOCSPCurrentPeriod: opt.ocsp_current_period = pargs->r.ret_int; break;
case oMaxReplies: opt.max_replies = pargs->r.ret_int; break;
case oHkpCaCert:
{
/* We need to register the filenames with gnutls (http.c) and
* also for our own cert cache. */
char *tmpname;
/* Do tilde expansion and make path absolute. */
tmpname = make_absfilename (pargs->r.ret_str, NULL);
http_register_tls_ca (tmpname);
add_to_strlist (&hkp_cacert_filenames, pargs->r.ret_str);
xfree (tmpname);
}
break;
case oIgnoreCert:
{
fingerprint_list_t item, r;
item = parse_fingerprint_item (pargs->r.ret_str, "--ignore-cert", 20);
if (item)
{ /* Append */
if (!opt.ignored_certs)
opt.ignored_certs = item;
else
{
for (r = opt.ignored_certs; r->next; r = r->next)
;
r->next = item;
}
}
}
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs->r.ret_str);
break;
case oIgnoreCRLExtension:
add_to_strlist (&opt.ignored_crl_extensions, pargs->r.ret_str);
break;
case oUseTor:
tor_mode = TOR_MODE_FORCE;
break;
case oNoUseTor:
if (tor_mode != TOR_MODE_FORCE)
tor_mode = TOR_MODE_NEVER;
break;
case oStandardResolver: enable_standard_resolver (1); break;
case oRecursiveResolver: enable_recursive_resolver (1); break;
case oLDAPServer:
#if USE_LDAP
{
ldap_server_t server;
char *p;
p = pargs->r.ret_str;
if (!strncmp (p, "ldap:", 5) && !(p[5] == '/' && p[6] == '/'))
p += 5;
server = ldapserver_parse_one (p, NULL, 0);
if (server)
{
if (ldapserver_list_needs_reset)
{
ldapserver_list_needs_reset = 0;
ldapserver_list_free (opt.ldapservers);
opt.ldapservers = NULL;
}
server->next = opt.ldapservers;
opt.ldapservers = server;
}
}
#endif
break;
case oKeyServer:
if (*pargs->r.ret_str)
add_to_strlist (&opt.keyserver, pargs->r.ret_str);
break;
case oNameServer:
set_dns_nameserver (pargs->r.ret_str);
break;
case oResolverTimeout:
set_dns_timeout (pargs->r.ret_int);
break;
case oConnectTimeout:
opt.connect_timeout = pargs->r.ret_ulong * 1000;
break;
case oConnectQuickTimeout:
opt.connect_quick_timeout = pargs->r.ret_ulong * 1000;
break;
case oLDAPTimeout:
opt.ldaptimeout = pargs->r.ret_int;
break;
case oDebugCacheExpiredCerts:
opt.debug_cache_expired_certs = 0;
break;
case oFakeCRL:
xfree (opt.fake_crl);
opt.fake_crl = *pargs->r.ret_str? xstrdup (pargs->r.ret_str) : NULL;
break;
case oCompatibilityFlags:
if (parse_compatibility_flags (pargs->r.ret_str, &opt.compat_flags,
compatibility_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_WARNING;
}
break;
default:
return 0; /* Not handled. */
}
set_dns_verbose (opt.verbose, !!DBG_DNS);
http_set_verbose (opt.verbose, !!DBG_NETWORK);
set_dns_disable_ipv4 (opt.disable_ipv4);
set_dns_disable_ipv6 (opt.disable_ipv6);
return 1; /* Handled. */
}
/* This function is called after option parsing to adjust some values
* and call option setup functions. */
static void
post_option_parsing (enum cmd_and_opt_values cmd)
{
/* It would be too surpirsing if the quick timeout is larger than
* the standard value. */
if (opt.connect_quick_timeout > opt.connect_timeout)
opt.connect_quick_timeout = opt.connect_timeout;
set_debug ();
/* For certain commands we do not want to set/test for Tor mode
* because that is somewhat expensive. */
switch (cmd)
{
case aGPGConfList:
case aGPGConfTest:
case aGPGConfVersions:
break;
default:
set_tor_mode ();
break;
}
}
#ifndef HAVE_W32_SYSTEM
static int
pid_suffix_callback (unsigned long *r_suffix)
{
union int_and_ptr_u value;
memset (&value, 0, sizeof value);
value.aptr = npth_getspecific (my_tlskey_current_fd);
*r_suffix = value.aint;
return (*r_suffix != -1); /* Use decimal representation. */
}
#endif /*!HAVE_W32_SYSTEM*/
#if HTTP_USE_NTBTLS
static void
my_ntbtls_log_handler (void *opaque, int level, const char *fmt, va_list argv)
{
(void)opaque;
if (level == -1)
log_logv_prefix (GPGRT_LOGLVL_INFO, "ntbtls: ", fmt, argv);
else
{
char prefix[10+20];
snprintf (prefix, sizeof prefix, "ntbtls(%d): ", level);
log_logv_prefix (GPGRT_LOGLVL_DEBUG, prefix, fmt, argv);
}
}
#endif
static void
thread_init (void)
{
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now with NPth running we can set the logging callback. Our
windows implementation does not yet feature the NPth TLS
functions. */
#ifndef HAVE_W32_SYSTEM
if (npth_key_create (&my_tlskey_current_fd, NULL) == 0)
if (npth_setspecific (my_tlskey_current_fd, NULL) == 0)
log_set_pid_suffix_cb (pid_suffix_callback);
#endif /*!HAVE_W32_SYSTEM*/
}
int
main (int argc, char **argv)
{
enum cmd_and_opt_values cmd = 0;
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
const char *shell;
int debug_argparser = 0;
int greeting = 0;
int nogreeting = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
#if USE_LDAP
char *ldapfile = NULL;
#endif /*USE_LDAP*/
int debug_wait = 0;
int rc;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
gpgrt_set_strusage (my_strusage);
log_set_prefix (DIRMNGR_NAME, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
/* Check that the libraries are suitable. Do it here because
the option parsing may need services of the libraries. */
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
ksba_set_hash_buffer_function (my_ksba_hash_buffer, NULL);
/* Init TLS library. */
#if HTTP_USE_NTBTLS
if (!ntbtls_check_version (NEED_NTBTLS_VERSION) )
log_fatal( _("%s is too old (need %s, have %s)\n"), "ntbtls",
NEED_NTBTLS_VERSION, ntbtls_check_version (NULL) );
#elif HTTP_USE_GNUTLS
rc = gnutls_global_init ();
if (rc)
log_fatal ("gnutls_global_init failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
/* Init Assuan. */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_assuan_log_prefix (log_get_prefix (NULL));
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, dirmngr_assuan_log_monitor);
setup_libgcrypt_logging ();
#if HTTP_USE_NTBTLS
ntbtls_set_log_handler (my_ntbtls_log_handler, NULL);
#endif
/* Setup defaults. */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Reset rereadable options to default values. */
parse_rereadable_options (NULL, 0);
/* Default TCP timeouts. */
opt.connect_timeout = DEFAULT_CONNECT_TIMEOUT;
opt.connect_quick_timeout = DEFAULT_CONNECT_QUICK_TIMEOUT;
/* LDAP defaults. */
opt.add_new_ldapservers = 0;
opt.ldaptimeout = DEFAULT_LDAP_TIMEOUT;
/* Other defaults. */
/* Check whether we have a config file given on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
socket_name = dirmngr_socket_name ();
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
- * flags so that the internal intialized flag won't be cleared. */
+ * flags so that the internal initialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, DIRMNGR_NAME EXTSEP_S "conf"))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
continue;
}
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aServer:
case aDaemon:
case aSupervised:
case aShutdown:
case aFlush:
case aListCRLs:
case aLoadCRL:
case aFetchCRL:
case aGPGConfList:
case aGPGConfTest:
case aGPGConfVersions:
cmd = pargs.r_opt;
break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: /* Ignore this option here. */; break;
case oNoDetach: nodetach = 1; break;
case oStealSocket: steal_socket = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oLDAPFile:
# if USE_LDAP
ldapfile = pargs.r.ret_str;
# endif /*USE_LDAP*/
break;
case oLDAPAddServers: opt.add_new_ldapservers = 1; break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oForce: opt.force = 1; break;
case oSocketName: socket_name = pargs.r.ret_str; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (),
DIRMNGR_NAME EXTSEP_S "conf",
NULL);
else
{
opt.config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit(2);
/* Get a default log file from common.conf. */
if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_DIRMNGR, debug_argparser))
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
if (nogreeting )
greeting = 0;
if (!opt.homedir_cache)
opt.homedir_cache = xstrdup (gnupg_homedir ());
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14));
es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15));
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (!gnupg_access ("/etc/"DIRMNGR_NAME, F_OK)
&& !strncmp (gnupg_homedir (), "/etc/", 5))
log_info
("NOTE: DirMngr is now a proper part of %s. The configuration and"
" other directory names changed. Please check that no other version"
" of dirmngr is still installed. To disable this warning, remove the"
" directory '/etc/dirmngr'.\n", GNUPG_NAME);
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
post_option_parsing (cmd);
/* Get LDAP server list from file unless --ldapserver has been used. */
#if USE_LDAP
if (opt.ldapservers)
;
else if (!ldapfile)
{
ldapfile = make_filename (gnupg_homedir (),
"dirmngr_ldapservers.conf",
NULL);
opt.ldapservers = parse_ldapserver_file (ldapfile, 1);
xfree (ldapfile);
}
else
opt.ldapservers = parse_ldapserver_file (ldapfile, 0);
#endif /*USE_LDAP*/
#ifndef HAVE_W32_SYSTEM
/* We need to ignore the PIPE signal because the we might log to a
socket and that code handles EPIPE properly. The ldap wrapper
also requires us to ignore this silly signal. Assuan would set
this signal to ignore anyway.*/
signal (SIGPIPE, SIG_IGN);
#endif
/* Ready. Now to our duties. */
if (!cmd)
cmd = aServer;
rc = 0;
if (cmd == aServer)
{
/* Note that this server mode is mainly useful for debugging. */
if (argc)
wrong_args ("--server");
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
start_command_handler (ASSUAN_INVALID_FD, 0);
shutdown_reaper ();
}
#ifndef HAVE_W32_SYSTEM
else if (cmd == aSupervised)
{
struct stat statbuf;
if (!opt.quiet)
log_info(_("WARNING: \"%s\" is a deprecated option\n"), "--supervised");
is_supervised = 1;
/* In supervised mode, we expect file descriptor 3 to be an
already opened, listening socket.
We will also not detach from the controlling process or close
stderr; the supervisor should handle all of that. */
if (fstat (3, &statbuf) == -1 && errno == EBADF)
{
log_error ("file descriptor 3 must be validin --supervised mode\n");
dirmngr_exit (1);
}
socket_name = gnupg_get_socket_name (3);
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
else
log_set_prefix (NULL, 0);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (3);
shutdown_reaper ();
}
#endif /*HAVE_W32_SYSTEM*/
else if (cmd == aDaemon)
{
assuan_fd_t fd;
pid_t pid;
int len;
struct sockaddr_un serv_addr;
if (argc)
wrong_args ("--daemon");
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
|GPGRT_LOG_WITH_TIME
|GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
#ifndef HAVE_W32_SYSTEM
if (strchr (socket_name, ':'))
{
log_error (_("colons are not allowed in the socket name\n"));
dirmngr_exit (1);
}
#endif
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
cleanup ();
dirmngr_exit (1);
}
{
int redirected;
if (assuan_sock_set_sockaddr_un (socket_name,
(struct sockaddr*)&serv_addr,
&redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), socket_name);
else
log_error ("error preparing socket '%s': %s\n",
socket_name,
gpg_strerror (gpg_error_from_syserror ()));
dirmngr_exit (1);
}
if (redirected)
{
redir_socket_name = xstrdup (serv_addr.sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n",
socket_name, redir_socket_name);
}
}
len = SUN_LEN (&serv_addr);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Fixme: We should actually test whether a dirmngr is
* already running. For now the steal option is a dummy. */
/* if (steal_socket) */
/* log_info (N_("trying to steal socket from running %s\n"), */
/* "dirmngr"); */
gnupg_remove (redir_socket_name? redir_socket_name : socket_name);
rc = assuan_sock_bind (fd, (struct sockaddr*) &serv_addr, len);
}
if (rc != -1
&& (rc = assuan_sock_get_nonce ((struct sockaddr*) &serv_addr, len, &socket_nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
serv_addr.sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
dirmngr_exit (1);
}
cleanup_socket = 1;
if (gnupg_chmod (serv_addr.sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
serv_addr.sun_path, strerror (errno));
if (listen (FD2INT (fd), listen_backlog) == -1)
{
log_error ("listen(fd,%d) failed: %s\n",
listen_backlog, strerror (errno));
assuan_sock_close (fd);
dirmngr_exit (1);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), serv_addr.sun_path);
es_fflush (NULL);
/* Note: We keep the dirmngr_info output only for the sake of
existing scripts which might use this to detect a successful
start of the dirmngr. */
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
pid = getpid ();
es_printf ("set %s=%s;%lu;1\n",
DIRMNGR_INFO_NAME, socket_name, (ulong) pid);
#else
pid = fork();
if (pid == (pid_t)-1)
{
log_fatal (_("error forking process: %s\n"), strerror (errno));
dirmngr_exit (1);
}
if (pid)
{ /* We are the parent */
char *infostr;
/* Don't let cleanup() remove the socket - the child is
responsible for doing that. */
cleanup_socket = 0;
close (fd);
/* Create the info string: <name>:<pid>:<protocol_version> */
if (asprintf (&infostr, "%s=%s:%lu:1",
DIRMNGR_INFO_NAME, serv_addr.sun_path, (ulong)pid ) < 0)
{
log_error (_("out of core\n"));
kill (pid, SIGTERM);
dirmngr_exit (1);
}
/* Print the environment string, so that the caller can use
shell's eval to set it. But see above. */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export %s;\n", infostr, DIRMNGR_INFO_NAME);
}
free (infostr);
exit (0);
/*NEVER REACHED*/
} /* end parent */
/*
This is the child
*/
/* Detach from tty and put process into a new session */
if (!nodetach )
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
dirmngr_exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
dirmngr_exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
}
#endif
if (!nodetach )
{
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
dirmngr_exit (1);
}
}
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
http_register_netactivity_cb (netactivity_action);
handle_connections (fd);
shutdown_reaper ();
}
else if (cmd == aListCRLs)
{
/* Just list the CRL cache and exit. */
if (argc)
wrong_args ("--list-crls");
crl_cache_init ();
crl_cache_list (es_stdout);
}
else if (cmd == aLoadCRL)
{
struct server_control_s ctrlbuf;
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
if (!argc)
rc = crl_cache_load (&ctrlbuf, NULL);
else
{
for (; !rc && argc; argc--, argv++)
rc = crl_cache_load (&ctrlbuf, *argv);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFetchCRL)
{
ksba_reader_t reader;
struct server_control_s ctrlbuf;
if (argc != 1)
wrong_args ("--fetch-crl URL");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
thread_init ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
ks_hkp_init ();
rc = crl_fetch (&ctrlbuf, argv[0], &reader);
if (rc)
log_error (_("fetching CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
else
{
rc = crl_cache_insert (&ctrlbuf, argv[0], reader);
if (rc)
log_error (_("processing CRL from '%s' failed: %s\n"),
argv[0], gpg_strerror (rc));
crl_close_reader (reader);
}
dirmngr_deinit_default_ctrl (&ctrlbuf);
}
else if (cmd == aFlush)
{
/* Delete cache and exit. */
if (argc)
wrong_args ("--flush");
rc = crl_cache_flush();
}
else if (cmd == aGPGConfTest)
dirmngr_exit (0);
else if (cmd == aGPGConfList)
{
unsigned long flags = 0;
char *filename_esc;
es_printf ("debug-level:%lu:\"none\n", flags | GC_OPT_FLAG_DEFAULT);
es_printf ("ldaptimeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_LDAP_TIMEOUT);
es_printf ("max-replies:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, DEFAULT_MAX_REPLIES);
filename_esc = percent_escape (get_default_keyserver (0), NULL);
es_printf ("keyserver:%lu:\"%s:\n", flags | GC_OPT_FLAG_DEFAULT,
filename_esc);
xfree (filename_esc);
es_printf ("resolver-timeout:%lu:%u\n",
flags | GC_OPT_FLAG_DEFAULT, 0);
}
else if (cmd == aGPGConfVersions)
gpgconf_versions ();
cleanup ();
return !!rc;
}
static void
cleanup (void)
{
crl_cache_deinit ();
cert_cache_deinit (1);
reload_dns_stuff (1);
#if USE_LDAP
ldapserver_list_free (opt.ldapservers);
#endif /*USE_LDAP*/
opt.ldapservers = NULL;
if (cleanup_socket)
{
cleanup_socket = 0;
if (redir_socket_name)
gnupg_remove (redir_socket_name);
else if (socket_name && *socket_name)
gnupg_remove (socket_name);
}
}
void
dirmngr_exit (int rc)
{
cleanup ();
exit (rc);
}
void
dirmngr_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
if (opt.http_proxy)
ctrl->http_proxy = xstrdup (opt.http_proxy);
ctrl->http_no_crl = 1;
ctrl->timeout = opt.connect_timeout;
}
void
dirmngr_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
ctrl->magic = 0xdeadbeef;
xfree (ctrl->http_proxy);
ctrl->http_proxy = NULL;
nvc_release (ctrl->rootdse);
ctrl->rootdse = NULL;
}
/* Create a list of LDAP servers from the file FILENAME. Returns the
list or NULL in case of errors.
The format of such a file is line oriented where empty lines and
lines starting with a hash mark are ignored. All other lines are
- assumed to be colon seprated with these fields:
+ assumed to be colon separated with these fields:
1. field: Hostname
2. field: Portnumber
3. field: Username
4. field: Password
5. field: Base DN
*/
#if USE_LDAP
static ldap_server_t
parse_ldapserver_file (const char* filename, int ignore_enoent)
{
char buffer[1024];
char *p;
ldap_server_t server, serverstart, *serverend;
int c;
unsigned int lineno = 0;
estream_t fp;
fp = es_fopen (filename, "r");
if (!fp)
{
if (ignore_enoent && gpg_err_code_from_syserror () == GPG_ERR_ENOENT)
;
else
log_info ("failed to open '%s': %s\n", filename, strerror (errno));
return NULL;
}
serverstart = NULL;
serverend = &serverstart;
while (es_fgets (buffer, sizeof buffer, fp))
{
lineno++;
if (!*buffer || buffer[strlen(buffer)-1] != '\n')
{
if (*buffer && es_feof (fp))
; /* Last line not terminated - continue. */
else
{
log_error (_("%s:%u: line too long - skipped\n"),
filename, lineno);
while ( (c=es_fgetc (fp)) != EOF && c != '\n')
; /* Skip until end of line. */
continue;
}
}
/* Skip empty and comment lines.*/
for (p=buffer; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
/* Parse the colon separated fields. */
server = ldapserver_parse_one (buffer, filename, lineno);
if (server)
{
*serverend = server;
serverend = &server->next;
}
}
if (es_ferror (fp))
log_error (_("error reading '%s': %s\n"), filename, strerror (errno));
es_fclose (fp);
return serverstart;
}
#endif /*USE_LDAP*/
/* Parse a fingerprint entry as used by --ocsc-signer. OPTIONNAME as
* a description on the options used. WANT_BINARY requests to store a
* binary fingerprint. Returns NULL on error and logs that error. */
static fingerprint_list_t
parse_fingerprint_item (const char *string,
const char *optionname, int want_binary)
{
gpg_error_t err;
char *fname;
estream_t fp;
char line[256];
char *p;
fingerprint_list_t list, *list_tail, item;
unsigned int lnr = 0;
int c, i, j;
int errflag = 0;
/* Check whether this is not a filename and treat it as a direct
fingerprint specification. */
if (!strpbrk (string, "/.~\\"))
{
item = xcalloc (1, sizeof *item);
for (i=j=0; (string[i] == ':' || hexdigitp (string+i)) && j < 40; i++)
if ( string[i] != ':' )
item->hexfpr[j++] = string[i] >= 'a'? (string[i] & 0xdf): string[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (string+i) || !string[i]))
{
log_error (_("%s:%u: invalid fingerprint detected\n"),
optionname, 0);
xfree (item);
return NULL;
}
if (want_binary)
{
item->binlen = 20;
hex2bin (item->hexfpr, item->hexfpr, 20);
}
return item;
}
/* Well, it is a filename. */
if (*string == '/' || (*string == '~' && string[1] == '/'))
fname = make_filename (string, NULL);
else
{
if (string[0] == '.' && string[1] == '/' )
string += 2;
fname = make_filename (gnupg_homedir (), string, NULL);
}
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
xfree (fname);
return NULL;
}
list = NULL;
list_tail = &list;
for (;;)
{
if (!es_fgets (line, DIM(line)-1, fp) )
{
if (!es_feof (fp))
{
err = gpg_error_from_syserror ();
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
}
es_fclose (fp);
if (errflag)
{
while (list)
{
fingerprint_list_t tmp = list->next;
xfree (list);
list = tmp;
}
}
xfree (fname);
return list; /* Ready. */
}
lnr++;
if (!*line || line[strlen(line)-1] != '\n')
{
/* Eat until end of line. */
while ( (c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
/* */: GPG_ERR_INCOMPLETE_LINE);
log_error (_("%s:%u: read error: %s\n"),
fname, lnr, gpg_strerror (err));
errflag = 1;
continue;
}
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '\n' || *p == '#')
continue;
item = xcalloc (1, sizeof *item);
*list_tail = item;
list_tail = &item->next;
for (i=j=0; (p[i] == ':' || hexdigitp (p+i)) && j < 40; i++)
if ( p[i] != ':' )
item->hexfpr[j++] = p[i] >= 'a'? (p[i] & 0xdf): p[i];
item->hexfpr[j] = 0;
if (j != 40 || !(spacep (p+i) || p[i] == '\n'))
{
log_error (_("%s:%u: invalid fingerprint detected\n"), fname, lnr);
errflag = 1;
}
else if (want_binary)
{
item->binlen = 20;
hex2bin (item->hexfpr, item->hexfpr, 20);
}
i++;
while (spacep (p+i))
i++;
if (p[i] && p[i] != '\n')
log_info (_("%s:%u: garbage at end of line ignored\n"), fname, lnr);
}
/*NOTREACHED*/
}
/*
Stuff used in daemon mode.
*/
/* Reread parts of the configuration. Note, that this function is
obviously not thread-safe and should only be called from the NPTH
signal handler.
Fixme: Due to the way the argument parsing works, we create a
memory leak here for all string type arguments. There is currently
no clean way to tell whether the memory for the argument has been
allocated or points into the process's original arguments. Unless
we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
gpgrt_argparse_t pargs;
char *twopart;
int dummy;
int logfile_seen = 0;
if (!opt.config_filename)
goto finish; /* No config file. */
twopart = strconcat (DIRMNGR_NAME EXTSEP_S "conf" PATHSEP_S,
opt.config_filename, NULL);
if (!twopart)
return; /* Out of core. */
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = (ARGPARSE_FLAG_KEEP
|ARGPARSE_FLAG_SYS
|ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, twopart))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
}
else if (pargs.r_opt < -1)
pargs.err = ARGPARSE_PRINT_WARNING;
else /* Try to parse this option - ignore unchangeable ones. */
{
if (pargs.r_opt == oLogFile)
logfile_seen = 1;
parse_rereadable_options (&pargs, 1);
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
xfree (twopart);
post_option_parsing (0);
finish:
/* Get a default log file from common.conf. */
if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_DIRMNGR, !!opt.debug))
{
if (!current_logfile || !comopt.logfile
|| strcmp (current_logfile, comopt.logfile))
{
log_set_file (comopt.logfile);
xfree (current_logfile);
current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL;
}
}
}
/* A global function which allows us to trigger the reload stuff from
other places. */
void
dirmngr_sighup_action (void)
{
log_info (_("SIGHUP received - "
"re-reading configuration and flushing caches\n"));
reread_configuration ();
set_tor_mode ();
cert_cache_deinit (0);
crl_cache_deinit ();
cert_cache_init (hkp_cacert_filenames);
crl_cache_init ();
http_reinitialize ();
reload_dns_stuff (0);
ks_hkp_reload ();
}
/* This function is called if some network activity was done. At this
* point we know the we have a network and we can decide whether to
* run scheduled background tasks soon. The function should return
* quickly and only trigger actions for another thread. */
static void
netactivity_action (void)
{
network_activity_seen = 1;
}
/* The signal handler. */
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
dirmngr_sighup_action ();
break;
case SIGUSR1:
/* See also cmd_getinfo:"stats". */
cert_cache_print_stats (NULL);
domaininfo_print_stats (NULL);
break;
case SIGUSR2:
log_info (_("SIGUSR2 received - no action defined\n"));
break;
case SIGTERM:
if (!shutdown_pending)
log_info (_("SIGTERM received - shutting down ...\n"));
else
log_info (_("SIGTERM received - still %d active connections\n"),
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info (_("shutdown forced\n"));
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
dirmngr_exit (0);
}
break;
case SIGINT:
log_info (_("SIGINT received - immediate shutdown\n"));
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
dirmngr_exit (0);
break;
default:
log_info (_("signal %d received - no action defined\n"), signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Thread to do the housekeeping. */
static void *
housekeeping_thread (void *arg)
{
static int sentinel;
time_t curtime;
struct server_control_s ctrlbuf;
(void)arg;
curtime = gnupg_get_time ();
if (sentinel)
{
log_info ("housekeeping is already going on\n");
return NULL;
}
sentinel++;
if (opt.verbose > 1)
log_info ("starting housekeeping\n");
memset (&ctrlbuf, 0, sizeof ctrlbuf);
dirmngr_init_default_ctrl (&ctrlbuf);
dns_stuff_housekeeping ();
ks_hkp_housekeeping (curtime);
if (network_activity_seen)
{
network_activity_seen = 0;
if (opt.allow_version_check)
dirmngr_load_swdb (&ctrlbuf, 0);
workqueue_run_global_tasks (&ctrlbuf, 1);
}
else
workqueue_run_global_tasks (&ctrlbuf, 0);
dirmngr_deinit_default_ctrl (&ctrlbuf);
if (opt.verbose > 1)
log_info ("ready with housekeeping\n");
sentinel--;
return NULL;
}
/* We try to enable correct overflow handling for signed int (commonly
* used for time_t). With gcc 4.2 -fno-strict-overflow was introduced
* and used here as a pragma. Later gcc versions (gcc 6?) removed
* this as a pragma and -fwrapv was then suggested as a replacement
* for -fno-strict-overflow. */
#if GPGRT_HAVE_PRAGMA_GCC_PUSH
# pragma GCC push_options
# pragma GCC optimize ("wrapv")
#endif
static int
time_for_housekeeping_p (time_t curtime)
{
static time_t last_housekeeping;
if (!last_housekeeping)
last_housekeeping = curtime;
if (last_housekeeping + HOUSEKEEPING_INTERVAL <= curtime
|| last_housekeeping > curtime /*(be prepared for y2038)*/)
{
last_housekeeping = curtime;
return 1;
}
return 0;
}
#if GPGRT_HAVE_PRAGMA_GCC_PUSH
# pragma GCC pop_options
#endif
/* This is the worker for the ticker. It is called every few seconds
and may only do fast operations. */
static void
handle_tick (void)
{
struct stat statbuf;
if (time_for_housekeeping_p (gnupg_get_time ()))
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
log_error ("error preparing housekeeping thread: %s\n", strerror (err));
else
{
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, housekeeping_thread, NULL);
if (err)
log_error ("error spawning housekeeping thread: %s\n",
strerror (err));
npth_attr_destroy (&tattr);
}
}
/* Check whether the homedir is still available. */
if (!shutdown_pending
&& gnupg_stat (gnupg_homedir (), &statbuf) && errno == ENOENT)
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
}
/* Check the nonce on a new connection. This is a NOP unless we are
using our Unix domain socket emulation under Windows. */
static int
check_nonce (assuan_fd_t fd, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (fd), strerror (errno));
assuan_sock_close (fd);
return -1;
}
else
return 0;
}
/* Helper to call a connection's main function. */
static void *
start_connection_thread (void *arg)
{
static unsigned int last_session_id;
unsigned int session_id;
union int_and_ptr_u argval;
gnupg_fd_t fd;
memset (&argval, 0, sizeof argval);
argval.aptr = arg;
fd = argval.afd;
if (check_nonce (fd, &socket_nonce))
{
log_error ("handler nonce check FAILED\n");
return NULL;
}
#ifndef HAVE_W32_SYSTEM
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
active_connections++;
if (opt.verbose)
log_info (_("handler for fd %d started\n"), FD_DBG (fd));
session_id = ++last_session_id;
if (!session_id)
session_id = ++last_session_id;
start_command_handler (fd, session_id);
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"), FD_DBG (fd));
active_connections--;
workqueue_run_post_session_tasks (session_id);
#ifndef HAVE_W32_SYSTEM
argval.afd = ASSUAN_INVALID_FD;
npth_setspecific (my_tlskey_current_fd, argval.aptr);
#endif
return NULL;
}
#ifdef HAVE_INOTIFY_INIT
/* Read an inotify event and return true if it matches NAME. */
static int
my_inotify_is_name (int fd, const char *name)
{
union {
struct inotify_event ev;
char _buf[sizeof (struct inotify_event) + 100 + 1];
} buf;
int n;
const char *s;
s = strrchr (name, '/');
if (s && s[1])
name = s + 1;
n = npth_read (fd, &buf, sizeof buf);
if (n < sizeof (struct inotify_event))
return 0;
if (buf.ev.len < strlen (name)+1)
return 0;
if (strcmp (buf.ev.name, name))
return 0; /* Not the desired file. */
return 1; /* Found. */
}
#endif /*HAVE_INOTIFY_INIT*/
/* Main loop in daemon mode. Note that LISTEN_FD will be owned by
* this function. */
static void
handle_connections (assuan_fd_t listen_fd)
{
npth_attr_t tattr;
#ifndef HAVE_W32_SYSTEM
int signo;
#endif
struct sockaddr_un paddr;
socklen_t plen = sizeof( paddr );
int nfd, ret;
fd_set fdset, read_fdset;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int saved_errno;
int my_inotify_fd = -1;
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM /* FIXME */
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#endif
#ifdef HAVE_INOTIFY_INIT
if (disable_check_own_socket)
my_inotify_fd = -1;
else if ((my_inotify_fd = inotify_init ()) == -1)
log_info ("error enabling fast daemon termination: %s\n",
strerror (errno));
else
{
/* We need to watch the directory for the file because there
* won't be an IN_DELETE_SELF for a socket file. */
char *slash = strrchr (socket_name, '/');
log_assert (slash && slash[1]);
*slash = 0;
if (inotify_add_watch (my_inotify_fd, socket_name, IN_DELETE) == -1)
{
close (my_inotify_fd);
my_inotify_fd = -1;
}
*slash = '/';
}
#endif /*HAVE_INOTIFY_INIT*/
/* Setup the fdset. It has only one member. This is because we use
pth_select instead of pth_accept to properly sync timeouts with
to full second. */
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2NUM (listen_fd);
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
if (my_inotify_fd > nfd)
nfd = my_inotify_fd;
}
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL;
/* Main loop. */
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections || is_supervised)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new dirmngr instance - which is unlikely the
* intention of a shutdown. */
/* assuan_sock_close (listen_fd); */
/* listen_fd = -1; */
FD_ZERO (&fdset);
nfd = -1;
if (my_inotify_fd != -1)
{
FD_SET (my_inotify_fd, &fdset);
nfd = my_inotify_fd;
}
}
/* Take a copy of the fdset. */
read_fdset = fdset;
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. When a shutdown is pending we use a shorter
* interval to handle the shutdown more quickly. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += (shutdown_pending
? TIMERTICK_INTERVAL_SHUTDOWN
: TIMERTICK_INTERVAL);
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, &timeout,
npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, &timeout, NULL, NULL);
saved_errno = errno;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
}
if (shutdown_pending)
{
/* Do not anymore accept connections. */
continue;
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1 && FD_ISSET (my_inotify_fd, &read_fdset)
&& my_inotify_is_name (my_inotify_fd, socket_name))
{
shutdown_pending = 1;
log_info ("socket file has been removed - shutting down\n");
}
#endif /*HAVE_INOTIFY_INIT*/
if (FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
gnupg_fd_t fd;
plen = sizeof paddr;
fd = assuan_sock_accept (listen_fd,
(struct sockaddr *)&paddr, &plen);
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else
{
char threadname[50];
union int_and_ptr_u argval;
npth_t thread;
memset (&argval, 0, sizeof argval);
argval.afd = fd;
snprintf (threadname, sizeof threadname,
"conn fd=%d", FD_DBG (fd));
ret = npth_create (&thread, &tattr,
start_connection_thread, argval.aptr);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret) );
assuan_sock_close (fd);
}
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_INOTIFY_INIT
if (my_inotify_fd != -1)
close (my_inotify_fd);
#endif /*HAVE_INOTIFY_INIT*/
npth_attr_destroy (&tattr);
if (listen_fd != GNUPG_INVALID_FD)
assuan_sock_close (listen_fd);
cleanup ();
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
}
const char*
dirmngr_get_current_socket_name (void)
{
if (socket_name)
return socket_name;
else
return dirmngr_socket_name ();
}
/* Parse the revision part from the extended version blurb. */
static const char *
get_revision_from_blurb (const char *blurb, int *r_len)
{
const char *s = blurb? blurb : "";
int n;
for (; *s; s++)
if (*s == '\n' && s[1] == '(')
break;
if (*s)
{
s += 2;
for (n=0; s[n] && s[n] != ' '; n++)
;
}
else
{
s = "?";
n = 1;
}
*r_len = n;
return s;
}
/* Print versions of dirmngr and used libraries. This is used by
* "gpgconf --show-versions" so that there is no need to link gpgconf
* against all these libraries. This is an internal API and should
* not be relied upon. */
static void
gpgconf_versions (void)
{
const char *s;
int n;
/* Unfortunately Npth has no way to get the version. */
s = get_revision_from_blurb (assuan_check_version ("\x01\x01"), &n);
es_fprintf (es_stdout, "* Libassuan %s (%.*s)\n\n",
assuan_check_version (NULL), n, s);
s = get_revision_from_blurb (ksba_check_version ("\x01\x01"), &n);
es_fprintf (es_stdout, "* KSBA %s (%.*s)\n\n",
ksba_check_version (NULL), n, s);
#ifdef HTTP_USE_NTBTLS
s = get_revision_from_blurb (ntbtls_check_version ("\x01\x01"), &n);
es_fprintf (es_stdout, "* NTBTLS %s (%.*s)\n\n",
ntbtls_check_version (NULL), n, s);
#elif HTTP_USE_GNUTLS
es_fprintf (es_stdout, "* GNUTLS %s\n\n",
gnutls_check_version (NULL));
#endif
}
diff --git a/dirmngr/dirmngr.h b/dirmngr/dirmngr.h
index 50c97f140..ace40e6d5 100644
--- a/dirmngr/dirmngr.h
+++ b/dirmngr/dirmngr.h
@@ -1,314 +1,314 @@
/* dirmngr.h - Common definitions for the dirmngr
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2004, 2015 g10 Code GmbH
* 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 <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0+
*/
#ifndef DIRMNGR_H
#define DIRMNGR_H
#include "./dirmngr-err.h"
#define map_assuan_err(a) \
map_assuan_err_with_source (GPG_ERR_SOURCE_DEFAULT, (a))
#include <errno.h>
#include <gcrypt.h>
#include <ksba.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "../common/asshelp.h" /* (assuan_context_t) */
#include "../common/i18n.h"
#include "../common/name-value.h"
#include "dirmngr-status.h"
#include "http.h" /* (parsed_uri_t) */
/* This objects keeps information about a particular LDAP server and
is used as item of a single linked list of servers. */
struct ldap_server_s
{
struct ldap_server_s* next;
char *host;
int port;
char *user;
char *pass;
char *base;
unsigned int starttls:1; /* Use STARTTLS. */
unsigned int ldap_over_tls:1; /* Use LDAP over an TLS tunnel */
unsigned int ntds:1; /* Use Active Directory authentication. */
unsigned int areconly:1; /* Set LDAP_OPT_AREC_EXCLUSIVE. */
};
typedef struct ldap_server_s *ldap_server_t;
/* This objects is used to build a list of URI consisting of the
original and the parsed URI. */
struct uri_item_s
{
struct uri_item_s *next;
parsed_uri_t parsed_uri; /* The broken down URI. */
char uri[1]; /* The original URI. */
};
typedef struct uri_item_s *uri_item_t;
/* A list of fingerprints. */
struct fingerprint_list_s;
typedef struct fingerprint_list_s *fingerprint_list_t;
struct fingerprint_list_s
{
fingerprint_list_t next;
char binlen; /* If this is not 0 hexfpr actually carries a binary fpr. */
char hexfpr[20+20+1];
};
/* A large struct named "opt" to keep global flags. */
EXTERN_UNLESS_MAIN_MODULE
struct
{
unsigned int debug; /* debug flags (DBG_foo_VALUE) */
int verbose; /* verbosity level */
int quiet; /* be as quiet as possible */
int dry_run; /* don't change any persistent data */
int batch; /* batch mode */
const char *homedir_cache; /* Dir for cache files (/var/cache/dirmngr). */
char *config_filename; /* Name of a config file, which will be
reread on a HUP if it is not NULL. */
char *ldap_wrapper_program; /* Override value for the LDAP wrapper
program. */
char *http_wrapper_program; /* Override value for the HTTP wrapper
program. */
int running_detached; /* We are running in detached mode. */
int allow_version_check; /* --allow-version-check is active. */
int force; /* Force loading outdated CRLs. */
char *fake_crl; /* Name of a file with faked CRL entries. */
unsigned int connect_timeout; /* Timeout for connect. */
unsigned int connect_quick_timeout; /* Shorter timeout for connect. */
int disable_http; /* Do not use HTTP at all. */
int disable_ldap; /* Do not use LDAP at all. */
int disable_ipv4; /* Do not use legacy IP addresses. */
int disable_ipv6; /* Do not use standard IP addresses. */
int honor_http_proxy; /* Honor the http_proxy env variable. */
const char *http_proxy; /* The default HTTP proxy. */
const char *ldap_proxy; /* Use given LDAP proxy. */
int only_ldap_proxy; /* Only use the LDAP proxy; no fallback. */
int ignore_http_dp; /* Ignore HTTP CRL distribution points. */
int ignore_ldap_dp; /* Ignore LDAP CRL distribution points. */
int ignore_ocsp_service_url; /* Ignore OCSP service URLs as given in
the certificate. */
/* A list of fingerprints of certififcates we should completely
* ignore. These are all stored in binary format. */
fingerprint_list_t ignored_certs;
/* A list of certificate extension OIDs which are ignored so that
one can claim that a critical extension has been handled. One
OID per string. */
strlist_t ignored_cert_extensions;
/* A list of CRL extension OIDs which are ignored so that one can
* claim that a critical extension has been handled. One OID per
* string. */
strlist_t ignored_crl_extensions;
/* Allow expired certificates in the cache. */
int debug_cache_expired_certs;
int allow_ocsp; /* Allow using OCSP. */
int max_replies;
unsigned int ldaptimeout;
ldap_server_t ldapservers;
int add_new_ldapservers;
const char *ocsp_responder; /* Standard OCSP responder's URL. */
fingerprint_list_t ocsp_signer; /* The list of fingerprints with allowed
standard OCSP signer certificates. */
unsigned int ocsp_max_clock_skew; /* Allowed seconds of clocks skew. */
unsigned int ocsp_max_period; /* Seconds a response is at maximum
considered valid after thisUpdate. */
unsigned int ocsp_current_period; /* Seconds a response is considered
current after nextUpdate. */
strlist_t keyserver; /* List of default keyservers. */
/* Compatibility flags (COMPAT_FLAG_xxxx). */
unsigned int compat_flags;
} opt;
#define DBG_X509_VALUE 1 /* debug x.509 parsing */
#define DBG_CRYPTO_VALUE 4 /* debug low level crypto */
#define DBG_DNS_VALUE 16 /* debug DNS calls. */
#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 /* debug assuan communication */
#define DBG_NETWORK_VALUE 2048 /* debug network I/O. */
#define DBG_LOOKUP_VALUE 8192 /* debug lookup details */
#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */
#define DBG_KEEPTMP_VALUE 32768 /* keep some temporary files */
#define DBG_X509 (opt.debug & DBG_X509_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_DNS (opt.debug & DBG_DNS_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)
#define DBG_NETWORK (opt.debug & DBG_NETWORK_VALUE)
#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
#define DBG_KEEPTMP (opt.debug & DBG_KEEPTMP_VALUE)
/* Compatibility flags */
/* Since version 2.2.12 dirmngr restricted HTTP redirection in an
* attempt to mitigate certain CSRF attacks. It turned out that this
* breaks too many WKD deployments and that the attack scenario is not
* due to gnupg's redirecting but due to insecure configured systems.
* Thus from 2.4.3 on we disable this restriction but allow to use the
* old behaviour by using this compatibility flag. For details see
* https://dev.gnupg.org/T6477. */
#define COMPAT_RESTRICT_HTTP_REDIR 1
/* A simple list of certificate references. FIXME: Better use
certlist_t also for references (Store NULL at .cert) */
struct cert_ref_s
{
struct cert_ref_s *next;
unsigned char fpr[20];
};
typedef struct cert_ref_s *cert_ref_t;
/* Forward reference; access only via ks-engine-ldap.c. */
struct ks_engine_ldap_local_s;
/* Forward reference; access only through server.c. */
struct server_local_s;
#if SIZEOF_UNSIGNED_LONG == 8
# define SERVER_CONTROL_MAGIC 0x6469726d6e677220
#else
# define SERVER_CONTROL_MAGIC 0x6469726d
#endif
/* Connection control structure. */
struct server_control_s
{
unsigned long magic;/* Always has SERVER_CONTROL_MAGIC. */
int refcount; /* Count additional references to this object. */
int no_server; /* We are not running under server control. */
int status_fd; /* Only for non-server mode. */
struct server_local_s *server_local;
struct ks_engine_ldap_local_s *ks_get_state;
int force_crl_refresh; /* Always load a fresh CRL. */
int check_revocations_nest_level; /* Internal to check_revovations. */
cert_ref_t ocsp_certs; /* Certificates from the current OCSP
response. */
int audit_events; /* Send audit events to client. */
char *http_proxy; /* The used http_proxy or NULL. */
- nvc_t rootdse; /* Container wit the rootDSE properties. */
+ nvc_t rootdse; /* Container with the rootDSE properties. */
unsigned int timeout; /* Timeout for connect calls in ms. */
unsigned int http_no_crl:1; /* Do not check CRLs for https. */
unsigned int rootdse_tried:1;/* Already tried to get the rootDSE. */
};
/*-- dirmngr.c --*/
void dirmngr_exit( int ); /* Wrapper for exit() */
void dirmngr_init_default_ctrl (ctrl_t ctrl);
void dirmngr_deinit_default_ctrl (ctrl_t ctrl);
void dirmngr_sighup_action (void);
const char* dirmngr_get_current_socket_name (void);
int dirmngr_use_tor (void);
int dirmngr_never_use_tor_p (void);
/*-- Various housekeeping functions. --*/
void ks_hkp_housekeeping (time_t curtime);
void ks_hkp_reload (void);
void ks_hkp_init (void);
/*-- server.c --*/
void release_uri_item_list (uri_item_t list);
ldap_server_t get_ldapservers_from_ctrl (ctrl_t ctrl);
ksba_cert_t get_cert_local (ctrl_t ctrl, const char *issuer);
ksba_cert_t get_issuing_cert_local (ctrl_t ctrl, const char *issuer);
ksba_cert_t get_cert_local_ski (ctrl_t ctrl,
const char *name, ksba_sexp_t keyid);
gpg_error_t get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr);
int dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg);
void start_command_handler (gnupg_fd_t fd, unsigned int session_id);
gpg_error_t dirmngr_tick (ctrl_t ctrl);
/*-- http-ntbtls.c --*/
/* Note that we don't use a callback for gnutls. */
gpg_error_t gnupg_http_tls_verify_cb (void *opaque,
http_t http,
http_session_t session,
unsigned int flags,
void *tls_context);
/*-- loadswdb.c --*/
gpg_error_t dirmngr_load_swdb (ctrl_t ctrl, int force);
/*-- domaininfo.c --*/
void domaininfo_print_stats (ctrl_t ctrl);
int domaininfo_is_wkd_not_supported (const char *domain);
void domaininfo_set_no_name (const char *domain);
void domaininfo_set_wkd_supported (const char *domain);
void domaininfo_set_wkd_not_supported (const char *domain);
void domaininfo_set_wkd_not_found (const char *domain);
/*-- workqueue.c --*/
typedef const char *(*wqtask_t)(ctrl_t ctrl, const char *args);
void workqueue_dump_queue (ctrl_t ctrl);
gpg_error_t workqueue_add_task (wqtask_t func, const char *args,
unsigned int session_id, int need_network);
void workqueue_run_global_tasks (ctrl_t ctrl, int with_network);
void workqueue_run_post_session_tasks (unsigned int session_id);
#endif /*DIRMNGR_H*/
diff --git a/dirmngr/dns.c b/dirmngr/dns.c
index e0eb33244..f023e7395 100644
--- a/dirmngr/dns.c
+++ b/dirmngr/dns.c
@@ -1,11684 +1,11684 @@
/* ==========================================================================
* dns.c - Recursive, Reentrant DNS Resolver.
* --------------------------------------------------------------------------
* Copyright (c) 2008, 2009, 2010, 2012-2016 William Ahern
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, sublicense, and/or sell copies of the Software, and to permit
* persons to whom the Software is furnished to do so, subject to the
* following conditions:
*
* The above copyright notice and this permission notice shall be included
* in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
* NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
* DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
* OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
* USE OR OTHER DEALINGS IN THE SOFTWARE.
* ==========================================================================
*/
#if HAVE_CONFIG_H
#include "config.h"
#elif !defined _GNU_SOURCE
#define _GNU_SOURCE 1
#endif
#include <limits.h> /* INT_MAX */
#include <stdarg.h> /* va_list va_start va_end */
#include <stddef.h> /* offsetof() */
#ifdef _WIN32
/* JW: This breaks our mingw build: #define uint32_t unsigned int */
#else
#include <stdint.h> /* uint32_t */
#endif
#include <stdlib.h> /* malloc(3) realloc(3) free(3) rand(3) random(3) arc4random(3) */
#include <stdio.h> /* FILE fopen(3) fclose(3) getc(3) rewind(3) vsnprintf(3) */
#include <string.h> /* memcpy(3) strlen(3) memmove(3) memchr(3) memcmp(3) strchr(3) strsep(3) strcspn(3) */
#include <strings.h> /* strcasecmp(3) strncasecmp(3) */
#include <ctype.h> /* isspace(3) isdigit(3) */
#include <time.h> /* time_t time(2) difftime(3) */
#include <signal.h> /* SIGPIPE sigemptyset(3) sigaddset(3) sigpending(2) sigprocmask(2) pthread_sigmask(3) sigtimedwait(2) */
#include <errno.h> /* errno EINVAL ENOENT */
#undef NDEBUG
#include <assert.h> /* assert(3) */
#if _WIN32
#ifndef FD_SETSIZE
#define FD_SETSIZE 1024
#endif
#include <winsock2.h>
#include <ws2tcpip.h>
typedef SOCKET socket_fd_t;
#define STDCALL __stdcall
#ifdef HAVE_SYS_TIME_H
#include <sys/time.h> /* gettimeofday(2) */
#endif
#else
typedef int socket_fd_t;
#define STDCALL
#include <sys/time.h> /* gettimeofday(2) */
#include <sys/types.h> /* FD_SETSIZE socklen_t */
#include <sys/select.h> /* FD_ZERO FD_SET fd_set select(2) */
#include <sys/socket.h> /* AF_INET AF_INET6 AF_UNIX struct sockaddr struct sockaddr_in struct sockaddr_in6 socket(2) */
#if defined(AF_UNIX)
#include <sys/un.h> /* struct sockaddr_un */
#endif
#include <fcntl.h> /* F_SETFD F_GETFL F_SETFL O_NONBLOCK fcntl(2) */
#include <unistd.h> /* _POSIX_THREADS gethostname(3) close(2) */
#include <poll.h> /* POLLIN POLLOUT */
#include <netinet/in.h> /* struct sockaddr_in struct sockaddr_in6 */
#include <arpa/inet.h> /* inet_pton(3) inet_ntop(3) htons(3) ntohs(3) */
#include <netdb.h> /* struct addrinfo */
#endif
#include "gpgrt.h" /* For GGPRT_GCC_VERSION */
#include "dns.h"
/*
* C O M P I L E R V E R S I O N & F E A T U R E D E T E C T I O N
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_GNUC_2VER(M, m, p) (((M) * 10000) + ((m) * 100) + (p))
#define DNS_GNUC_PREREQ(M, m, p) (__GNUC__ > 0 && DNS_GNUC_2VER(__GNUC__, __GNUC_MINOR__, __GNUC_PATCHLEVEL__) >= DNS_GNUC_2VER((M), (m), (p)))
#define DNS_MSC_2VER(M, m, p) ((((M) + 6) * 10000000) + ((m) * 1000000) + (p))
#define DNS_MSC_PREREQ(M, m, p) (_MSC_VER_FULL > 0 && _MSC_VER_FULL >= DNS_MSC_2VER((M), (m), (p)))
#define DNS_SUNPRO_PREREQ(M, m, p) (__SUNPRO_C > 0 && __SUNPRO_C >= 0x ## M ## m ## p)
#if defined __has_builtin
#define dns_has_builtin(x) __has_builtin(x)
#else
#define dns_has_builtin(x) 0
#endif
#if defined __has_extension
#define dns_has_extension(x) __has_extension(x)
#else
#define dns_has_extension(x) 0
#endif
#ifndef HAVE___ASSUME
#define HAVE___ASSUME DNS_MSC_PREREQ(8,0,0)
#endif
#ifndef HAVE___BUILTIN_TYPES_COMPATIBLE_P
#define HAVE___BUILTIN_TYPES_COMPATIBLE_P (DNS_GNUC_PREREQ(3,1,1) || __clang__)
#endif
#ifndef HAVE___BUILTIN_UNREACHABLE
#define HAVE___BUILTIN_UNREACHABLE (DNS_GNUC_PREREQ(4,5,0) || dns_has_builtin(__builtin_unreachable))
#endif
#ifndef HAVE_PRAGMA_MESSAGE
#define HAVE_PRAGMA_MESSAGE (DNS_GNUC_PREREQ(4,4,0) || __clang__ || _MSC_VER)
#endif
/*
* C O M P I L E R A N N O T A T I O N S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if __GNUC__
#define DNS_NOTUSED __attribute__((unused))
#define DNS_NORETURN __attribute__((__noreturn__))
#else
#define DNS_NOTUSED
#define DNS_NORETURN
#endif
#if __clang__
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-parameter"
#pragma clang diagnostic ignored "-Wmissing-field-initializers"
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wunused-parameter"
#pragma GCC diagnostic ignored "-Wmissing-field-initializers"
#endif
/*
* S T A N D A R D M A C R O S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if HAVE___BUILTIN_TYPES_COMPATIBLE_P
#define dns_same_type(a, b, def) __builtin_types_compatible_p(__typeof__ (a), __typeof__ (b))
#else
#define dns_same_type(a, b, def) (def)
#endif
#define dns_isarray(a) (!dns_same_type((a), (&(a)[0]), 0))
/* NB: "_" field silences Sun Studio "zero-sized struct/union" error diagnostic */
#define dns_inline_assert(cond) ((void)(sizeof (struct { int:-!(cond); int _; })))
#if HAVE___ASSUME
#define dns_assume(cond) __assume(cond)
#elif HAVE___BUILTIN_UNREACHABLE
#define dns_assume(cond) do { if (!(cond)) __builtin_unreachable(); } while (0)
#else
#define dns_assume(cond) do { (void)(cond); } while (0)
#endif
#ifndef lengthof
#define lengthof(a) (dns_inline_assert(dns_isarray(a)), (sizeof (a) / sizeof (a)[0]))
#endif
#ifndef endof
#define endof(a) (dns_inline_assert(dns_isarray(a)), &(a)[lengthof((a))])
#endif
/*
* M I S C E L L A N E O U S C O M P A T
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if _WIN32 || _WIN64
#define PRIuZ "Iu"
#else
#define PRIuZ "zu"
#endif
#ifndef DNS_THREAD_SAFE
#if (defined _REENTRANT || defined _THREAD_SAFE) && _POSIX_THREADS > 0
#define DNS_THREAD_SAFE 1
#else
#define DNS_THREAD_SAFE 0
#endif
#endif
#ifndef HAVE__STATIC_ASSERT
#define HAVE__STATIC_ASSERT \
(dns_has_extension(c_static_assert) || DNS_GNUC_PREREQ(4,6,0) || \
__C11FEATURES__ || __STDC_VERSION__ >= 201112L)
#endif
#ifndef HAVE_STATIC_ASSERT
#if DNS_GNUC_PREREQ(0,0,0) && !DNS_GNUC_PREREQ(4,6,0)
#define HAVE_STATIC_ASSERT 0 /* glibc doesn't check GCC version */
#elif defined(static_assert)
#define HAVE_STATIC_ASSERT 1
#else
#define HAVE_STATIC_ASSERT 0
#endif
#endif
#if HAVE_STATIC_ASSERT
#define dns_static_assert(cond, msg) static_assert(cond, msg)
#elif HAVE__STATIC_ASSERT
#define dns_static_assert(cond, msg) _Static_assert(cond, msg)
#else
#define dns_static_assert(cond, msg) extern char DNS_PP_XPASTE(dns_assert_, __LINE__)[sizeof (int[1 - 2*!(cond)])]
#endif
/*
* D E B U G M A C R O S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int *dns_debug_p(void) {
static int debug;
return &debug;
} /* dns_debug_p() */
#if DNS_DEBUG
#undef DNS_DEBUG
#define DNS_DEBUG dns_debug
#define DNS_SAY_(fmt, ...) \
do { if (DNS_DEBUG > 0) fprintf(stderr, fmt "%.1s", __func__, __LINE__, __VA_ARGS__); } while (0)
#define DNS_SAY(...) DNS_SAY_("@@ (%s:%d) " __VA_ARGS__, "\n")
#define DNS_HAI DNS_SAY("HAI")
#define DNS_SHOW_(P, fmt, ...) do { \
if (DNS_DEBUG > 1) { \
fprintf(stderr, "@@ BEGIN * * * * * * * * * * * *\n"); \
fprintf(stderr, "@@ " fmt "%.0s\n", __VA_ARGS__); \
dns_p_dump((P), stderr); \
fprintf(stderr, "@@ END * * * * * * * * * * * * *\n\n"); \
} \
} while (0)
#define DNS_SHOW(...) DNS_SHOW_(__VA_ARGS__, "")
#else /* !DNS_DEBUG */
#undef DNS_DEBUG
#define DNS_DEBUG 0
#define DNS_SAY(...)
#define DNS_HAI
#define DNS_SHOW(...)
#endif /* DNS_DEBUG */
#define DNS_CARP(...) DNS_SAY(__VA_ARGS__)
/*
* V E R S I O N R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
const char *dns_vendor(void) {
return DNS_VENDOR;
} /* dns_vendor() */
int dns_v_rel(void) {
return DNS_V_REL;
} /* dns_v_rel() */
int dns_v_abi(void) {
return DNS_V_ABI;
} /* dns_v_abi() */
int dns_v_api(void) {
return DNS_V_API;
} /* dns_v_api() */
/*
* E R R O R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef EPROTO
# define EPROTO EPROTONOSUPPORT
#endif
#if _WIN32
#define DNS_EINTR WSAEINTR
#define DNS_EINPROGRESS WSAEINPROGRESS
#define DNS_EISCONN WSAEISCONN
#define DNS_EWOULDBLOCK WSAEWOULDBLOCK
#define DNS_EALREADY WSAEALREADY
#define DNS_EAGAIN EAGAIN
#define DNS_ETIMEDOUT WSAETIMEDOUT
#define dns_syerr() ((int)GetLastError())
#define dns_soerr() ((int)WSAGetLastError())
#else
#define DNS_EINTR EINTR
#define DNS_EINPROGRESS EINPROGRESS
#define DNS_EISCONN EISCONN
#define DNS_EWOULDBLOCK EWOULDBLOCK
#define DNS_EALREADY EALREADY
#define DNS_EAGAIN EAGAIN
#define DNS_ETIMEDOUT ETIMEDOUT
#define dns_syerr() errno
#define dns_soerr() errno
#endif
const char *dns_strerror(int error) {
switch (error) {
case DNS_ENOBUFS:
return "DNS packet buffer too small";
case DNS_EILLEGAL:
return "Illegal DNS RR name or data";
case DNS_EORDER:
return "Attempt to push RR out of section order";
case DNS_ESECTION:
return "Invalid section specified";
case DNS_EUNKNOWN:
return "Unknown DNS error";
case DNS_EADDRESS:
return "Invalid textual address form";
case DNS_ENOQUERY:
return "Bad execution state (missing query packet)";
case DNS_ENOANSWER:
return "Bad execution state (missing answer packet)";
case DNS_EFETCHED:
return "Answer already fetched";
case DNS_ESERVICE:
return "The service passed was not recognized for the specified socket type";
case DNS_ENONAME:
return "The name does not resolve for the supplied parameters";
case DNS_EFAIL:
return "A non-recoverable error occurred when attempting to resolve the name";
case DNS_ECONNFIN:
return "Connection closed";
case DNS_EVERIFY:
return "Reply failed verification";
default:
return strerror(error);
} /* switch() */
} /* dns_strerror() */
/*
* A T O M I C R O U T I N E S
*
* Use GCC's __atomic built-ins if possible. Unlike the __sync built-ins, we
* can use the preprocessor to detect API and, more importantly, ISA
* support. We want to avoid linking headaches where the API depends on an
* external library if the ISA (e.g. i386) doesn't support lockless
* operation.
*
* TODO: Support C11's atomic API. Although that may require some finesse
* with how we define some public types, such as dns_atomic_t and struct
* dns_resolv_conf.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef HAVE___ATOMIC_FETCH_ADD
#ifdef __ATOMIC_RELAXED
#define HAVE___ATOMIC_FETCH_ADD 1
#else
#define HAVE___ATOMIC_FETCH_ADD 0
#endif
#endif
#ifndef HAVE___ATOMIC_FETCH_SUB
#define HAVE___ATOMIC_FETCH_SUB HAVE___ATOMIC_FETCH_ADD
#endif
#ifndef DNS_ATOMIC_FETCH_ADD
#if HAVE___ATOMIC_FETCH_ADD && __GCC_ATOMIC_LONG_LOCK_FREE == 2
#define DNS_ATOMIC_FETCH_ADD(i) __atomic_fetch_add((i), 1, __ATOMIC_RELAXED)
#else
#pragma message("no atomic_fetch_add available")
#define DNS_ATOMIC_FETCH_ADD(i) ((*(i))++)
#endif
#endif
#ifndef DNS_ATOMIC_FETCH_SUB
#if HAVE___ATOMIC_FETCH_SUB && __GCC_ATOMIC_LONG_LOCK_FREE == 2
#define DNS_ATOMIC_FETCH_SUB(i) __atomic_fetch_sub((i), 1, __ATOMIC_RELAXED)
#else
#pragma message("no atomic_fetch_sub available")
#define DNS_ATOMIC_FETCH_SUB(i) ((*(i))--)
#endif
#endif
static inline unsigned dns_atomic_fetch_add(dns_atomic_t *i) {
return DNS_ATOMIC_FETCH_ADD(i);
} /* dns_atomic_fetch_add() */
static inline unsigned dns_atomic_fetch_sub(dns_atomic_t *i) {
return DNS_ATOMIC_FETCH_SUB(i);
} /* dns_atomic_fetch_sub() */
/*
* C R Y P T O R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/*
* P R N G
*/
#ifndef DNS_RANDOM
#if defined(HAVE_ARC4RANDOM) \
|| defined(__OpenBSD__) \
|| defined(__FreeBSD__) \
|| defined(__NetBSD__) \
|| defined(__APPLE__)
#define DNS_RANDOM arc4random
#elif __linux
#define DNS_RANDOM random
#else
#define DNS_RANDOM rand
#endif
#endif
#define DNS_RANDOM_arc4random 1
#define DNS_RANDOM_random 2
#define DNS_RANDOM_rand 3
#define DNS_RANDOM_RAND_bytes 4
#define DNS_RANDOM_OPENSSL (DNS_RANDOM_RAND_bytes == DNS_PP_XPASTE(DNS_RANDOM_, DNS_RANDOM))
#if DNS_RANDOM_OPENSSL
#include <openssl/rand.h>
#endif
static unsigned dns_random_(void) {
#if DNS_RANDOM_OPENSSL
unsigned r;
_Bool ok;
ok = (1 == RAND_bytes((unsigned char *)&r, sizeof r));
assert(ok && "1 == RAND_bytes()");
return r;
#else
return DNS_RANDOM();
#endif
} /* dns_random_() */
dns_random_f **dns_random_p(void) {
static dns_random_f *random_f = &dns_random_;
return &random_f;
} /* dns_random_p() */
/*
* P E R M U T A T I O N G E N E R A T O R
*/
#define DNS_K_TEA_KEY_SIZE 16
#define DNS_K_TEA_BLOCK_SIZE 8
#define DNS_K_TEA_CYCLES 32
#define DNS_K_TEA_MAGIC 0x9E3779B9U
struct dns_k_tea {
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
unsigned cycles;
}; /* struct dns_k_tea */
static void dns_k_tea_init(struct dns_k_tea *tea, uint32_t key[], unsigned cycles) {
memcpy(tea->key, key, sizeof tea->key);
tea->cycles = (cycles)? cycles : DNS_K_TEA_CYCLES;
} /* dns_k_tea_init() */
static void dns_k_tea_encrypt(struct dns_k_tea *tea, uint32_t v[], uint32_t *w) {
uint32_t y, z, sum, n;
y = v[0];
z = v[1];
sum = 0;
for (n = 0; n < tea->cycles; n++) {
sum += DNS_K_TEA_MAGIC;
y += ((z << 4) + tea->key[0]) ^ (z + sum) ^ ((z >> 5) + tea->key[1]);
z += ((y << 4) + tea->key[2]) ^ (y + sum) ^ ((y >> 5) + tea->key[3]);
}
w[0] = y;
w[1] = z;
return /* void */;
} /* dns_k_tea_encrypt() */
/*
* Permutation generator, based on a Luby-Rackoff Feistel construction.
*
* Specifically, this is a generic balanced Feistel block cipher using TEA
* (another block cipher) as the pseudo-random function, F. At best it's as
* strong as F (TEA), notwithstanding the seeding. F could be AES, SHA-1, or
* perhaps Bernstein's Salsa20 core; I am naively trying to keep things
* simple.
*
* The generator can create a permutation of any set of numbers, as long as
* the size of the set is an even power of 2. This limitation arises either
* out of an inherent property of balanced Feistel constructions, or by my
* own ignorance. I'll tackle an unbalanced construction after I wrap my
* head around Schneier and Kelsey's paper.
*
* CAVEAT EMPTOR. IANAC.
*/
#define DNS_K_PERMUTOR_ROUNDS 8
struct dns_k_permutor {
unsigned stepi, length, limit;
unsigned shift, mask, rounds;
struct dns_k_tea tea;
}; /* struct dns_k_permutor */
static inline unsigned dns_k_permutor_powof(unsigned n) {
unsigned m, i = 0;
for (m = 1; m < n; m <<= 1, i++)
;;
return i;
} /* dns_k_permutor_powof() */
static void dns_k_permutor_init(struct dns_k_permutor *p, unsigned low, unsigned high) {
uint32_t key[DNS_K_TEA_KEY_SIZE / sizeof (uint32_t)];
unsigned width, i;
p->stepi = 0;
p->length = (high - low) + 1;
p->limit = high;
width = dns_k_permutor_powof(p->length);
width += width % 2;
p->shift = width / 2;
p->mask = (1U << p->shift) - 1;
p->rounds = DNS_K_PERMUTOR_ROUNDS;
for (i = 0; i < lengthof(key); i++)
key[i] = dns_random();
dns_k_tea_init(&p->tea, key, 0);
return /* void */;
} /* dns_k_permutor_init() */
static unsigned dns_k_permutor_F(struct dns_k_permutor *p, unsigned k, unsigned x) {
uint32_t in[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)], out[DNS_K_TEA_BLOCK_SIZE / sizeof (uint32_t)];
memset(in, '\0', sizeof in);
in[0] = k;
in[1] = x;
dns_k_tea_encrypt(&p->tea, in, out);
return p->mask & out[0];
} /* dns_k_permutor_F() */
static unsigned dns_k_permutor_E(struct dns_k_permutor *p, unsigned n) {
unsigned l[2], r[2];
unsigned i;
i = 0;
l[i] = p->mask & (n >> p->shift);
r[i] = p->mask & (n >> 0);
do {
l[(i + 1) % 2] = r[i % 2];
r[(i + 1) % 2] = l[i % 2] ^ dns_k_permutor_F(p, i, r[i % 2]);
i++;
} while (i < p->rounds - 1);
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
} /* dns_k_permutor_E() */
DNS_NOTUSED static unsigned dns_k_permutor_D(struct dns_k_permutor *p, unsigned n) {
unsigned l[2], r[2];
unsigned i;
i = p->rounds - 1;
l[i % 2] = p->mask & (n >> p->shift);
r[i % 2] = p->mask & (n >> 0);
do {
i--;
r[i % 2] = l[(i + 1) % 2];
l[i % 2] = r[(i + 1) % 2] ^ dns_k_permutor_F(p, i, l[(i + 1) % 2]);
} while (i > 0);
return ((l[i % 2] & p->mask) << p->shift) | ((r[i % 2] & p->mask) << 0);
} /* dns_k_permutor_D() */
static unsigned dns_k_permutor_step(struct dns_k_permutor *p) {
unsigned n;
do {
n = dns_k_permutor_E(p, p->stepi++);
} while (n >= p->length);
return n + (p->limit + 1 - p->length);
} /* dns_k_permutor_step() */
/*
* Simple permutation box. Useful for shuffling rrsets from an iterator.
* Uses AES s-box to provide good diffusion.
*
* Seems to pass muster under runs test.
*
* $ for i in 0 1 2 3 4 5 6 7 8 9; do ./dns shuffle-16 > /tmp/out; done
* $ R -q -f /dev/stdin 2>/dev/null <<-EOF | awk '/p-value/{ print $8 }'
* library(lawstat)
* runs.test(scan(file="/tmp/out"))
* EOF
*/
static unsigned short dns_k_shuffle16(unsigned short n, unsigned s) {
static const unsigned char sbox[256] =
{ 0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16 };
unsigned char a, b;
unsigned i;
a = 0xff & (n >> 0);
b = 0xff & (n >> 8);
for (i = 0; i < 4; i++) {
a ^= 0xff & s;
a = sbox[a] ^ b;
b = sbox[b] ^ a;
s >>= 8;
}
return ((0xff00 & (a << 8)) | (0x00ff & (b << 0)));
} /* dns_k_shuffle16() */
/*
* S T A T E M A C H I N E R O U T I N E S
*
* Application code should define DNS_SM_RESTORE and DNS_SM_SAVE, and the
* local variable pc.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_SM_ENTER \
do { \
static const int pc0 = __LINE__; \
DNS_SM_RESTORE; \
switch (pc0 + pc) { \
case __LINE__: (void)0
#define DNS_SM_SAVE_AND_DO(do_statement) \
do { \
pc = __LINE__ - pc0; \
DNS_SM_SAVE; \
do_statement; \
case __LINE__: (void)0; \
} while (0)
#define DNS_SM_YIELD(rv) \
DNS_SM_SAVE_AND_DO(return (rv))
#define DNS_SM_EXIT \
do { goto leave; } while (0)
#define DNS_SM_LEAVE \
leave: (void)0; \
DNS_SM_SAVE_AND_DO(break); \
} \
} while (0)
/*
* U T I L I T Y R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_MAXINTERVAL 300
struct dns_clock {
time_t sample, elapsed;
}; /* struct dns_clock */
static void dns_begin(struct dns_clock *clk) {
clk->sample = time(0);
clk->elapsed = 0;
} /* dns_begin() */
static time_t dns_elapsed(struct dns_clock *clk) {
time_t curtime;
if ((time_t)-1 == time(&curtime))
return clk->elapsed;
if (curtime > clk->sample)
clk->elapsed += (time_t)DNS_PP_MIN(difftime(curtime, clk->sample), DNS_MAXINTERVAL);
clk->sample = curtime;
return clk->elapsed;
} /* dns_elapsed() */
DNS_NOTUSED static size_t dns_strnlen(const char *src, size_t m) {
size_t n = 0;
while (*src++ && n < m)
++n;
return n;
} /* dns_strnlen() */
DNS_NOTUSED static size_t dns_strnlcpy(char *dst, size_t lim, const char *src, size_t max) {
size_t len = dns_strnlen(src, max), n;
if (lim > 0) {
n = DNS_PP_MIN(lim - 1, len);
memcpy(dst, src, n);
dst[n] = '\0';
}
return len;
} /* dns_strnlcpy() */
#if (defined AF_UNIX && !defined _WIN32)
#define DNS_HAVE_SOCKADDR_UN 1
#else
#define DNS_HAVE_SOCKADDR_UN 0
#endif
static size_t dns_af_len(int af) {
static const size_t table[AF_MAX] = {
[AF_INET6] = sizeof (struct sockaddr_in6),
[AF_INET] = sizeof (struct sockaddr_in),
#if DNS_HAVE_SOCKADDR_UN
[AF_UNIX] = sizeof (struct sockaddr_un),
#endif
};
return table[af];
} /* dns_af_len() */
#define dns_sa_family(sa) (((struct sockaddr *)(sa))->sa_family)
#define dns_sa_len(sa) dns_af_len(dns_sa_family(sa))
#define DNS_SA_NOPORT &dns_sa_noport
static unsigned short dns_sa_noport;
static unsigned short *dns_sa_port(int af, void *sa) {
switch (af) {
case AF_INET6:
return &((struct sockaddr_in6 *)sa)->sin6_port;
case AF_INET:
return &((struct sockaddr_in *)sa)->sin_port;
default:
return DNS_SA_NOPORT;
}
} /* dns_sa_port() */
static void *dns_sa_addr(int af, const void *sa, socklen_t *size) {
switch (af) {
case AF_INET6: {
struct in6_addr *in6 = &((struct sockaddr_in6 *)sa)->sin6_addr;
if (size)
*size = sizeof *in6;
return in6;
}
case AF_INET: {
struct in_addr *in = &((struct sockaddr_in *)sa)->sin_addr;
if (size)
*size = sizeof *in;
return in;
}
default:
if (size)
*size = 0;
return 0;
}
} /* dns_sa_addr() */
#if DNS_HAVE_SOCKADDR_UN
#define DNS_SUNPATHMAX (sizeof ((struct sockaddr_un *)0)->sun_path)
#endif
DNS_NOTUSED static void *dns_sa_path(void *sa, socklen_t *size) {
switch (dns_sa_family(sa)) {
#if DNS_HAVE_SOCKADDR_UN
case AF_UNIX: {
char *path = ((struct sockaddr_un *)sa)->sun_path;
if (size)
*size = dns_strnlen(path, DNS_SUNPATHMAX);
return path;
}
#endif
default:
if (size)
*size = 0;
return NULL;
}
} /* dns_sa_path() */
static int dns_sa_cmp(void *a, void *b) {
int cmp, af;
if ((cmp = dns_sa_family(a) - dns_sa_family(b)))
return cmp;
switch ((af = dns_sa_family(a))) {
case AF_INET: {
struct in_addr *a4, *b4;
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
return cmp;
a4 = dns_sa_addr(af, a, NULL);
b4 = dns_sa_addr(af, b, NULL);
if (ntohl(a4->s_addr) < ntohl(b4->s_addr))
return -1;
if (ntohl(a4->s_addr) > ntohl(b4->s_addr))
return 1;
return 0;
}
case AF_INET6: {
struct in6_addr *a6, *b6;
size_t i;
if ((cmp = htons(*dns_sa_port(af, a)) - htons(*dns_sa_port(af, b))))
return cmp;
a6 = dns_sa_addr(af, a, NULL);
b6 = dns_sa_addr(af, b, NULL);
/* XXX: do we need to use in6_clearscope()? */
for (i = 0; i < sizeof a6->s6_addr; i++) {
if ((cmp = a6->s6_addr[i] - b6->s6_addr[i]))
return cmp;
}
return 0;
}
#if DNS_HAVE_SOCKADDR_UN
case AF_UNIX: {
char a_path[DNS_SUNPATHMAX + 1], b_path[sizeof a_path];
dns_strnlcpy(a_path, sizeof a_path, dns_sa_path(a, NULL), DNS_SUNPATHMAX);
dns_strnlcpy(b_path, sizeof b_path, dns_sa_path(b, NULL), DNS_SUNPATHMAX);
return strcmp(a_path, b_path);
}
#endif
default:
return -1;
}
} /* dns_sa_cmp() */
#if _WIN32
static int dns_inet_pton(int af, const void *src, void *dst) {
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
int size_of_u = (int)sizeof u;
u.sin.sin_family = af;
if (0 != WSAStringToAddressA((void *)src, af, (void *)0, (struct sockaddr *)&u, &size_of_u))
return -1;
switch (af) {
case AF_INET6:
*(struct in6_addr *)dst = u.sin6.sin6_addr;
return 1;
case AF_INET:
*(struct in_addr *)dst = u.sin.sin_addr;
return 1;
default:
return 0;
}
} /* dns_inet_pton() */
static const char *dns_inet_ntop(int af, const void *src, void *dst, unsigned long lim) {
union { struct sockaddr_in sin; struct sockaddr_in6 sin6; } u;
/* NOTE: WSAAddressToString will print .sin_port unless zeroed. */
memset(&u, 0, sizeof u);
u.sin.sin_family = af;
switch (af) {
case AF_INET6:
u.sin6.sin6_addr = *(struct in6_addr *)src;
break;
case AF_INET:
u.sin.sin_addr = *(struct in_addr *)src;
break;
default:
return 0;
}
if (0 != WSAAddressToStringA((struct sockaddr *)&u, dns_sa_len(&u), (void *)0, dst, &lim))
return 0;
return dst;
} /* dns_inet_ntop() */
#else
#define dns_inet_pton(...) inet_pton(__VA_ARGS__)
#define dns_inet_ntop(...) inet_ntop(__VA_ARGS__)
#endif
static dns_error_t dns_pton(int af, const void *src, void *dst) {
switch (dns_inet_pton(af, src, dst)) {
case 1:
return 0;
case -1:
return dns_soerr();
default:
return DNS_EADDRESS;
}
} /* dns_pton() */
static dns_error_t dns_ntop(int af, const void *src, void *dst, unsigned long lim) {
return (dns_inet_ntop(af, src, dst, lim))? 0 : dns_soerr();
} /* dns_ntop() */
size_t dns_strlcpy(char *dst, const char *src, size_t lim) {
char *d = dst;
char *e = &dst[lim];
const char *s = src;
if (d < e) {
do {
if ('\0' == (*d++ = *s++))
return s - src - 1;
} while (d < e);
d[-1] = '\0';
}
while (*s++ != '\0')
;;
return s - src - 1;
} /* dns_strlcpy() */
size_t dns_strlcat(char *dst, const char *src, size_t lim) {
char *d = memchr(dst, '\0', lim);
char *e = &dst[lim];
const char *s = src;
const char *p;
if (d && d < e) {
do {
if ('\0' == (*d++ = *s++))
return d - dst - 1;
} while (d < e);
d[-1] = '\0';
}
p = s;
while (*s++ != '\0')
;;
return lim + (s - p - 1);
} /* dns_strlcat() */
static void *dns_reallocarray(void *p, size_t nmemb, size_t size, dns_error_t *error) {
void *rp;
if (nmemb > 0 && SIZE_MAX / nmemb < size) {
*error = EOVERFLOW;
return NULL;
}
if (!(rp = realloc(p, nmemb * size)))
*error = (errno)? errno : EINVAL;
return rp;
} /* dns_reallocarray() */
#if _WIN32
static char *dns_strsep(char **sp, const char *delim) {
char *p;
if (!(p = *sp))
return 0;
*sp += strcspn(p, delim);
if (**sp != '\0') {
**sp = '\0';
++*sp;
} else
*sp = NULL;
return p;
} /* dns_strsep() */
#else
#define dns_strsep(...) strsep(__VA_ARGS__)
#endif
#if _WIN32
#define strcasecmp(...) _stricmp(__VA_ARGS__)
#define strncasecmp(...) _strnicmp(__VA_ARGS__)
#endif
static inline _Bool dns_isalpha(unsigned char c) {
return isalpha(c);
} /* dns_isalpha() */
static inline _Bool dns_isdigit(unsigned char c) {
return isdigit(c);
} /* dns_isdigit() */
static inline _Bool dns_isalnum(unsigned char c) {
return isalnum(c);
} /* dns_isalnum() */
static inline _Bool dns_isspace(unsigned char c) {
return isspace(c);
} /* dns_isspace() */
static inline _Bool dns_isgraph(unsigned char c) {
return isgraph(c);
} /* dns_isgraph() */
static int dns_poll(int fd, short events, int timeout) {
fd_set rset, wset;
struct timeval tv = { timeout, 0 };
if (!events)
return 0;
if (fd < 0 || (unsigned)fd >= FD_SETSIZE)
return EINVAL;
FD_ZERO(&rset);
FD_ZERO(&wset);
if (events & DNS_POLLIN)
FD_SET(fd, &rset);
if (events & DNS_POLLOUT)
FD_SET(fd, &wset);
select(fd + 1, &rset, &wset, 0, (timeout >= 0)? &tv : NULL);
return 0;
} /* dns_poll() */
#if !_WIN32
DNS_NOTUSED static int dns_sigmask(int how, const sigset_t *set, sigset_t *oset) {
#if DNS_THREAD_SAFE
return pthread_sigmask(how, set, oset);
#else
return (0 == sigprocmask(how, set, oset))? 0 : errno;
#endif
} /* dns_sigmask() */
#endif
static size_t dns_send(int fd, const void *src, size_t len, int flags, dns_error_t *error) {
long n = send(fd, src, len, flags);
if (n < 0) {
*error = dns_soerr();
return 0;
} else {
*error = 0;
return n;
}
} /* dns_send() */
static size_t dns_recv(int fd, void *dst, size_t lim, int flags, dns_error_t *error) {
long n = recv(fd, dst, lim, flags);
if (n < 0) {
*error = dns_soerr();
return 0;
} else if (n == 0) {
*error = (lim > 0)? DNS_ECONNFIN : EINVAL;
return 0;
} else {
*error = 0;
return n;
}
} /* dns_recv() */
static size_t dns_send_nopipe(int fd, const void *src, size_t len, int flags, dns_error_t *_error) {
#if _WIN32 || !defined SIGPIPE || defined SO_NOSIGPIPE
return dns_send(fd, src, len, flags, _error);
#elif defined MSG_NOSIGNAL
return dns_send(fd, src, len, (flags|MSG_NOSIGNAL), _error);
#elif _POSIX_REALTIME_SIGNALS > 0 /* require sigtimedwait */
/*
* SIGPIPE handling similar to the approach described in
* http://krokisplace.blogspot.com/2010/02/suppressing-sigpipe-in-library.html
*/
sigset_t pending, blocked, piped;
size_t count;
int error;
sigemptyset(&pending);
sigpending(&pending);
if (!sigismember(&pending, SIGPIPE)) {
sigemptyset(&piped);
sigaddset(&piped, SIGPIPE);
sigemptyset(&blocked);
if ((error = dns_sigmask(SIG_BLOCK, &piped, &blocked)))
goto error;
}
count = dns_send(fd, src, len, flags, &error);
if (!sigismember(&pending, SIGPIPE)) {
int saved = error;
const struct timespec ts = { 0, 0 };
if (!count && error == EPIPE) {
while (-1 == sigtimedwait(&piped, NULL, &ts) && errno == EINTR)
;;
}
if ((error = dns_sigmask(SIG_SETMASK, &blocked, NULL)))
goto error;
error = saved;
}
*_error = error;
return count;
error:
*_error = error;
return 0;
#else
#error "unable to suppress SIGPIPE"
return dns_send(fd, src, len, flags, _error);
#endif
} /* dns_send_nopipe() */
static dns_error_t dns_connect(int fd, const struct sockaddr *addr, socklen_t addrlen) {
if (0 != connect(fd, addr, addrlen))
return dns_soerr();
return 0;
} /* dns_connect() */
#define DNS_FOPEN_STDFLAGS "rwabt+"
static dns_error_t dns_fopen_addflag(char *dst, const char *src, size_t lim, int fc) {
char *p = dst, *pe = dst + lim;
/* copy standard flags */
while (*src && strchr(DNS_FOPEN_STDFLAGS, *src)) {
if (!(p < pe))
return ENOMEM;
*p++ = *src++;
}
/* append flag to standard flags */
if (!(p < pe))
return ENOMEM;
*p++ = fc;
/* copy remaining mode string, including '\0' */
do {
if (!(p < pe))
return ENOMEM;
} while ((*p++ = *src++));
return 0;
} /* dns_fopen_addflag() */
static FILE *dns_fopen(const char *path, const char *mode, dns_error_t *_error) {
FILE *fp;
char mode_cloexec[32];
int error;
assert(path && mode && *mode);
if (!*path) {
error = EINVAL;
goto error;
}
#if _WIN32 || _WIN64
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'N')))
goto error;
if (!(fp = fopen(path, mode_cloexec)))
goto syerr;
#else
if ((error = dns_fopen_addflag(mode_cloexec, mode, sizeof mode_cloexec, 'e')))
goto error;
if (!(fp = fopen(path, mode_cloexec))) {
if (errno != EINVAL)
goto syerr;
if (!(fp = fopen(path, mode)))
goto syerr;
}
#endif
return fp;
syerr:
error = dns_syerr();
error:
*_error = error;
return NULL;
} /* dns_fopen() */
struct dns_hxd_lines_i {
int pc;
size_t p;
};
#define DNS_SM_RESTORE \
do { \
pc = state->pc; \
sp = src + state->p; \
se = src + len; \
} while (0)
#define DNS_SM_SAVE \
do { \
state->p = sp - src; \
state->pc = pc; \
} while (0)
static size_t dns_hxd_lines(void *dst, size_t lim, const unsigned char *src, size_t len, struct dns_hxd_lines_i *state) {
static const unsigned char hex[] = "0123456789abcdef";
static const unsigned char tmpl[] = " | |\n";
unsigned char ln[sizeof tmpl];
const unsigned char *sp, *se;
unsigned char *h, *g;
unsigned i, n;
int pc;
DNS_SM_ENTER;
while (sp < se) {
memcpy(ln, tmpl, sizeof ln);
h = &ln[2];
g = &ln[53];
for (n = 0; n < 2; n++) {
for (i = 0; i < 8 && se - sp > 0; i++, sp++) {
h[0] = hex[0x0f & (*sp >> 4)];
h[1] = hex[0x0f & (*sp >> 0)];
h += 3;
*g++ = (dns_isgraph(*sp))? *sp : '.';
}
h++;
}
n = dns_strlcpy(dst, (char *)ln, lim);
DNS_SM_YIELD(n);
}
DNS_SM_EXIT;
DNS_SM_LEAVE;
return 0;
}
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
/*
* A R I T H M E T I C R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_CHECK_OVERFLOW(error, r, f, ...) \
do { \
uintmax_t _r; \
*(error) = f(&_r, __VA_ARGS__); \
*(r) = _r; \
} while (0)
static dns_error_t dns_clamp_overflow(uintmax_t *r, uintmax_t n, uintmax_t clamp) {
if (n > clamp) {
*r = clamp;
return ERANGE;
} else {
*r = n;
return 0;
}
} /* dns_clamp_overflow() */
static dns_error_t dns_add_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
if (~a < b) {
*r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
return ERANGE;
} else {
return dns_clamp_overflow(r, a + b, clamp);
}
} /* dns_add_overflow() */
static dns_error_t dns_mul_overflow(uintmax_t *r, uintmax_t a, uintmax_t b, uintmax_t clamp) {
if (a > 0 && UINTMAX_MAX / a < b) {
*r = DNS_PP_MIN(clamp, ~UINTMAX_C(0));
return ERANGE;
} else {
return dns_clamp_overflow(r, a * b, clamp);
}
} /* dns_mul_overflow() */
/*
* F I X E D - S I Z E D B U F F E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_B_INIT(src, n) { \
(unsigned char *)(src), \
(unsigned char *)(src), \
(unsigned char *)(src) + (n), \
}
#define DNS_B_FROM(src, n) DNS_B_INIT((src), (n))
#define DNS_B_INTO(src, n) DNS_B_INIT((src), (n))
struct dns_buf {
const unsigned char *base;
unsigned char *p;
const unsigned char *pe;
dns_error_t error;
size_t overflow;
}; /* struct dns_buf */
static inline size_t
dns_b_tell(struct dns_buf *b)
{
return b->p - b->base;
}
static inline dns_error_t
dns_b_setoverflow(struct dns_buf *b, size_t n, dns_error_t error)
{
b->overflow += n;
return b->error = error;
}
DNS_NOTUSED static struct dns_buf *
dns_b_into(struct dns_buf *b, void *src, size_t n)
{
*b = (struct dns_buf)DNS_B_INTO(src, n);
return b;
}
static dns_error_t
dns_b_putc(struct dns_buf *b, unsigned char uc)
{
if (!(b->p < b->pe))
return dns_b_setoverflow(b, 1, DNS_ENOBUFS);
*b->p++ = uc;
return 0;
}
static dns_error_t
dns_b_pputc(struct dns_buf *b, unsigned char uc, size_t p)
{
size_t pe = b->pe - b->base;
if (pe <= p)
return dns_b_setoverflow(b, p - pe + 1, DNS_ENOBUFS);
*((unsigned char *)b->base + p) = uc;
return 0;
}
static inline dns_error_t
dns_b_put16(struct dns_buf *b, uint16_t u)
{
return dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
}
static inline dns_error_t
dns_b_pput16(struct dns_buf *b, uint16_t u, size_t p)
{
if (dns_b_pputc(b, u >> 8, p) || dns_b_pputc(b, u >> 0, p + 1))
return b->error;
return 0;
}
DNS_NOTUSED static inline dns_error_t
dns_b_put32(struct dns_buf *b, uint32_t u)
{
return dns_b_putc(b, u >> 24), dns_b_putc(b, u >> 16),
dns_b_putc(b, u >> 8), dns_b_putc(b, u >> 0);
}
static dns_error_t
dns_b_put(struct dns_buf *b, const void *src, size_t len)
{
size_t n = DNS_PP_MIN((size_t)(b->pe - b->p), len);
memcpy(b->p, src, n);
b->p += n;
if (n < len)
return dns_b_setoverflow(b, len - n, DNS_ENOBUFS);
return 0;
}
static dns_error_t
dns_b_puts(struct dns_buf *b, const void *src)
{
return dns_b_put(b, src, strlen(src));
}
DNS_NOTUSED static inline dns_error_t
dns_b_fmtju(struct dns_buf *b, const uintmax_t u, const unsigned width)
{
size_t digits, padding, overflow;
uintmax_t r;
unsigned char *tp, *te, tc;
digits = 0;
r = u;
do {
digits++;
r /= 10;
} while (r);
padding = width - DNS_PP_MIN(digits, width);
overflow = (digits + padding) - DNS_PP_MIN((size_t)(b->pe - b->p), (digits + padding));
while (padding--) {
dns_b_putc(b, '0');
}
digits = 0;
tp = b->p;
r = u;
do {
if (overflow < ++digits)
dns_b_putc(b, '0' + (r % 10));
r /= 10;
} while (r);
te = b->p;
while (tp < te) {
tc = *--te;
*te = *tp;
*tp++ = tc;
}
return b->error;
}
static void
dns_b_popc(struct dns_buf *b)
{
if (b->overflow && !--b->overflow)
b->error = 0;
if (b->p > b->base)
b->p--;
}
static inline const char *
dns_b_tolstring(struct dns_buf *b, size_t *n)
{
if (b->p < b->pe) {
*b->p = '\0';
*n = b->p - b->base;
return (const char *)b->base;
} else if (b->p > b->base) {
if (b->p[-1] != '\0') {
dns_b_setoverflow(b, 1, DNS_ENOBUFS);
b->p[-1] = '\0';
}
*n = &b->p[-1] - b->base;
return (const char *)b->base;
} else {
*n = 0;
return "";
}
}
static inline const char *
dns_b_tostring(struct dns_buf *b)
{
size_t n;
return dns_b_tolstring(b, &n);
}
static inline size_t
dns_b_strlen(struct dns_buf *b)
{
size_t n;
dns_b_tolstring(b, &n);
return n;
}
static inline size_t
dns_b_strllen(struct dns_buf *b)
{
size_t n = dns_b_strlen(b);
return n + b->overflow;
}
DNS_NOTUSED static const struct dns_buf *
dns_b_from(const struct dns_buf *b, const void *src, size_t n)
{
*(struct dns_buf *)b = (struct dns_buf)DNS_B_FROM(src, n);
return b;
}
static inline int
dns_b_getc(const struct dns_buf *_b, const int eof)
{
struct dns_buf *b = (struct dns_buf *)_b;
if (!(b->p < b->pe))
return dns_b_setoverflow(b, 1, DNS_EILLEGAL), eof;
return *b->p++;
}
static inline intmax_t
dns_b_get16(const struct dns_buf *b, const intmax_t eof)
{
intmax_t n;
n = (dns_b_getc(b, 0) << 8);
n |= (dns_b_getc(b, 0) << 0);
return (!b->overflow)? n : eof;
}
DNS_NOTUSED static inline intmax_t
dns_b_get32(const struct dns_buf *b, const intmax_t eof)
{
intmax_t n;
n = (dns_b_get16(b, 0) << 16);
n |= (dns_b_get16(b, 0) << 0);
return (!b->overflow)? n : eof;
}
static inline dns_error_t
dns_b_move(struct dns_buf *dst, const struct dns_buf *_src, size_t n)
{
struct dns_buf *src = (struct dns_buf *)_src;
size_t src_n = DNS_PP_MIN((size_t)(src->pe - src->p), n);
size_t src_r = n - src_n;
dns_b_put(dst, src->p, src_n);
src->p += src_n;
if (src_r)
return dns_b_setoverflow(src, src_r, DNS_EILLEGAL);
return dst->error;
}
/*
* T I M E R O U T I N E S
*
* Most functions still rely on the older time routines defined in the
* utility routines section, above.
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_TIME_C(n) UINT64_C(n)
#define DNS_TIME_INF (~DNS_TIME_C(0))
typedef uint64_t dns_time_t;
typedef dns_time_t dns_microseconds_t;
static dns_error_t dns_time_add(dns_time_t *r, dns_time_t a, dns_time_t b) {
int error;
DNS_CHECK_OVERFLOW(&error, r, dns_add_overflow, a, b, DNS_TIME_INF);
return error;
}
static dns_error_t dns_time_mul(dns_time_t *r, dns_time_t a, dns_time_t b) {
int error;
DNS_CHECK_OVERFLOW(&error, r, dns_mul_overflow, a, b, DNS_TIME_INF);
return error;
}
static dns_error_t dns_time_diff(dns_time_t *r, dns_time_t a, dns_time_t b) {
if (a < b) {
*r = DNS_TIME_C(0);
return ERANGE;
} else {
*r = a - b;
return 0;
}
}
static dns_microseconds_t dns_ts2us(const struct timespec *ts, _Bool rup) {
if (ts) {
dns_time_t sec = DNS_PP_MAX(0, ts->tv_sec);
dns_time_t nsec = DNS_PP_MAX(0, ts->tv_nsec);
dns_time_t usec = nsec / 1000;
dns_microseconds_t r;
if (rup && nsec % 1000 > 0)
usec++;
dns_time_mul(&r, sec, DNS_TIME_C(1000000));
dns_time_add(&r, r, usec);
return r;
} else {
return DNS_TIME_INF;
}
} /* dns_ts2us() */
static struct timespec *dns_tv2ts(struct timespec *ts, const struct timeval *tv) {
if (tv) {
ts->tv_sec = tv->tv_sec;
ts->tv_nsec = tv->tv_usec * 1000;
return ts;
} else {
return NULL;
}
} /* dns_tv2ts() */
static size_t dns_utime_print(void *_dst, size_t lim, dns_microseconds_t us) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, us / 1000000, 1);
dns_b_putc(&dst, '.');
dns_b_fmtju(&dst, us % 1000000, 6);
return dns_b_strllen(&dst);
} /* dns_utime_print() */
/*
* P A C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
unsigned dns_p_count(struct dns_packet *P, enum dns_section section) {
unsigned count;
switch (section) {
case DNS_S_QD:
return ntohs(dns_header(P)->qdcount);
case DNS_S_AN:
return ntohs(dns_header(P)->ancount);
case DNS_S_NS:
return ntohs(dns_header(P)->nscount);
case DNS_S_AR:
return ntohs(dns_header(P)->arcount);
default:
count = 0;
if (section & DNS_S_QD)
count += ntohs(dns_header(P)->qdcount);
if (section & DNS_S_AN)
count += ntohs(dns_header(P)->ancount);
if (section & DNS_S_NS)
count += ntohs(dns_header(P)->nscount);
if (section & DNS_S_AR)
count += ntohs(dns_header(P)->arcount);
return count;
}
} /* dns_p_count() */
struct dns_packet *dns_p_init(struct dns_packet *P, size_t size) {
if (!P)
return 0;
assert(size >= offsetof(struct dns_packet, data) + 12);
memset(P, 0, sizeof *P);
P->size = size - offsetof(struct dns_packet, data);
P->end = 12;
memset(P->data, '\0', 12);
return P;
} /* dns_p_init() */
static struct dns_packet *dns_p_reset(struct dns_packet *P) {
return dns_p_init(P, offsetof(struct dns_packet, data) + P->size);
} /* dns_p_reset() */
static unsigned short dns_p_qend(struct dns_packet *P) {
unsigned short qend = 12;
unsigned i, count = dns_p_count(P, DNS_S_QD);
for (i = 0; i < count && qend < P->end; i++) {
if (P->end == (qend = dns_d_skip(qend, P)))
goto invalid;
if (P->end - qend < 4)
goto invalid;
qend += 4;
}
return DNS_PP_MIN(qend, P->end);
invalid:
return P->end;
} /* dns_p_qend() */
struct dns_packet *dns_p_make(size_t len, int *error) {
struct dns_packet *P;
size_t size = dns_p_calcsize(len);
if (!(P = dns_p_init(malloc(size), size)))
*error = dns_syerr();
return P;
} /* dns_p_make() */
static void dns_p_free(struct dns_packet *P) {
free(P);
} /* dns_p_free() */
/* convenience routine to free any existing packet before storing new packet */
static struct dns_packet *dns_p_setptr(struct dns_packet **dst, struct dns_packet *src) {
dns_p_free(*dst);
*dst = src;
return src;
} /* dns_p_setptr() */
static struct dns_packet *dns_p_movptr(struct dns_packet **dst, struct dns_packet **src) {
dns_p_setptr(dst, *src);
*src = NULL;
return *dst;
} /* dns_p_movptr() */
int dns_p_grow(struct dns_packet **P) {
struct dns_packet *tmp;
size_t size;
int error;
if (!*P) {
if (!(*P = dns_p_make(DNS_P_QBUFSIZ, &error)))
return error;
return 0;
}
size = dns_p_sizeof(*P);
size |= size >> 1;
size |= size >> 2;
size |= size >> 4;
size |= size >> 8;
size++;
if (size > 65536)
return DNS_ENOBUFS;
if (!(tmp = realloc(*P, dns_p_calcsize(size))))
return dns_syerr();
tmp->size = size;
*P = tmp;
return 0;
} /* dns_p_grow() */
struct dns_packet *dns_p_copy(struct dns_packet *P, const struct dns_packet *P0) {
if (!P)
return 0;
P->end = DNS_PP_MIN(P->size, P0->end);
memcpy(P->data, P0->data, P->end);
return P;
} /* dns_p_copy() */
struct dns_packet *dns_p_merge(struct dns_packet *A, enum dns_section Amask, struct dns_packet *B, enum dns_section Bmask, int *error_) {
size_t bufsiz = DNS_PP_MIN(65535, ((A)? A->end : 0) + ((B)? B->end : 0));
struct dns_packet *M;
enum dns_section section;
struct dns_rr rr, mr;
int error, copy;
if (!A && B) {
A = B;
Amask = Bmask;
B = 0;
}
merge:
if (!(M = dns_p_make(bufsiz, &error)))
goto error;
for (section = DNS_S_QD; (DNS_S_ALL & section); section <<= 1) {
if (A && (section & Amask)) {
dns_rr_foreach(&rr, A, .section = section) {
if ((error = dns_rr_copy(M, &rr, A)))
goto error;
}
}
if (B && (section & Bmask)) {
dns_rr_foreach(&rr, B, .section = section) {
copy = 1;
dns_rr_foreach(&mr, M, .type = rr.type, .section = DNS_S_ALL) {
if (!(copy = dns_rr_cmp(&rr, B, &mr, M)))
break;
}
if (copy && (error = dns_rr_copy(M, &rr, B)))
goto error;
}
}
}
return M;
error:
dns_p_setptr(&M, NULL);
if (error == DNS_ENOBUFS && bufsiz < 65535) {
bufsiz = DNS_PP_MIN(65535, bufsiz * 2);
goto merge;
}
*error_ = error;
return 0;
} /* dns_p_merge() */
static unsigned short dns_l_skip(unsigned short, const unsigned char *, size_t);
void dns_p_dictadd(struct dns_packet *P, unsigned short dn) {
unsigned short lp, lptr, i;
lp = dn;
while (lp < P->end) {
if (0xc0 == (0xc0 & P->data[lp]) && P->end - lp >= 2 && lp != dn) {
lptr = ((0x3f & P->data[lp + 0]) << 8)
| ((0xff & P->data[lp + 1]) << 0);
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
if (P->dict[i] == lptr) {
P->dict[i] = dn;
return;
}
}
}
lp = dns_l_skip(lp, P->data, P->end);
}
for (i = 0; i < lengthof(P->dict); i++) {
if (!P->dict[i]) {
P->dict[i] = dn;
break;
}
}
} /* dns_p_dictadd() */
static inline uint16_t
plus1_ns (uint16_t count_net)
{
uint16_t count = ntohs (count_net);
count++;
return htons (count);
}
int dns_p_push(struct dns_packet *P, enum dns_section section, const void *dn, size_t dnlen, enum dns_type type, enum dns_class class, unsigned ttl, const void *any) {
size_t end = P->end;
int error;
if ((error = dns_d_push(P, dn, dnlen)))
goto error;
if (P->size - P->end < 4)
goto nobufs;
P->data[P->end++] = 0xff & (type >> 8);
P->data[P->end++] = 0xff & (type >> 0);
P->data[P->end++] = 0xff & (class >> 8);
P->data[P->end++] = 0xff & (class >> 0);
if (section == DNS_S_QD)
goto update;
if (P->size - P->end < 6)
goto nobufs;
if (type != DNS_T_OPT)
ttl = DNS_PP_MIN(ttl, 0x7fffffffU);
P->data[P->end++] = ttl >> 24;
P->data[P->end++] = ttl >> 16;
P->data[P->end++] = ttl >> 8;
P->data[P->end++] = ttl >> 0;
if ((error = dns_any_push(P, (union dns_any *)any, type)))
goto error;
update:
switch (section) {
case DNS_S_QD:
if (dns_p_count(P, DNS_S_AN|DNS_S_NS|DNS_S_AR))
goto order;
if (!P->memo.qd.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->qdcount = plus1_ns (dns_header(P)->qdcount);
P->memo.qd.end = P->end;
P->memo.an.base = P->end;
P->memo.an.end = P->end;
P->memo.ns.base = P->end;
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_AN:
if (dns_p_count(P, DNS_S_NS|DNS_S_AR))
goto order;
if (!P->memo.an.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->ancount = plus1_ns (dns_header(P)->ancount);
P->memo.an.end = P->end;
P->memo.ns.base = P->end;
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_NS:
if (dns_p_count(P, DNS_S_AR))
goto order;
if (!P->memo.ns.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->nscount = plus1_ns (dns_header(P)->nscount);
P->memo.ns.end = P->end;
P->memo.ar.base = P->end;
P->memo.ar.end = P->end;
break;
case DNS_S_AR:
if (!P->memo.ar.base && (error = dns_p_study(P)))
goto error;
dns_header(P)->arcount = plus1_ns (dns_header(P)->arcount);
P->memo.ar.end = P->end;
if (type == DNS_T_OPT && !P->memo.opt.p) {
P->memo.opt.p = end;
P->memo.opt.maxudp = class;
P->memo.opt.ttl = ttl;
}
break;
default:
error = DNS_ESECTION;
goto error;
} /* switch() */
return 0;
nobufs:
error = DNS_ENOBUFS;
goto error;
order:
error = DNS_EORDER;
goto error;
error:
P->end = end;
return error;
} /* dns_p_push() */
#define DNS_SM_RESTORE do { pc = state->pc; error = state->error; } while (0)
#define DNS_SM_SAVE do { state->error = error; state->pc = pc; } while (0)
struct dns_p_lines_i {
int pc;
enum dns_section section;
struct dns_rr rr;
int error;
};
static size_t dns_p_lines_fmt(void *dst, size_t lim, dns_error_t *_error, const char *fmt, ...) {
va_list ap;
int error = 0, n;
va_start(ap, fmt);
if ((n = vsnprintf(dst, lim, fmt, ap)) < 0)
error = errno;
va_end(ap);
*_error = error;
return DNS_PP_MAX(n, 0);
} /* dns_p_lines_fmt() */
#define DNS_P_LINE(...) \
do { \
len = dns_p_lines_fmt(dst, lim, &error, __VA_ARGS__); \
if (len == 0 && error) \
goto error; \
DNS_SM_YIELD(len); \
} while (0)
static size_t dns_p_lines(void *dst, size_t lim, dns_error_t *_error, struct dns_packet *P, struct dns_rr_i *I, struct dns_p_lines_i *state) {
int error, pc;
size_t len;
char __dst[DNS_STRMAXLEN + 1] = { 0 };
*_error = 0;
DNS_SM_ENTER;
DNS_P_LINE(";; [HEADER]\n");
DNS_P_LINE(";; qid : %d\n", ntohs(dns_header(P)->qid));
DNS_P_LINE(";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
DNS_P_LINE(";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
DNS_P_LINE(";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
DNS_P_LINE(";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
DNS_P_LINE(";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
DNS_P_LINE(";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
DNS_P_LINE(";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
while (dns_rr_grep(&state->rr, 1, I, P, &error)) {
if (state->section != state->rr.section) {
DNS_P_LINE("\n");
DNS_P_LINE(";; [%s:%d]\n", dns_strsection(state->rr.section, __dst), dns_p_count(P, state->rr.section));
}
if (!(len = dns_rr_print(dst, lim, &state->rr, P, &error)))
goto error;
dns_strlcat(dst, "\n", lim);
DNS_SM_YIELD(len + 1);
state->section = state->rr.section;
}
if (error)
goto error;
DNS_SM_EXIT;
error:
for (;;) {
*_error = error;
DNS_SM_YIELD(0);
}
DNS_SM_LEAVE;
*_error = 0;
return 0;
} /* dns_p_lines() */
#undef DNS_P_LINE
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
static void dns_p_dump3(struct dns_packet *P, struct dns_rr_i *I, FILE *fp) {
struct dns_p_lines_i lines = { 0 };
char line[sizeof (union dns_any) * 2];
size_t len;
int error;
while ((len = dns_p_lines(line, sizeof line, &error, P, I, &lines))) {
if (len < sizeof line) {
fwrite(line, 1, len, fp);
} else {
fwrite(line, 1, sizeof line - 1, fp);
fputc('\n', fp);
}
}
} /* dns_p_dump3() */
void dns_p_dump(struct dns_packet *P, FILE *fp) {
struct dns_rr_i I_instance = { 0 };
dns_p_dump3(P, &I_instance, fp);
} /* dns_p_dump() */
static void dns_s_unstudy(struct dns_s_memo *m)
{ m->base = 0; m->end = 0; }
static void dns_m_unstudy(struct dns_p_memo *m) {
dns_s_unstudy(&m->qd);
dns_s_unstudy(&m->an);
dns_s_unstudy(&m->ns);
dns_s_unstudy(&m->ar);
m->opt.p = 0;
m->opt.maxudp = 0;
m->opt.ttl = 0;
} /* dns_m_unstudy() */
static int dns_s_study(struct dns_s_memo *m, enum dns_section section, unsigned short base, struct dns_packet *P) {
unsigned short count, rp;
count = dns_p_count(P, section);
for (rp = base; count && rp < P->end; count--)
rp = dns_rr_skip(rp, P);
m->base = base;
m->end = rp;
return 0;
} /* dns_s_study() */
static int dns_m_study(struct dns_p_memo *m, struct dns_packet *P) {
struct dns_rr rr;
int error;
if ((error = dns_s_study(&m->qd, DNS_S_QD, 12, P)))
goto error;
if ((error = dns_s_study(&m->an, DNS_S_AN, m->qd.end, P)))
goto error;
if ((error = dns_s_study(&m->ns, DNS_S_NS, m->an.end, P)))
goto error;
if ((error = dns_s_study(&m->ar, DNS_S_AR, m->ns.end, P)))
goto error;
m->opt.p = 0;
m->opt.maxudp = 0;
m->opt.ttl = 0;
dns_rr_foreach(&rr, P, .type = DNS_T_OPT, .section = DNS_S_AR) {
m->opt.p = rr.dn.p;
m->opt.maxudp = rr.class;
m->opt.ttl = rr.ttl;
break;
}
return 0;
error:
dns_m_unstudy(m);
return error;
} /* dns_m_study() */
int dns_p_study(struct dns_packet *P) {
return dns_m_study(&P->memo, P);
} /* dns_p_study() */
enum dns_rcode dns_p_rcode(struct dns_packet *P) {
return 0xfff & ((P->memo.opt.ttl >> 20) | dns_header(P)->rcode);
} /* dns_p_rcode() */
/*
* Q U E R Y P A C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#define DNS_Q_RD 0x1 /* recursion desired */
#define DNS_Q_EDNS0 0x2 /* include OPT RR */
static dns_error_t
dns_q_make2(struct dns_packet **_Q, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass, int qflags)
{
struct dns_packet *Q = NULL;
int error;
if (dns_p_movptr(&Q, _Q)) {
dns_p_reset(Q);
} else if (!(Q = dns_p_make(DNS_P_QBUFSIZ, &error))) {
goto error;
}
if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, qtype, qclass, 0, 0)))
goto error;
dns_header(Q)->rd = !!(qflags & DNS_Q_RD);
if (qflags & DNS_Q_EDNS0) {
struct dns_opt opt = DNS_OPT_INIT(&opt);
opt.version = 0; /* RFC 6891 version */
opt.maxudp = 4096;
if ((error = dns_p_push(Q, DNS_S_AR, ".", 1, DNS_T_OPT, dns_opt_class(&opt), dns_opt_ttl(&opt), &opt)))
goto error;
}
*_Q = Q;
return 0;
error:
dns_p_free(Q);
return error;
}
static dns_error_t
dns_q_make(struct dns_packet **Q, const char *qname, enum dns_type qtype, enum dns_class qclass, int qflags)
{
return dns_q_make2(Q, qname, strlen(qname), qtype, qclass, qflags);
}
static dns_error_t
dns_q_remake(struct dns_packet **Q, int qflags)
{
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
struct dns_rr rr;
int error;
assert(Q && *Q);
if ((error = dns_rr_parse(&rr, 12, *Q)))
return error;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, *Q, &error)))
return error;
if (qlen >= sizeof qname)
return DNS_EILLEGAL;
return dns_q_make2(Q, qname, qlen, rr.type, rr.class, qflags);
}
/*
* D O M A I N N A M E R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#ifndef DNS_D_MAXPTRS
#define DNS_D_MAXPTRS 127 /* Arbitrary; possible, valid depth is something like packet size / 2 + fudge. */
#endif
static size_t dns_l_expand(unsigned char *dst, size_t lim, unsigned short src, unsigned short *nxt, const unsigned char *data, size_t end) {
unsigned short len;
unsigned nptrs = 0;
retry:
if (src >= end)
goto invalid;
switch (0x03 & (data[src] >> 6)) {
case 0x00:
len = (0x3f & (data[src++]));
if (end - src < len)
goto invalid;
if (lim > 0) {
memcpy(dst, &data[src], DNS_PP_MIN(lim, len));
dst[DNS_PP_MIN(lim - 1, len)] = '\0';
}
*nxt = src + len;
return len;
case 0x01:
goto invalid;
case 0x02:
goto invalid;
case 0x03:
if (++nptrs > DNS_D_MAXPTRS)
goto invalid;
if (end - src < 2)
goto invalid;
src = ((0x3f & data[src + 0]) << 8)
| ((0xff & data[src + 1]) << 0);
goto retry;
} /* switch() */
/* NOT REACHED */
invalid:
*nxt = end;
return 0;
} /* dns_l_expand() */
static unsigned short dns_l_skip(unsigned short src, const unsigned char *data, size_t end) {
unsigned short len;
if (src >= end)
goto invalid;
switch (0x03 & (data[src] >> 6)) {
case 0x00:
len = (0x3f & (data[src++]));
if (end - src < len)
goto invalid;
return (len)? src + len : end;
case 0x01:
goto invalid;
case 0x02:
goto invalid;
case 0x03:
return end;
} /* switch() */
/* NOT REACHED */
invalid:
return end;
} /* dns_l_skip() */
static _Bool dns_d_isanchored(const void *_src, size_t len) {
const unsigned char *src = _src;
return len > 0 && src[len - 1] == '.';
} /* dns_d_isanchored() */
static size_t dns_d_ndots(const void *_src, size_t len) {
const unsigned char *p = _src, *pe = p + len;
size_t ndots = 0;
while ((p = memchr(p, '.', pe - p))) {
ndots++;
p++;
}
return ndots;
} /* dns_d_ndots() */
static size_t dns_d_trim(void *dst_, size_t lim, const void *src_, size_t len, int flags) {
unsigned char *dst = dst_;
const unsigned char *src = src_;
size_t dp = 0, sp = 0;
int lc;
/* trim any leading dot(s) */
while (sp < len && src[sp] == '.')
sp++;
for (lc = 0; sp < len; lc = src[sp++]) {
/* trim extra dot(s) */
if (src[sp] == '.' && lc == '.')
continue;
if (dp < lim)
dst[dp] = src[sp];
dp++;
}
if ((flags & DNS_D_ANCHOR) && lc != '.') {
if (dp < lim)
dst[dp] = '.';
dp++;
}
if (lim > 0)
dst[DNS_PP_MIN(dp, lim - 1)] = '\0';
return dp;
} /* dns_d_trim() */
char *dns_d_init(void *dst, size_t lim, const void *src, size_t len, int flags) {
if (flags & DNS_D_TRIM) {
dns_d_trim(dst, lim, src, len, flags);
} if (flags & DNS_D_ANCHOR) {
dns_d_anchor(dst, lim, src, len);
} else {
memmove(dst, src, DNS_PP_MIN(lim, len));
if (lim > 0)
((char *)dst)[DNS_PP_MIN(len, lim - 1)] = '\0';
}
return dst;
} /* dns_d_init() */
size_t dns_d_anchor(void *dst, size_t lim, const void *src, size_t len) {
if (len == 0)
return 0;
memmove(dst, src, DNS_PP_MIN(lim, len));
if (((const char *)src)[len - 1] != '.') {
if (len < lim)
((char *)dst)[len] = '.';
len++;
}
if (lim > 0)
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
return len;
} /* dns_d_anchor() */
size_t dns_d_cleave(void *dst, size_t lim, const void *src, size_t len) {
const char *dot;
/* XXX: Skip any leading dot. Handles cleaving root ".". */
if (len == 0 || !(dot = memchr((const char *)src + 1, '.', len - 1)))
return 0;
len -= dot - (const char *)src;
/* XXX: Unless root, skip the label's trailing dot. */
if (len > 1) {
src = ++dot;
len--;
} else
src = dot;
memmove(dst, src, DNS_PP_MIN(lim, len));
if (lim > 0)
((char *)dst)[DNS_PP_MIN(lim - 1, len)] = '\0';
return len;
} /* dns_d_cleave() */
size_t dns_d_comp(void *dst_, size_t lim, const void *src_, size_t len, struct dns_packet *P, int *error) {
struct { unsigned char *b; size_t p, x; } dst, src;
unsigned char ch = '.';
dst.b = dst_;
dst.p = 0;
dst.x = 1;
src.b = (unsigned char *)src_;
src.p = 0;
src.x = 0;
while (src.x < len) {
ch = src.b[src.x];
if (ch == '.') {
if (dst.p < lim)
dst.b[dst.p] = (0x3f & (src.x - src.p));
dst.p = dst.x++;
src.p = ++src.x;
} else {
if (dst.x < lim)
dst.b[dst.x] = ch;
dst.x++;
src.x++;
}
} /* while() */
if (src.x > src.p) {
if (dst.p < lim)
dst.b[dst.p] = (0x3f & (src.x - src.p));
dst.p = dst.x;
}
if (dst.p > 1) {
if (dst.p < lim)
dst.b[dst.p] = 0x00;
dst.p++;
}
#if 1
if (dst.p < lim) {
struct { unsigned char label[DNS_D_MAXLABEL + 1]; size_t len; unsigned short p, x, y; } a, b;
unsigned i;
a.p = 0;
while ((a.len = dns_l_expand(a.label, sizeof a.label, a.p, &a.x, dst.b, lim))) {
for (i = 0; i < lengthof(P->dict) && P->dict[i]; i++) {
b.p = P->dict[i];
while ((b.len = dns_l_expand(b.label, sizeof b.label, b.p, &b.x, P->data, P->end))) {
a.y = a.x;
b.y = b.x;
while (a.len && b.len && 0 == strcasecmp((char *)a.label, (char *)b.label)) {
a.len = dns_l_expand(a.label, sizeof a.label, a.y, &a.y, dst.b, lim);
b.len = dns_l_expand(b.label, sizeof b.label, b.y, &b.y, P->data, P->end);
}
if (a.len == 0 && b.len == 0 && b.p <= 0x3fff) {
dst.b[a.p++] = 0xc0
| (0x3f & (b.p >> 8));
dst.b[a.p++] = (0xff & (b.p >> 0));
/* silence static analyzers */
dns_assume(a.p > 0);
return a.p;
}
b.p = b.x;
} /* while() */
} /* for() */
a.p = a.x;
} /* while() */
} /* if () */
#endif
if (!dst.p)
*error = DNS_EILLEGAL;
return dst.p;
} /* dns_d_comp() */
unsigned short dns_d_skip(unsigned short src, struct dns_packet *P) {
unsigned short len;
while (src < P->end) {
switch (0x03 & (P->data[src] >> 6)) {
case 0x00: /* FOLLOWS */
len = (0x3f & P->data[src++]);
if (0 == len) {
/* success ==> */ return src;
} else if (P->end - src > len) {
src += len;
break;
} else
goto invalid;
/* NOT REACHED */
case 0x01: /* RESERVED */
goto invalid;
case 0x02: /* RESERVED */
goto invalid;
case 0x03: /* POINTER */
if (P->end - src < 2)
goto invalid;
src += 2;
/* success ==> */ return src;
} /* switch() */
} /* while() */
invalid:
return P->end;
} /* dns_d_skip() */
#include <stdio.h>
size_t dns_d_expand(void *dst, size_t lim, unsigned short src, struct dns_packet *P, int *error) {
size_t dstp = 0;
unsigned nptrs = 0;
unsigned char len;
while (src < P->end) {
switch ((0x03 & (P->data[src] >> 6))) {
case 0x00: /* FOLLOWS */
len = (0x3f & P->data[src]);
if (0 == len) {
if (dstp == 0) {
if (dstp < lim)
((unsigned char *)dst)[dstp] = '.';
dstp++;
}
/* NUL terminate */
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
/* success ==> */ return dstp;
}
src++;
if (P->end - src < len)
goto toolong;
if (dstp < lim)
memcpy(&((unsigned char *)dst)[dstp], &P->data[src], DNS_PP_MIN(len, lim - dstp));
src += len;
dstp += len;
if (dstp < lim)
((unsigned char *)dst)[dstp] = '.';
dstp++;
nptrs = 0;
continue;
case 0x01: /* RESERVED */
goto reserved;
case 0x02: /* RESERVED */
goto reserved;
case 0x03: /* POINTER */
if (++nptrs > DNS_D_MAXPTRS)
goto toolong;
if (P->end - src < 2)
goto toolong;
src = ((0x3f & P->data[src + 0]) << 8)
| ((0xff & P->data[src + 1]) << 0);
continue;
} /* switch() */
} /* while() */
toolong:
*error = DNS_EILLEGAL;
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
return 0;
reserved:
*error = DNS_EILLEGAL;
if (lim > 0)
((unsigned char *)dst)[DNS_PP_MIN(dstp, lim - 1)] = '\0';
return 0;
} /* dns_d_expand() */
int dns_d_push(struct dns_packet *P, const void *dn, size_t len) {
size_t lim = P->size - P->end;
unsigned dp = P->end;
int error = DNS_EILLEGAL; /* silence compiler */
len = dns_d_comp(&P->data[dp], lim, dn, len, P, &error);
if (len == 0)
return error;
if (len > lim)
return DNS_ENOBUFS;
P->end += len;
dns_p_dictadd(P, dp);
return 0;
} /* dns_d_push() */
size_t dns_d_cname(void *dst, size_t lim, const void *dn, size_t len, struct dns_packet *P, int *error_) {
char host[DNS_D_MAXNAME + 1];
struct dns_rr_i i;
struct dns_rr rr;
unsigned depth;
int error;
if (sizeof host <= dns_d_anchor(host, sizeof host, dn, len))
{ error = ENAMETOOLONG; goto error; }
for (depth = 0; depth < 7; depth++) {
memset(&i, 0, sizeof i);
i.section = DNS_S_ALL & ~DNS_S_QD;
i.name = host;
i.type = DNS_T_CNAME;
if (!dns_rr_grep(&rr, 1, &i, P, &error))
break;
if ((error = dns_cname_parse((struct dns_cname *)host, &rr, P)))
goto error;
}
return dns_strlcpy(dst, host, lim);
error:
*error_ = error;
return 0;
} /* dns_d_cname() */
/*
* R E S O U R C E R E C O R D R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
int dns_rr_copy(struct dns_packet *P, struct dns_rr *rr, struct dns_packet *Q) {
unsigned char dn[DNS_D_MAXNAME + 1];
union dns_any any;
size_t len;
int error;
if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, Q, &error)))
return error;
else if (len >= sizeof dn)
return DNS_EILLEGAL;
if (rr->section != DNS_S_QD && (error = dns_any_parse(dns_any_init(&any, sizeof any), rr, Q)))
return error;
return dns_p_push(P, rr->section, dn, len, rr->type, rr->class, rr->ttl, &any);
} /* dns_rr_copy() */
int dns_rr_parse(struct dns_rr *rr, unsigned short src, struct dns_packet *P) {
unsigned short p = src;
if (src >= P->end)
goto invalid;
rr->dn.p = p;
rr->dn.len = (p = dns_d_skip(p, P)) - rr->dn.p;
if (P->end - p < 4)
goto invalid;
rr->type = ((0xff & P->data[p + 0]) << 8)
| ((0xff & P->data[p + 1]) << 0);
rr->class = ((0xff & P->data[p + 2]) << 8)
| ((0xff & P->data[p + 3]) << 0);
p += 4;
if (src < dns_p_qend(P)) {
rr->section = DNS_S_QUESTION;
rr->ttl = 0;
rr->rd.p = 0;
rr->rd.len = 0;
return 0;
}
if (P->end - p < 4)
goto invalid;
rr->ttl = ((0xff & P->data[p + 0]) << 24)
| ((0xff & P->data[p + 1]) << 16)
| ((0xff & P->data[p + 2]) << 8)
| ((0xff & P->data[p + 3]) << 0);
if (rr->type != DNS_T_OPT)
rr->ttl = DNS_PP_MIN(rr->ttl, 0x7fffffffU);
p += 4;
if (P->end - p < 2)
goto invalid;
rr->rd.len = ((0xff & P->data[p + 0]) << 8)
| ((0xff & P->data[p + 1]) << 0);
rr->rd.p = p + 2;
p += 2;
if (P->end - p < rr->rd.len)
goto invalid;
return 0;
invalid:
return DNS_EILLEGAL;
} /* dns_rr_parse() */
static unsigned short dns_rr_len(const unsigned short src, struct dns_packet *P) {
unsigned short rp, rdlen;
rp = dns_d_skip(src, P);
if (P->end - rp < 4)
return P->end - src;
rp += 4; /* TYPE, CLASS */
if (rp <= dns_p_qend(P))
return rp - src;
if (P->end - rp < 6)
return P->end - src;
rp += 6; /* TTL, RDLEN */
rdlen = ((0xff & P->data[rp - 2]) << 8)
| ((0xff & P->data[rp - 1]) << 0);
if (P->end - rp < rdlen)
return P->end - src;
rp += rdlen;
return rp - src;
} /* dns_rr_len() */
unsigned short dns_rr_skip(unsigned short src, struct dns_packet *P) {
return src + dns_rr_len(src, P);
} /* dns_rr_skip() */
static enum dns_section dns_rr_section(unsigned short src, struct dns_packet *P) {
enum dns_section section;
unsigned count, index;
unsigned short rp;
if (src >= P->memo.qd.base && src < P->memo.qd.end)
return DNS_S_QD;
if (src >= P->memo.an.base && src < P->memo.an.end)
return DNS_S_AN;
if (src >= P->memo.ns.base && src < P->memo.ns.end)
return DNS_S_NS;
if (src >= P->memo.ar.base && src < P->memo.ar.end)
return DNS_S_AR;
/* NOTE: Possibly bad memoization. Try it the hard-way. */
for (rp = 12, index = 0; rp < src && rp < P->end; index++)
rp = dns_rr_skip(rp, P);
section = DNS_S_QD;
count = dns_p_count(P, section);
while (index >= count && section <= DNS_S_AR) {
section <<= 1;
count += dns_p_count(P, section);
}
return DNS_S_ALL & section;
} /* dns_rr_section() */
static enum dns_type dns_rr_type(unsigned short src, struct dns_packet *P) {
struct dns_rr rr;
int error;
if ((error = dns_rr_parse(&rr, src, P)))
return 0;
return rr.type;
} /* dns_rr_type() */
int dns_rr_cmp(struct dns_rr *r0, struct dns_packet *P0, struct dns_rr *r1, struct dns_packet *P1) {
char host0[DNS_D_MAXNAME + 1], host1[DNS_D_MAXNAME + 1];
union dns_any any0, any1;
int cmp, error;
size_t len;
if ((cmp = r0->type - r1->type))
return cmp;
if ((cmp = r0->class - r1->class))
return cmp;
/*
* FIXME: Do label-by-label comparison to handle illegally long names?
*/
if (!(len = dns_d_expand(host0, sizeof host0, r0->dn.p, P0, &error))
|| len >= sizeof host0)
return -1;
if (!(len = dns_d_expand(host1, sizeof host1, r1->dn.p, P1, &error))
|| len >= sizeof host1)
return 1;
if ((cmp = strcasecmp(host0, host1)))
return cmp;
if (DNS_S_QD & (r0->section | r1->section)) {
if (r0->section == r1->section)
return 0;
return (r0->section == DNS_S_QD)? -1 : 1;
}
if ((error = dns_any_parse(&any0, r0, P0)))
return -1;
if ((error = dns_any_parse(&any1, r1, P1)))
return 1;
return dns_any_cmp(&any0, r0->type, &any1, r1->type);
} /* dns_rr_cmp() */
static _Bool dns_rr_exists(struct dns_rr *rr0, struct dns_packet *P0, struct dns_packet *P1) {
struct dns_rr rr1;
dns_rr_foreach(&rr1, P1, .section = rr0->section, .type = rr0->type) {
if (0 == dns_rr_cmp(rr0, P0, &rr1, P1))
return 1;
}
return 0;
} /* dns_rr_exists() */
static unsigned short dns_rr_offset(struct dns_rr *rr) {
return rr->dn.p;
} /* dns_rr_offset() */
static _Bool dns_rr_i_match(struct dns_rr *rr, struct dns_rr_i *i, struct dns_packet *P) {
if (i->section && !(rr->section & i->section))
return 0;
if (i->type && rr->type != i->type && i->type != DNS_T_ALL)
return 0;
if (i->class && rr->class != i->class && i->class != DNS_C_ANY)
return 0;
if (i->name) {
char dn[DNS_D_MAXNAME + 1];
size_t len;
int error;
if (!(len = dns_d_expand(dn, sizeof dn, rr->dn.p, P, &error))
|| len >= sizeof dn)
return 0;
if (0 != strcasecmp(dn, i->name))
return 0;
}
if (i->data && i->type && rr->section > DNS_S_QD) {
union dns_any rd;
int error;
if ((error = dns_any_parse(&rd, rr, P)))
return 0;
if (0 != dns_any_cmp(&rd, rr->type, i->data, i->type))
return 0;
}
return 1;
} /* dns_rr_i_match() */
static unsigned short dns_rr_i_start(struct dns_rr_i *i, struct dns_packet *P) {
unsigned short rp;
struct dns_rr r0, rr;
int error;
if ((i->section & DNS_S_QD) && P->memo.qd.base)
rp = P->memo.qd.base;
else if ((i->section & DNS_S_AN) && P->memo.an.base)
rp = P->memo.an.base;
else if ((i->section & DNS_S_NS) && P->memo.ns.base)
rp = P->memo.ns.base;
else if ((i->section & DNS_S_AR) && P->memo.ar.base)
rp = P->memo.ar.base;
else
rp = 12;
for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
r0 = rr;
goto lower;
}
return P->end;
lower:
if (i->sort == &dns_rr_i_packet)
return dns_rr_offset(&r0);
while ((rp = dns_rr_skip(rp, P)) < P->end) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) < 0)
r0 = rr;
}
return dns_rr_offset(&r0);
} /* dns_rr_i_start() */
static unsigned short dns_rr_i_skip(unsigned short rp, struct dns_rr_i *i, struct dns_packet *P) {
struct dns_rr r0, r1, rr;
int error;
if ((error = dns_rr_parse(&r0, rp, P)))
return P->end;
r0.section = dns_rr_section(rp, P);
rp = (i->sort == &dns_rr_i_packet)? dns_rr_skip(rp, P) : 12;
for (; rp < P->end; rp = dns_rr_skip(rp, P)) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) <= 0)
continue;
r1 = rr;
goto lower;
}
return P->end;
lower:
if (i->sort == &dns_rr_i_packet)
return dns_rr_offset(&r1);
while ((rp = dns_rr_skip(rp, P)) < P->end) {
if ((error = dns_rr_parse(&rr, rp, P)))
continue;
rr.section = dns_rr_section(rp, P);
if (!dns_rr_i_match(&rr, i, P))
continue;
if (i->sort(&rr, &r0, i, P) <= 0)
continue;
if (i->sort(&rr, &r1, i, P) >= 0)
continue;
r1 = rr;
}
return dns_rr_offset(&r1);
} /* dns_rr_i_skip() */
int dns_rr_i_packet(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
(void)i;
(void)P;
return (int)a->dn.p - (int)b->dn.p;
} /* dns_rr_i_packet() */
int dns_rr_i_order(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
int cmp;
(void)i;
if ((cmp = a->section - b->section))
return cmp;
if (a->type != b->type)
return (int)a->dn.p - (int)b->dn.p;
return dns_rr_cmp(a, P, b, P);
} /* dns_rr_i_order() */
int dns_rr_i_shuffle(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
int cmp;
(void)i;
(void)P;
while (!i->state.regs[0])
i->state.regs[0] = dns_random();
if ((cmp = a->section - b->section))
return cmp;
return dns_k_shuffle16(a->dn.p, i->state.regs[0]) - dns_k_shuffle16(b->dn.p, i->state.regs[0]);
} /* dns_rr_i_shuffle() */
void dns_rr_i_init(struct dns_rr_i *i) {
static const struct dns_rr_i i_initializer;
i->state = i_initializer.state;
i->saved = i->state;
} /* dns_rr_i_init() */
unsigned dns_rr_grep(struct dns_rr *rr, unsigned lim, struct dns_rr_i *i, struct dns_packet *P, int *error_) {
unsigned count = 0;
int error;
switch (i->state.exec) {
case 0:
if (!i->sort)
i->sort = &dns_rr_i_packet;
i->state.next = dns_rr_i_start(i, P);
i->state.exec++;
/* FALL THROUGH */
case 1:
while (count < lim && i->state.next < P->end) {
if ((error = dns_rr_parse(rr, i->state.next, P)))
goto error;
rr->section = dns_rr_section(i->state.next, P);
rr++;
count++;
i->state.count++;
i->state.next = dns_rr_i_skip(i->state.next, i, P);
} /* while() */
break;
} /* switch() */
return count;
error:
if (error_)
*error_ = error;
return count;
} /* dns_rr_grep() */
size_t dns_rr_print(void *_dst, size_t lim, struct dns_rr *rr, struct dns_packet *P, int *_error) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
union dns_any any;
size_t n;
int error;
char __dst[DNS_STRMAXLEN + 1] = { 0 };
if (rr->section == DNS_S_QD)
dns_b_putc(&dst, ';');
if (!(n = dns_d_expand(any.ns.host, sizeof any.ns.host, rr->dn.p, P, &error)))
goto error;
dns_b_put(&dst, any.ns.host, DNS_PP_MIN(n, sizeof any.ns.host - 1));
if (rr->section != DNS_S_QD) {
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, rr->ttl, 0);
}
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, dns_strclass(rr->class, __dst));
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, dns_strtype(rr->type, __dst));
if (rr->section == DNS_S_QD)
goto epilog;
dns_b_putc(&dst, ' ');
if ((error = dns_any_parse(dns_any_init(&any, sizeof any), rr, P)))
goto error;
n = dns_any_print(dst.p, dst.pe - dst.p, &any, rr->type);
dst.p += DNS_PP_MIN(n, (size_t)(dst.pe - dst.p));
epilog:
return dns_b_strllen(&dst);
error:
*_error = error;
return 0;
} /* dns_rr_print() */
int dns_a_parse(struct dns_a *a, struct dns_rr *rr, struct dns_packet *P) {
unsigned long addr;
if (rr->rd.len != 4)
return DNS_EILLEGAL;
addr = ((0xffU & P->data[rr->rd.p + 0]) << 24)
| ((0xffU & P->data[rr->rd.p + 1]) << 16)
| ((0xffU & P->data[rr->rd.p + 2]) << 8)
| ((0xffU & P->data[rr->rd.p + 3]) << 0);
a->addr.s_addr = htonl(addr);
return 0;
} /* dns_a_parse() */
static int dns_a_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_a_parse (&arg0->a, rr, P);
}
int dns_a_push(struct dns_packet *P, struct dns_a *a) {
unsigned long addr;
if (P->size - P->end < 6)
return DNS_ENOBUFS;
P->data[P->end++] = 0x00;
P->data[P->end++] = 0x04;
addr = ntohl(a->addr.s_addr);
P->data[P->end++] = 0xffU & (addr >> 24);
P->data[P->end++] = 0xffU & (addr >> 16);
P->data[P->end++] = 0xffU & (addr >> 8);
P->data[P->end++] = 0xffU & (addr >> 0);
return 0;
} /* dns_a_push() */
static int dns_a_push0(struct dns_packet *P, void *a) {
return dns_a_push(P, a);
}
size_t dns_a_arpa(void *_dst, size_t lim, const struct dns_a *a) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned long octets = ntohl(a->addr.s_addr);
unsigned i;
for (i = 0; i < 4; i++) {
dns_b_fmtju(&dst, 0xff & octets, 0);
dns_b_putc(&dst, '.');
octets >>= 8;
}
dns_b_puts(&dst, "in-addr.arpa.");
return dns_b_strllen(&dst);
} /* dns_a_arpa() */
int dns_a_cmp(const struct dns_a *a, const struct dns_a *b) {
if (ntohl(a->addr.s_addr) < ntohl(b->addr.s_addr))
return -1;
if (ntohl(a->addr.s_addr) > ntohl(b->addr.s_addr))
return 1;
return 0;
} /* dns_a_cmp() */
static int dns_a_cmp0(const void *a, const void *b) {
return dns_a_cmp(a, b);
}
size_t dns_a_print(void *dst, size_t lim, struct dns_a *a) {
char addr[INET_ADDRSTRLEN + 1] = "0.0.0.0";
dns_inet_ntop(AF_INET, &a->addr, addr, sizeof addr);
return dns_strlcpy(dst, addr, lim);
} /* dns_a_print() */
static size_t dns_a_print0(void *dst, size_t lim, void *a) {
return dns_a_print(dst, lim, a);
}
int dns_aaaa_parse(struct dns_aaaa *aaaa, struct dns_rr *rr, struct dns_packet *P) {
if (rr->rd.len != sizeof aaaa->addr.s6_addr)
return DNS_EILLEGAL;
memcpy(aaaa->addr.s6_addr, &P->data[rr->rd.p], sizeof aaaa->addr.s6_addr);
return 0;
} /* dns_aaaa_parse() */
static int dns_aaaa_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_aaaa_parse (&arg0->aaaa, rr, P);
}
int dns_aaaa_push(struct dns_packet *P, struct dns_aaaa *aaaa) {
if (P->size - P->end < 2 + sizeof aaaa->addr.s6_addr)
return DNS_ENOBUFS;
P->data[P->end++] = 0x00;
P->data[P->end++] = 0x10;
memcpy(&P->data[P->end], aaaa->addr.s6_addr, sizeof aaaa->addr.s6_addr);
P->end += sizeof aaaa->addr.s6_addr;
return 0;
} /* dns_aaaa_push() */
static int dns_aaaa_push0(struct dns_packet *P, void *aaaa) {
return dns_aaaa_push(P, aaaa);
}
int dns_aaaa_cmp(const struct dns_aaaa *a, const struct dns_aaaa *b) {
unsigned i;
int cmp;
for (i = 0; i < lengthof(a->addr.s6_addr); i++) {
if ((cmp = (a->addr.s6_addr[i] - b->addr.s6_addr[i])))
return cmp;
}
return 0;
} /* dns_aaaa_cmp() */
static int dns_aaaa_cmp0(const void *a, const void *b) {
return dns_aaaa_cmp(a, b);
}
size_t dns_aaaa_arpa(void *_dst, size_t lim, const struct dns_aaaa *aaaa) {
static const unsigned char hex[16] = "0123456789abcdef";
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned nyble;
int i, j;
for (i = sizeof aaaa->addr.s6_addr - 1; i >= 0; i--) {
nyble = aaaa->addr.s6_addr[i];
for (j = 0; j < 2; j++) {
dns_b_putc(&dst, hex[0x0f & nyble]);
dns_b_putc(&dst, '.');
nyble >>= 4;
}
}
dns_b_puts(&dst, "ip6.arpa.");
return dns_b_strllen(&dst);
} /* dns_aaaa_arpa() */
size_t dns_aaaa_print(void *dst, size_t lim, struct dns_aaaa *aaaa) {
char addr[INET6_ADDRSTRLEN + 1] = "::";
dns_inet_ntop(AF_INET6, &aaaa->addr, addr, sizeof addr);
return dns_strlcpy(dst, addr, lim);
} /* dns_aaaa_print() */
static size_t dns_aaaa_print0(void *dst, size_t lim, void *aaaa) {
return dns_aaaa_print(dst, lim, aaaa);
}
int dns_mx_parse(struct dns_mx *mx, struct dns_rr *rr, struct dns_packet *P) {
size_t len;
int error;
if (rr->rd.len < 3)
return DNS_EILLEGAL;
mx->preference = (0xff00 & (P->data[rr->rd.p + 0] << 8))
| (0x00ff & (P->data[rr->rd.p + 1] << 0));
if (!(len = dns_d_expand(mx->host, sizeof mx->host, rr->rd.p + 2, P, &error)))
return error;
else if (len >= sizeof mx->host)
return DNS_EILLEGAL;
return 0;
} /* dns_mx_parse() */
static int dns_mx_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_mx_parse (&arg0->mx, rr, P);
}
int dns_mx_push(struct dns_packet *P, struct dns_mx *mx) {
size_t end, len;
int error;
if (P->size - P->end < 5)
return DNS_ENOBUFS;
end = P->end;
P->end += 2;
P->data[P->end++] = 0xff & (mx->preference >> 8);
P->data[P->end++] = 0xff & (mx->preference >> 0);
if ((error = dns_d_push(P, mx->host, strlen(mx->host))))
goto error;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
error:
P->end = end;
return error;
} /* dns_mx_push() */
static int dns_mx_push0(struct dns_packet *P, void *mx) {
return dns_mx_push (P, mx);
}
int dns_mx_cmp(const struct dns_mx *a, const struct dns_mx *b) {
int cmp;
if ((cmp = a->preference - b->preference))
return cmp;
return strcasecmp(a->host, b->host);
} /* dns_mx_cmp() */
static int dns_mx_cmp0(const void *a, const void *b) {
return dns_mx_cmp (a, b);
}
size_t dns_mx_print(void *_dst, size_t lim, struct dns_mx *mx) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, mx->preference, 0);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, mx->host);
return dns_b_strllen(&dst);
} /* dns_mx_print() */
static size_t dns_mx_print0(void *_dst, size_t lim, void *mx) {
return dns_mx_print (_dst, lim, mx);
}
size_t dns_mx_cname(void *dst, size_t lim, struct dns_mx *mx) {
return dns_strlcpy(dst, mx->host, lim);
} /* dns_mx_cname() */
static size_t dns_mx_cname0(void *dst, size_t lim, void *mx) {
return dns_mx_cname (dst, lim, mx);
}
int dns_ns_parse(struct dns_ns *ns, struct dns_rr *rr, struct dns_packet *P) {
size_t len;
int error;
if (!(len = dns_d_expand(ns->host, sizeof ns->host, rr->rd.p, P, &error)))
return error;
else if (len >= sizeof ns->host)
return DNS_EILLEGAL;
return 0;
} /* dns_ns_parse() */
int dns_ns_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse (&arg0->ns, rr, P);
}
int dns_ns_push(struct dns_packet *P, struct dns_ns *ns) {
size_t end, len;
int error;
if (P->size - P->end < 3)
return DNS_ENOBUFS;
end = P->end;
P->end += 2;
if ((error = dns_d_push(P, ns->host, strlen(ns->host))))
goto error;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
error:
P->end = end;
return error;
} /* dns_ns_push() */
static int dns_ns_push0(struct dns_packet *P, void *ns) {
return dns_ns_push (P, ns);
}
int dns_ns_cmp(const struct dns_ns *a, const struct dns_ns *b) {
return strcasecmp(a->host, b->host);
} /* dns_ns_cmp() */
static int dns_ns_cmp0(const void *a, const void *b) {
return dns_ns_cmp (a, b);
}
size_t dns_ns_print(void *dst, size_t lim, struct dns_ns *ns) {
return dns_strlcpy(dst, ns->host, lim);
} /* dns_ns_print() */
static size_t dns_ns_print0(void *dst, size_t lim, void *ns) {
return dns_ns_print (dst, lim, ns);
}
size_t dns_ns_cname(void *dst, size_t lim, struct dns_ns *ns) {
return dns_strlcpy(dst, ns->host, lim);
} /* dns_ns_cname() */
static size_t dns_ns_cname0(void *dst, size_t lim, void *ns) {
return dns_ns_cname (dst, lim, ns);
}
int dns_cname_parse(struct dns_cname *cname, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse((struct dns_ns *)cname, rr, P);
} /* dns_cname_parse() */
static int dns_cname_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_cname_parse (&arg0->cname, rr, P);
}
int dns_cname_push(struct dns_packet *P, struct dns_cname *cname) {
return dns_ns_push(P, (struct dns_ns *)cname);
} /* dns_cname_push() */
static int dns_cname_push0(struct dns_packet *P, void *cname) {
return dns_cname_push (P, cname);
}
int dns_cname_cmp(const struct dns_cname *a, const struct dns_cname *b) {
return strcasecmp(a->host, b->host);
} /* dns_cname_cmp() */
static int dns_cname_cmp0(const void *a, const void *b) {
return dns_cname_cmp (a, b);
}
size_t dns_cname_print(void *dst, size_t lim, struct dns_cname *cname) {
return dns_ns_print(dst, lim, (struct dns_ns *)cname);
} /* dns_cname_print() */
static size_t dns_cname_print0(void *dst, size_t lim, void *cname) {
return dns_cname_print (dst, lim, cname);
}
size_t dns_cname_cname(void *dst, size_t lim, struct dns_cname *cname) {
return dns_strlcpy(dst, cname->host, lim);
} /* dns_cname_cname() */
size_t dns_cname_cname0(void *dst, size_t lim, void *cname) {
return dns_cname_cname(dst, lim, cname);
}
int dns_soa_parse(struct dns_soa *soa, struct dns_rr *rr, struct dns_packet *P) {
struct { void *dst; size_t lim; } dn[] =
{ { soa->mname, sizeof soa->mname },
{ soa->rname, sizeof soa->rname } };
unsigned *ts[] =
{ &soa->serial, &soa->refresh, &soa->retry, &soa->expire, &soa->minimum };
unsigned short rp;
unsigned i, j, n;
int error;
/* MNAME / RNAME */
if ((rp = rr->rd.p) >= P->end)
return DNS_EILLEGAL;
for (i = 0; i < lengthof(dn); i++) {
if (!(n = dns_d_expand(dn[i].dst, dn[i].lim, rp, P, &error)))
return error;
else if (n >= dn[i].lim)
return DNS_EILLEGAL;
if ((rp = dns_d_skip(rp, P)) >= P->end)
return DNS_EILLEGAL;
}
/* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
for (i = 0; i < lengthof(ts); i++) {
for (j = 0; j < 4; j++, rp++) {
if (rp >= P->end)
return DNS_EILLEGAL;
*ts[i] <<= 8;
*ts[i] |= (0xff & P->data[rp]);
}
}
return 0;
} /* dns_soa_parse() */
static int dns_soa_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_soa_parse (&arg0->soa, rr, P);
}
int dns_soa_push(struct dns_packet *P, struct dns_soa *soa) {
void *dn[] = { soa->mname, soa->rname };
unsigned ts[] = { (0xffffffff & soa->serial),
(0x7fffffff & soa->refresh),
(0x7fffffff & soa->retry),
(0x7fffffff & soa->expire),
(0xffffffff & soa->minimum) };
unsigned i, j;
size_t end, len;
int error;
end = P->end;
if ((P->end += 2) >= P->size)
goto toolong;
/* MNAME / RNAME */
for (i = 0; i < lengthof(dn); i++) {
if ((error = dns_d_push(P, dn[i], strlen(dn[i]))))
goto error;
}
/* SERIAL / REFRESH / RETRY / EXPIRE / MINIMUM */
for (i = 0; i < lengthof(ts); i++) {
if ((P->end += 4) >= P->size)
goto toolong;
for (j = 1; j <= 4; j++) {
P->data[P->end - j] = (0xff & ts[i]);
ts[i] >>= 8;
}
}
len = P->end - end - 2;
P->data[end + 0] = (0xff & (len >> 8));
P->data[end + 1] = (0xff & (len >> 0));
return 0;
toolong:
error = DNS_ENOBUFS;
/* FALL THROUGH */
error:
P->end = end;
return error;
} /* dns_soa_push() */
static int dns_soa_push0(struct dns_packet *P, void *soa) {
return dns_soa_push (P, soa);
};
int dns_soa_cmp(const struct dns_soa *a, const struct dns_soa *b) {
int cmp;
if ((cmp = strcasecmp(a->mname, b->mname)))
return cmp;
if ((cmp = strcasecmp(a->rname, b->rname)))
return cmp;
if (a->serial > b->serial)
return -1;
else if (a->serial < b->serial)
return 1;
if (a->refresh > b->refresh)
return -1;
else if (a->refresh < b->refresh)
return 1;
if (a->retry > b->retry)
return -1;
else if (a->retry < b->retry)
return 1;
if (a->expire > b->expire)
return -1;
else if (a->expire < b->expire)
return 1;
if (a->minimum > b->minimum)
return -1;
else if (a->minimum < b->minimum)
return 1;
return 0;
} /* dns_soa_cmp() */
static int dns_soa_cmp0(const void *a, const void *b) {
return dns_soa_cmp (a, b);
}
size_t dns_soa_print(void *_dst, size_t lim, struct dns_soa *soa) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_puts(&dst, soa->mname);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, soa->rname);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->serial, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->refresh, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->retry, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->expire, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, soa->minimum, 0);
return dns_b_strllen(&dst);
} /* dns_soa_print() */
static size_t dns_soa_print0(void *_dst, size_t lim, void *soa) {
return dns_soa_print(_dst, lim, soa);
}
int dns_srv_parse(struct dns_srv *srv, struct dns_rr *rr, struct dns_packet *P) {
unsigned short rp;
unsigned i;
size_t n;
int error;
memset(srv, '\0', sizeof *srv);
rp = rr->rd.p;
if (rr->rd.len < 7)
return DNS_EILLEGAL;
for (i = 0; i < 2; i++, rp++) {
srv->priority <<= 8;
srv->priority |= (0xff & P->data[rp]);
}
for (i = 0; i < 2; i++, rp++) {
srv->weight <<= 8;
srv->weight |= (0xff & P->data[rp]);
}
for (i = 0; i < 2; i++, rp++) {
srv->port <<= 8;
srv->port |= (0xff & P->data[rp]);
}
if (!(n = dns_d_expand(srv->target, sizeof srv->target, rp, P, &error)))
return error;
else if (n >= sizeof srv->target)
return DNS_EILLEGAL;
return 0;
} /* dns_srv_parse() */
static int dns_srv_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_srv_parse (&arg0->srv, rr, P);
}
int dns_srv_push(struct dns_packet *P, struct dns_srv *srv) {
size_t end, len;
int error;
end = P->end;
if (P->size - P->end < 2)
goto toolong;
P->end += 2;
if (P->size - P->end < 6)
goto toolong;
P->data[P->end++] = 0xff & (srv->priority >> 8);
P->data[P->end++] = 0xff & (srv->priority >> 0);
P->data[P->end++] = 0xff & (srv->weight >> 8);
P->data[P->end++] = 0xff & (srv->weight >> 0);
P->data[P->end++] = 0xff & (srv->port >> 8);
P->data[P->end++] = 0xff & (srv->port >> 0);
if (0 == (len = dns_d_comp(&P->data[P->end], P->size - P->end, srv->target, strlen(srv->target), P, &error)))
goto error;
else if (P->size - P->end < len)
goto toolong;
P->end += len;
if (P->end > 65535)
goto toolong;
len = P->end - end - 2;
P->data[end + 0] = 0xff & (len >> 8);
P->data[end + 1] = 0xff & (len >> 0);
return 0;
toolong:
error = DNS_ENOBUFS;
/* FALL THROUGH */
error:
P->end = end;
return error;
} /* dns_srv_push() */
static int dns_srv_push0(struct dns_packet *P, void *srv) {
return dns_srv_push (P, srv);
}
int dns_srv_cmp(const struct dns_srv *a, const struct dns_srv *b) {
int cmp;
if ((cmp = a->priority - b->priority))
return cmp;
/*
* FIXME: We need some sort of random seed to implement the dynamic
* weighting required by RFC 2782.
*/
if ((cmp = a->weight - b->weight))
return cmp;
if ((cmp = a->port - b->port))
return cmp;
return strcasecmp(a->target, b->target);
} /* dns_srv_cmp() */
static int dns_srv_cmp0(const void *a, const void *b) {
return dns_srv_cmp (a, b);
}
size_t dns_srv_print(void *_dst, size_t lim, struct dns_srv *srv) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
dns_b_fmtju(&dst, srv->priority, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, srv->weight, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, srv->port, 0);
dns_b_putc(&dst, ' ');
dns_b_puts(&dst, srv->target);
return dns_b_strllen(&dst);
} /* dns_srv_print() */
static size_t dns_srv_print0(void *_dst, size_t lim, void *srv) {
return dns_srv_print (_dst, lim, srv);
}
size_t dns_srv_cname(void *dst, size_t lim, struct dns_srv *srv) {
return dns_strlcpy(dst, srv->target, lim);
} /* dns_srv_cname() */
static size_t dns_srv_cname0(void *dst, size_t lim, void *srv) {
return dns_srv_cname (dst, lim, srv);
}
unsigned int dns_opt_ttl(const struct dns_opt *opt) {
unsigned int ttl = 0;
ttl |= (0xffU & opt->rcode) << 24;
ttl |= (0xffU & opt->version) << 16;
ttl |= (0xffffU & opt->flags) << 0;
return ttl;
} /* dns_opt_ttl() */
unsigned short dns_opt_class(const struct dns_opt *opt) {
return opt->maxudp;
} /* dns_opt_class() */
struct dns_opt *dns_opt_init(struct dns_opt *opt, size_t size) {
assert(size >= offsetof(struct dns_opt, data));
opt->size = size - offsetof(struct dns_opt, data);
opt->len = 0;
opt->rcode = 0;
opt->version = 0;
opt->maxudp = 0;
return opt;
} /* dns_opt_init() */
static union dns_any *dns_opt_initany(union dns_any *any, size_t size) {
return dns_opt_init(&any->opt, size), any;
} /* dns_opt_initany() */
int dns_opt_parse(struct dns_opt *opt, struct dns_rr *rr, struct dns_packet *P) {
const struct dns_buf src = DNS_B_FROM(&P->data[rr->rd.p], rr->rd.len);
struct dns_buf dst = DNS_B_INTO(opt->data, opt->size);
int error;
opt->rcode = 0xfff & ((rr->ttl >> 20) | dns_header(P)->rcode);
opt->version = 0xff & (rr->ttl >> 16);
opt->flags = 0xffff & rr->ttl;
opt->maxudp = 0xffff & rr->class;
while (src.p < src.pe) {
int code, len;
if (-1 == (code = dns_b_get16(&src, -1)))
return src.error;
if (-1 == (len = dns_b_get16(&src, -1)))
return src.error;
switch (code) {
default:
dns_b_put16(&dst, code);
dns_b_put16(&dst, len);
if ((error = dns_b_move(&dst, &src, len)))
return error;
break;
}
}
return 0;
} /* dns_opt_parse() */
static int dns_opt_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_opt_parse (&arg0->opt, rr, P);
}
int dns_opt_push(struct dns_packet *P, struct dns_opt *opt) {
const struct dns_buf src = DNS_B_FROM(opt->data, opt->len);
struct dns_buf dst = DNS_B_INTO(&P->data[P->end], (P->size - P->end));
int error;
/* rdata length (see below) */
if ((error = dns_b_put16(&dst, 0)))
goto error;
/* ... push known options here */
/* push opaque option data */
if ((error = dns_b_move(&dst, &src, (size_t)(src.pe - src.p))))
goto error;
/* rdata length */
if ((error = dns_b_pput16(&dst, dns_b_tell(&dst) - 2, 0)))
goto error;
#if !DNS_DEBUG_OPT_FORMERR
P->end += dns_b_tell(&dst);
#endif
return 0;
error:
return error;
} /* dns_opt_push() */
static int dns_opt_push0(struct dns_packet *P, void *opt) {
return dns_opt_push (P, opt);
}
int dns_opt_cmp(const struct dns_opt *a, const struct dns_opt *b) {
(void)a;
(void)b;
return -1;
} /* dns_opt_cmp() */
static int dns_opt_cmp0(const void *a, const void *b) {
return dns_opt_cmp (a, b);
}
size_t dns_opt_print(void *_dst, size_t lim, struct dns_opt *opt) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
size_t p;
dns_b_putc(&dst, '"');
for (p = 0; p < opt->len; p++) {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, opt->data[p], 3);
}
dns_b_putc(&dst, '"');
return dns_b_strllen(&dst);
} /* dns_opt_print() */
static size_t dns_opt_print0(void *_dst, size_t lim, void *opt) {
return dns_opt_print (_dst, lim, opt);
}
int dns_ptr_parse(struct dns_ptr *ptr, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse((struct dns_ns *)ptr, rr, P);
} /* dns_ptr_parse() */
int dns_ptr_parse0(union dns_any *ptr, struct dns_rr *rr, struct dns_packet *P) {
return dns_ns_parse0(ptr, rr, P);
}
int dns_ptr_push(struct dns_packet *P, struct dns_ptr *ptr) {
return dns_ns_push(P, (struct dns_ns *)ptr);
} /* dns_ptr_push() */
static int dns_ptr_push0(struct dns_packet *P, void *ptr) {
return dns_ns_push(P, ptr);
}
size_t dns_ptr_qname(void *dst, size_t lim, int af, void *addr) {
switch (af) {
case AF_INET6:
return dns_aaaa_arpa(dst, lim, addr);
case AF_INET:
return dns_a_arpa(dst, lim, addr);
default: {
struct dns_a a;
a.addr.s_addr = INADDR_NONE;
return dns_a_arpa(dst, lim, &a);
}
}
} /* dns_ptr_qname() */
int dns_ptr_cmp(const struct dns_ptr *a, const struct dns_ptr *b) {
return strcasecmp(a->host, b->host);
} /* dns_ptr_cmp() */
static int dns_ptr_cmp0(const void *a, const void *b) {
return dns_ptr_cmp (a, b);
}
size_t dns_ptr_print(void *dst, size_t lim, struct dns_ptr *ptr) {
return dns_ns_print(dst, lim, (struct dns_ns *)ptr);
} /* dns_ptr_print() */
static size_t dns_ptr_print0(void *dst, size_t lim, void *ptr) {
return dns_ns_print(dst, lim, ptr);
}
size_t dns_ptr_cname(void *dst, size_t lim, struct dns_ptr *ptr) {
return dns_strlcpy(dst, ptr->host, lim);
} /* dns_ptr_cname() */
static size_t dns_ptr_cname0(void *dst, size_t lim, void *ptr) {
return dns_ptr_cname (dst, lim, ptr);
}
int dns_sshfp_parse(struct dns_sshfp *fp, struct dns_rr *rr, struct dns_packet *P) {
unsigned p = rr->rd.p, pe = rr->rd.p + rr->rd.len;
if (pe - p < 2)
return DNS_EILLEGAL;
fp->algo = P->data[p++];
fp->type = P->data[p++];
switch (fp->type) {
case DNS_SSHFP_SHA1:
if (pe - p < sizeof fp->digest.sha1)
return DNS_EILLEGAL;
memcpy(fp->digest.sha1, &P->data[p], sizeof fp->digest.sha1);
break;
default:
break;
} /* switch() */
return 0;
} /* dns_sshfp_parse() */
static int dns_sshfp_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_sshfp_parse (&arg0->sshfp, rr, P);
}
int dns_sshfp_push(struct dns_packet *P, struct dns_sshfp *fp) {
unsigned p = P->end, pe = P->size, n;
if (pe - p < 4)
return DNS_ENOBUFS;
p += 2;
P->data[p++] = 0xff & fp->algo;
P->data[p++] = 0xff & fp->type;
switch (fp->type) {
case DNS_SSHFP_SHA1:
if (pe - p < sizeof fp->digest.sha1)
return DNS_ENOBUFS;
memcpy(&P->data[p], fp->digest.sha1, sizeof fp->digest.sha1);
p += sizeof fp->digest.sha1;
break;
default:
return DNS_EILLEGAL;
} /* switch() */
n = p - P->end - 2;
P->data[P->end++] = 0xff & (n >> 8);
P->data[P->end++] = 0xff & (n >> 0);
P->end = p;
return 0;
} /* dns_sshfp_push() */
static int dns_sshfp_push0(struct dns_packet *P, void *fp) {
return dns_sshfp_push (P, fp);
}
int dns_sshfp_cmp(const struct dns_sshfp *a, const struct dns_sshfp *b) {
int cmp;
if ((cmp = a->algo - b->algo) || (cmp = a->type - b->type))
return cmp;
switch (a->type) {
case DNS_SSHFP_SHA1:
return memcmp(a->digest.sha1, b->digest.sha1, sizeof a->digest.sha1);
default:
return 0;
} /* switch() */
/* NOT REACHED */
} /* dns_sshfp_cmp() */
static int dns_sshfp_cmp0(const void *a, const void *b) {
return dns_sshfp_cmp (a, b);
}
size_t dns_sshfp_print(void *_dst, size_t lim, struct dns_sshfp *fp) {
static const unsigned char hex[16] = "0123456789abcdef";
struct dns_buf dst = DNS_B_INTO(_dst, lim);
size_t i;
dns_b_fmtju(&dst, fp->algo, 0);
dns_b_putc(&dst, ' ');
dns_b_fmtju(&dst, fp->type, 0);
dns_b_putc(&dst, ' ');
switch (fp->type) {
case DNS_SSHFP_SHA1:
for (i = 0; i < sizeof fp->digest.sha1; i++) {
dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 4)]);
dns_b_putc(&dst, hex[0x0f & (fp->digest.sha1[i] >> 0)]);
}
break;
default:
dns_b_putc(&dst, '0');
break;
} /* switch() */
return dns_b_strllen(&dst);
} /* dns_sshfp_print() */
static size_t dns_sshfp_print0 (void *_dst, size_t lim, void *fp) {
return dns_sshfp_print (_dst, lim, fp);
}
struct dns_txt *dns_txt_init(struct dns_txt *txt, size_t size) {
assert(size > offsetof(struct dns_txt, data));
txt->size = size - offsetof(struct dns_txt, data);
txt->len = 0;
return txt;
} /* dns_txt_init() */
static union dns_any *dns_txt_initany(union dns_any *any, size_t size) {
/* NB: union dns_any is already initialized as struct dns_txt */
(void)size;
return any;
} /* dns_txt_initany() */
int dns_txt_parse(struct dns_txt *txt, struct dns_rr *rr, struct dns_packet *P) {
struct { unsigned char *b; size_t p, end; } dst, src;
unsigned n;
dst.b = txt->data;
dst.p = 0;
dst.end = txt->size;
src.b = P->data;
src.p = rr->rd.p;
src.end = src.p + rr->rd.len;
while (src.p < src.end) {
n = 0xff & P->data[src.p++];
if (src.end - src.p < n || dst.end - dst.p < n)
return DNS_EILLEGAL;
memcpy(&dst.b[dst.p], &src.b[src.p], n);
dst.p += n;
src.p += n;
}
txt->len = dst.p;
return 0;
} /* dns_txt_parse() */
static int dns_txt_parse0(union dns_any *arg0, struct dns_rr *rr, struct dns_packet *P) {
return dns_txt_parse (&arg0->txt, rr, P);
}
int dns_txt_push(struct dns_packet *P, struct dns_txt *txt) {
struct { unsigned char *b; size_t p, end; } dst, src;
unsigned n;
dst.b = P->data;
dst.p = P->end;
dst.end = P->size;
src.b = txt->data;
src.p = 0;
src.end = txt->len;
if (dst.end - dst.p < 2)
return DNS_ENOBUFS;
n = txt->len + ((txt->len + 254) / 255);
dst.b[dst.p++] = 0xff & (n >> 8);
dst.b[dst.p++] = 0xff & (n >> 0);
while (src.p < src.end) {
n = DNS_PP_MIN(255, src.end - src.p);
if (dst.p >= dst.end)
return DNS_ENOBUFS;
dst.b[dst.p++] = n;
if (dst.end - dst.p < n)
return DNS_ENOBUFS;
memcpy(&dst.b[dst.p], &src.b[src.p], n);
dst.p += n;
src.p += n;
}
P->end = dst.p;
return 0;
} /* dns_txt_push() */
static int dns_txt_push0(struct dns_packet *P, void *txt) {
return dns_txt_push(P, txt);
}
int dns_txt_cmp(const struct dns_txt *a, const struct dns_txt *b) {
(void)a;
(void)b;
return -1;
} /* dns_txt_cmp() */
static int dns_txt_cmp0(const void *a, const void *b) {
return dns_txt_cmp (a, b);
}
size_t dns_txt_print(void *_dst, size_t lim, struct dns_txt *txt) {
struct dns_buf src = DNS_B_FROM(txt->data, txt->len);
struct dns_buf dst = DNS_B_INTO(_dst, lim);
unsigned i;
if (src.p < src.pe) {
do {
dns_b_putc(&dst, '"');
for (i = 0; i < 256 && src.p < src.pe; i++, src.p++) {
if (*src.p < 32 || *src.p > 126 || *src.p == '"' || *src.p == '\\') {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, *src.p, 3);
} else {
dns_b_putc(&dst, *src.p);
}
}
dns_b_putc(&dst, '"');
dns_b_putc(&dst, ' ');
} while (src.p < src.pe);
dns_b_popc(&dst);
} else {
dns_b_putc(&dst, '"');
dns_b_putc(&dst, '"');
}
return dns_b_strllen(&dst);
} /* dns_txt_print() */
static size_t dns_txt_print0(void *_dst, size_t lim, void *txt) {
return dns_txt_print (_dst, lim, txt);
}
static const struct dns_rrtype {
enum dns_type type;
const char *name;
union dns_any *(*init)(union dns_any *, size_t);
int (*parse)(union dns_any *, struct dns_rr *, struct dns_packet *);
int (*push)(struct dns_packet *, void *);
int (*cmp)(const void *, const void *);
size_t (*print)(void *, size_t, void *);
size_t (*cname)(void *, size_t, void *);
} dns_rrtypes[] = {
{ DNS_T_A, "A", 0, &dns_a_parse0, &dns_a_push0, &dns_a_cmp0, &dns_a_print0, 0, },
{ DNS_T_AAAA, "AAAA", 0, &dns_aaaa_parse0, &dns_aaaa_push0, &dns_aaaa_cmp0, &dns_aaaa_print0, 0, },
{ DNS_T_MX, "MX", 0, &dns_mx_parse0, &dns_mx_push0, &dns_mx_cmp0, &dns_mx_print0, &dns_mx_cname0, },
{ DNS_T_NS, "NS", 0, &dns_ns_parse0, &dns_ns_push0, &dns_ns_cmp0, &dns_ns_print0, &dns_ns_cname0, },
{ DNS_T_CNAME, "CNAME", 0, &dns_cname_parse0, &dns_cname_push0, &dns_cname_cmp0, &dns_cname_print0, &dns_cname_cname0,},
{ DNS_T_SOA, "SOA", 0, &dns_soa_parse0, &dns_soa_push0, &dns_soa_cmp0, &dns_soa_print0, 0, },
{ DNS_T_SRV, "SRV", 0, &dns_srv_parse0, &dns_srv_push0, &dns_srv_cmp0, &dns_srv_print0, &dns_srv_cname0, },
{ DNS_T_OPT, "OPT", &dns_opt_initany, &dns_opt_parse0, &dns_opt_push0, &dns_opt_cmp0, &dns_opt_print0, 0, },
{ DNS_T_PTR, "PTR", 0, &dns_ptr_parse0, &dns_ptr_push0, &dns_ptr_cmp0, &dns_ptr_print0, &dns_ptr_cname0, },
{ DNS_T_TXT, "TXT", &dns_txt_initany, &dns_txt_parse0, &dns_txt_push0, &dns_txt_cmp0, &dns_txt_print0, 0, },
{ DNS_T_SPF, "SPF", &dns_txt_initany, &dns_txt_parse0, &dns_txt_push0, &dns_txt_cmp0, &dns_txt_print0, 0, },
{ DNS_T_SSHFP, "SSHFP", 0, &dns_sshfp_parse0, &dns_sshfp_push0, &dns_sshfp_cmp0, &dns_sshfp_print0, 0, },
{ DNS_T_AXFR, "AXFR", 0, 0, 0, 0, 0, 0, },
}; /* dns_rrtypes[] */
static const struct dns_rrtype *dns_rrtype(enum dns_type type) {
const struct dns_rrtype *t;
for (t = dns_rrtypes; t < endof(dns_rrtypes); t++) {
if (t->type == type && t->parse) {
return t;
}
}
return NULL;
} /* dns_rrtype() */
union dns_any *dns_any_init(union dns_any *any, size_t size) {
dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
return (union dns_any *)dns_txt_init(&any->rdata, size);
} /* dns_any_init() */
static size_t dns_any_sizeof(union dns_any *any) {
dns_static_assert(dns_same_type(any->txt, any->rdata, 1), "unexpected rdata type");
return offsetof(struct dns_txt, data) + any->rdata.size;
} /* dns_any_sizeof() */
static union dns_any *dns_any_reinit(union dns_any *any, const struct dns_rrtype *t) {
return (t->init)? t->init(any, dns_any_sizeof(any)) : any;
} /* dns_any_reinit() */
int dns_any_parse(union dns_any *any, struct dns_rr *rr, struct dns_packet *P) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(rr->type)))
return t->parse(dns_any_reinit(any, t), rr, P);
if (rr->rd.len > any->rdata.size)
return DNS_EILLEGAL;
memcpy(any->rdata.data, &P->data[rr->rd.p], rr->rd.len);
any->rdata.len = rr->rd.len;
return 0;
} /* dns_any_parse() */
int dns_any_push(struct dns_packet *P, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(type)))
return t->push(P, any);
if (P->size - P->end < any->rdata.len + 2)
return DNS_ENOBUFS;
P->data[P->end++] = 0xff & (any->rdata.len >> 8);
P->data[P->end++] = 0xff & (any->rdata.len >> 0);
memcpy(&P->data[P->end], any->rdata.data, any->rdata.len);
P->end += any->rdata.len;
return 0;
} /* dns_any_push() */
int dns_any_cmp(const union dns_any *a, enum dns_type x, const union dns_any *b, enum dns_type y) {
const struct dns_rrtype *t;
int cmp;
if ((cmp = x - y))
return cmp;
if ((t = dns_rrtype(x)))
return t->cmp(a, b);
return -1;
} /* dns_any_cmp() */
size_t dns_any_print(void *_dst, size_t lim, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
struct dns_buf src, dst;
if ((t = dns_rrtype(type)))
return t->print(_dst, lim, any);
dns_b_from(&src, any->rdata.data, any->rdata.len);
dns_b_into(&dst, _dst, lim);
dns_b_putc(&dst, '"');
while (src.p < src.pe) {
dns_b_putc(&dst, '\\');
dns_b_fmtju(&dst, *src.p++, 3);
}
dns_b_putc(&dst, '"');
return dns_b_strllen(&dst);
} /* dns_any_print() */
size_t dns_any_cname(void *dst, size_t lim, union dns_any *any, enum dns_type type) {
const struct dns_rrtype *t;
if ((t = dns_rrtype(type)) && t->cname)
return t->cname(dst, lim, any);
return 0;
} /* dns_any_cname() */
/*
* E V E N T T R A C I N G R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#include <float.h> /* DBL_MANT_DIG */
#include <inttypes.h> /* PRIu64 */
/* for default trace ID generation try to fit in lua_Number, usually double */
#define DNS_TRACE_ID_BITS DNS_PP_MIN(DBL_MANT_DIG, (sizeof (dns_trace_id_t) * CHAR_BIT)) /* assuming FLT_RADIX == 2 */
#define DNS_TRACE_ID_MASK (((DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)) - 1) | (DNS_TRACE_ID_C(1) << (DNS_TRACE_ID_BITS - 1)))
#define DNS_TRACE_ID_PRI PRIu64
static inline dns_trace_id_t dns_trace_mkid(void) {
dns_trace_id_t id = 0;
unsigned r; /* return type of dns_random() */
const size_t id_bit = sizeof id * CHAR_BIT;
const size_t r_bit = sizeof r * CHAR_BIT;
for (size_t n = 0; n < id_bit; n += r_bit) {
r = dns_random();
id <<= r_bit;
id |= r;
}
return DNS_TRACE_ID_MASK & id;
}
struct dns_trace {
dns_atomic_t refcount;
FILE *fp;
dns_trace_id_t id;
struct {
struct dns_trace_cname {
char host[DNS_D_MAXNAME + 1];
struct sockaddr_storage addr;
} base[4];
size_t p;
} cnames;
};
static void dns_te_initname(struct sockaddr_storage *ss, int fd, int (* STDCALL f)(socket_fd_t, struct sockaddr *, socklen_t *)) {
socklen_t n = sizeof *ss;
if (0 != f(fd, (struct sockaddr *)ss, &n))
goto unspec;
if (n > sizeof *ss)
goto unspec;
return;
unspec:
memset(ss, '\0', sizeof *ss);
ss->ss_family = AF_UNSPEC;
}
static void dns_te_initnames(struct sockaddr_storage *local, struct sockaddr_storage *remote, int fd) {
dns_te_initname(local, fd, &getsockname);
dns_te_initname(remote, fd, &getpeername);
}
static struct dns_trace_event *dns_te_init(struct dns_trace_event *te, int type) {
/* NB: silence valgrind */
memset(te, '\0', offsetof(struct dns_trace_event, data));
te->type = type;
return te;
}
int dns_trace_abi(void) {
return DNS_TRACE_ABI;
}
struct dns_trace *dns_trace_open(FILE *fp, dns_error_t *error) {
static const struct dns_trace trace_initializer = { .refcount = 1 };
struct dns_trace *trace;
if (!(trace = malloc(sizeof *trace)))
goto syerr;
*trace = trace_initializer;
if (fp) {
trace->fp = fp;
}
trace->id = dns_trace_mkid();
return trace;
syerr:
*error = dns_syerr();
dns_trace_close(trace);
return NULL;
} /* dns_trace_open() */
void dns_trace_close(struct dns_trace *trace) {
if (!trace || 1 != dns_trace_release(trace))
return;
if (trace->fp)
fclose(trace->fp);
free(trace);
} /* dns_trace_close() */
dns_refcount_t dns_trace_acquire(struct dns_trace *trace) {
return dns_atomic_fetch_add(&trace->refcount);
} /* dns_trace_acquire() */
static struct dns_trace *dns_trace_acquire_p(struct dns_trace *trace) {
return (trace)? dns_trace_acquire(trace), trace : NULL;
} /* dns_trace_acquire_p() */
dns_refcount_t dns_trace_release(struct dns_trace *trace) {
return dns_atomic_fetch_sub(&trace->refcount);
} /* dns_trace_release() */
dns_trace_id_t dns_trace_id(struct dns_trace *trace) {
return trace->id;
} /* dns_trace_id() */
dns_trace_id_t dns_trace_setid(struct dns_trace *trace, dns_trace_id_t id) {
trace->id = (id)? id : dns_trace_mkid();
return trace->id;
} /* dns_trace_setid() */
struct dns_trace_event *dns_trace_get(struct dns_trace *trace, struct dns_trace_event **tp, dns_error_t *error) {
return dns_trace_fget(tp, trace->fp, error);
} /* dns_trace_get() */
dns_error_t dns_trace_put(struct dns_trace *trace, const struct dns_trace_event *te, const void *data, size_t datasize) {
return dns_trace_fput(te, data, datasize, trace->fp);
} /* dns_trace_put() */
struct dns_trace_event *dns_trace_tag(struct dns_trace *trace, struct dns_trace_event *te) {
struct timeval tv;
te->id = trace->id;
gettimeofday(&tv, NULL);
dns_tv2ts(&te->ts, &tv);
te->abi = DNS_TRACE_ABI;
return te;
} /* dns_trace_tag() */
static dns_error_t dns_trace_tag_and_put(struct dns_trace *trace, struct dns_trace_event *te, const void *data, size_t datasize) {
return dns_trace_put(trace, dns_trace_tag(trace, te), data, datasize);
} /* dns_trace_tag_and_put() */
struct dns_trace_event *dns_trace_fget(struct dns_trace_event **tp, FILE *fp, dns_error_t *error) {
const size_t headsize = offsetof(struct dns_trace_event, data);
struct dns_trace_event tmp, *te;
size_t n;
errno = 0;
if (!(n = fread(&tmp, 1, headsize, fp)))
goto none;
if (n < offsetof(struct dns_trace_event, data))
goto some;
if (!(te = realloc(*tp, DNS_PP_MAX(headsize, tmp.size)))) {
*error = errno;
return NULL;
}
*tp = te;
memcpy(te, &tmp, offsetof(struct dns_trace_event, data));
if (dns_te_datasize(te)) {
errno = 0;
if (!(n = fread(te->data, 1, dns_te_datasize(te), fp)))
goto none;
if (n < dns_te_datasize(te))
goto some;
}
return te;
none:
*error = (ferror(fp))? errno : 0;
return NULL;
some:
*error = 0;
return NULL;
}
dns_error_t dns_trace_fput(const struct dns_trace_event *te, const void *data, size_t datasize, FILE *fp) {
size_t headsize = offsetof(struct dns_trace_event, data);
struct dns_trace_event tmp;
memcpy(&tmp, te, headsize);
tmp.size = headsize + datasize;
/* NB: ignore seek error as fp might not point to a regular file */
(void)fseek(fp, 0, SEEK_END);
if (fwrite(&tmp, 1, headsize, fp) < headsize)
return errno;
if (data)
if (fwrite(data, 1, datasize, fp) < datasize)
return errno;
if (fflush(fp))
return errno;
return 0;
}
static void dns_trace_setcname(struct dns_trace *trace, const char *host, const struct sockaddr *addr) {
struct dns_trace_cname *cname;
if (!trace || !trace->fp)
return;
cname = &trace->cnames.base[trace->cnames.p];
dns_strlcpy(cname->host, host, sizeof cname->host);
memcpy(&cname->addr, addr, DNS_PP_MIN(dns_sa_len(addr), sizeof cname->addr));
trace->cnames.p = (trace->cnames.p + 1) % lengthof(trace->cnames.base);
}
static const char *dns_trace_cname(struct dns_trace *trace, const struct sockaddr *addr) {
if (!trace || !trace->fp)
return NULL;
/* NB: start search from the write cursor to */
for (const struct dns_trace_cname *cname = trace->cnames.base; cname < endof(trace->cnames.base); cname++) {
if (0 == dns_sa_cmp((struct sockaddr *)addr, (struct sockaddr *)&cname->addr))
return cname->host;
}
return NULL;
}
static void dns_trace_res_submit(struct dns_trace *trace, const char *qname, enum dns_type qtype, enum dns_class qclass, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_RES_SUBMIT);
dns_strlcpy(te.res_submit.qname, qname, sizeof te.res_submit.qname);
te.res_submit.qtype = qtype;
te.res_submit.qclass = qclass;
te.res_submit.error = error;
dns_trace_tag_and_put(trace, &te, NULL, 0);
}
static void dns_trace_res_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
const void *data;
size_t datasize;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_RES_FETCH);
data = (packet)? packet->data : NULL;
datasize = (packet)? packet->end : 0;
te.res_fetch.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_so_submit(struct dns_trace *trace, const struct dns_packet *packet, const struct sockaddr *haddr, int error) {
struct dns_trace_event te;
const char *cname;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_SUBMIT);
memcpy(&te.so_submit.haddr, haddr, DNS_PP_MIN(dns_sa_len(haddr), sizeof te.so_submit.haddr));
if ((cname = dns_trace_cname(trace, haddr)))
dns_strlcpy(te.so_submit.hname, cname, sizeof te.so_submit.hname);
te.so_submit.error = error;
dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
}
static void dns_trace_so_verify(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_VERIFY);
te.so_verify.error = error;
dns_trace_tag_and_put(trace, &te, packet->data, packet->end);
}
static void dns_trace_so_fetch(struct dns_trace *trace, const struct dns_packet *packet, int error) {
struct dns_trace_event te;
const void *data;
size_t datasize;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SO_FETCH);
data = (packet)? packet->data : NULL;
datasize = (packet)? packet->end : 0;
te.so_fetch.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_sys_connect(struct dns_trace *trace, int fd, int socktype, const struct sockaddr *dst, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_CONNECT);
dns_te_initname(&te.sys_connect.src, fd, &getsockname);
memcpy(&te.sys_connect.dst, dst, DNS_PP_MIN(dns_sa_len(dst), sizeof te.sys_connect.dst));
te.sys_connect.socktype = socktype;
te.sys_connect.error = error;
dns_trace_tag_and_put(trace, &te, NULL, 0);
}
static void dns_trace_sys_send(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_SEND);
dns_te_initnames(&te.sys_send.src, &te.sys_send.dst, fd);
te.sys_send.socktype = socktype;
te.sys_send.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static void dns_trace_sys_recv(struct dns_trace *trace, int fd, int socktype, const void *data, size_t datasize, int error) {
struct dns_trace_event te;
if (!trace || !trace->fp)
return;
dns_te_init(&te, DNS_TE_SYS_RECV);
dns_te_initnames(&te.sys_recv.dst, &te.sys_recv.src, fd);
te.sys_recv.socktype = socktype;
te.sys_recv.error = error;
dns_trace_tag_and_put(trace, &te, data, datasize);
}
static dns_error_t dns_trace_dump_packet(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
struct dns_packet *packet = NULL;
char *line = NULL, *p;
size_t size = 1, skip = 0;
struct dns_rr_i records;
struct dns_p_lines_i lines;
size_t len, count;
int error;
if (!(packet = dns_p_make(datasize, &error)))
goto error;
memcpy(packet->data, data, datasize);
packet->end = datasize;
(void)dns_p_study(packet);
resize:
if (!(p = dns_reallocarray(line, size, 2, &error)))
goto error;
line = p;
size *= 2;
memset(&records, 0, sizeof records);
memset(&lines, 0, sizeof lines);
count = 0;
while ((len = dns_p_lines(line, size, &error, packet, &records, &lines))) {
if (!(len < size)) {
skip = count;
goto resize;
} else if (skip <= count) {
fputs(prefix, fp);
fwrite(line, 1, len, fp);
}
count++;
}
if (error)
goto error;
error = 0;
error:
free(line);
dns_p_free(packet);
return error;
}
static dns_error_t dns_trace_dump_data(struct dns_trace *trace, const char *prefix, const unsigned char *data, size_t datasize, FILE *fp) {
struct dns_hxd_lines_i lines = { 0 };
char line[128];
size_t len;
while ((len = dns_hxd_lines(line, sizeof line, data, datasize, &lines))) {
if (len >= sizeof line)
return EOVERFLOW; /* shouldn't be possible */
fputs(prefix, fp);
fwrite(line, 1, len, fp);
}
return 0;
}
static dns_error_t dns_trace_dump_addr(struct dns_trace *trace, const char *prefix, const struct sockaddr_storage *ss, FILE *fp) {
const void *addr;
const char *path;
socklen_t len;
int error;
if ((addr = dns_sa_addr(ss->ss_family, (struct sockaddr *)ss, NULL))) {
char ip[INET6_ADDRSTRLEN + 1];
if ((error = dns_ntop(ss->ss_family, addr, ip, sizeof ip)))
return error;
fprintf(fp, "%s%s\n", prefix, ip);
} else if ((path = dns_sa_path((struct sockaddr *)ss, &len))) {
fprintf(fp, "%sunix:%.*s", prefix, (int)len, path);
} else {
return EINVAL;
}
return 0;
}
static dns_error_t dns_trace_dump_meta(struct dns_trace *trace, const char *prefix, const struct dns_trace_event *te, dns_microseconds_t elapsed, FILE *fp) {
char time_s[48], elapsed_s[48];
dns_utime_print(time_s, sizeof time_s, dns_ts2us(&te->ts, 0));
dns_utime_print(elapsed_s, sizeof elapsed_s, elapsed);
fprintf(fp, "%sid: %"DNS_TRACE_ID_PRI"\n", prefix, te->id);
fprintf(fp, "%sts: %s (%s)\n", prefix, time_s, elapsed_s);
fprintf(fp, "%sabi: 0x%x (0x%x)\n", prefix, te->abi, DNS_TRACE_ABI);
return 0;
}
static dns_error_t dns_trace_dump_error(struct dns_trace *trace, const char *prefix, int error, FILE *fp) {
fprintf(fp, "%s%d (%s)\n", prefix, error, (error)? dns_strerror(error) : "none");
return 0;
}
dns_error_t dns_trace_dump(struct dns_trace *trace, FILE *fp) {
struct dns_trace_event *te = NULL;
struct {
dns_trace_id_t id;
dns_microseconds_t begin, elapsed;
} state = { 0 };
int error;
char __dst[DNS_STRMAXLEN + 1] = { 0 };
if (!trace || !trace->fp)
return EINVAL;
if (0 != fseek(trace->fp, 0, SEEK_SET))
goto syerr;
while (dns_trace_fget(&te, trace->fp, &error)) {
size_t datasize = dns_te_datasize(te);
const unsigned char *data = (datasize)? te->data : NULL;
if (state.id != te->id) {
state.id = te->id;
state.begin = dns_ts2us(&te->ts, 0);
}
dns_time_diff(&state.elapsed, dns_ts2us(&te->ts, 0), state.begin);
switch(te->type) {
case DNS_TE_RES_SUBMIT:
fprintf(fp, "dns_res_submit:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
fprintf(fp, " qname: %s\n", te->res_submit.qname);
fprintf(fp, " qtype: %s\n", dns_strtype(te->res_submit.qtype, __dst));
fprintf(fp, " qclass: %s\n", dns_strclass(te->res_submit.qclass, __dst));
dns_trace_dump_error(trace, " error: ", te->res_submit.error, fp);
break;
case DNS_TE_RES_FETCH:
fprintf(fp, "dns_res_fetch:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->res_fetch.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_SUBMIT:
fprintf(fp, "dns_so_submit:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
fprintf(fp, " hname: %s\n", te->so_submit.hname);
dns_trace_dump_addr(trace, " haddr: ", &te->so_submit.haddr, fp);
dns_trace_dump_error(trace, " error: ", te->so_submit.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_VERIFY:
fprintf(fp, "dns_so_verify:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->so_verify.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SO_FETCH:
fprintf(fp, "dns_so_fetch:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_error(trace, " error: ", te->so_fetch.error, fp);
if (data) {
fprintf(fp, " packet: |\n");
if ((error = dns_trace_dump_packet(trace, " ", data, datasize, fp)))
goto error;
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
case DNS_TE_SYS_CONNECT: {
int socktype = te->sys_connect.socktype;
fprintf(fp, "dns_sys_connect:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_connect.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_connect.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_connect.error, fp);
break;
}
case DNS_TE_SYS_SEND: {
int socktype = te->sys_send.socktype;
fprintf(fp, "dns_sys_send:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_send.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_send.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_send.error, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
case DNS_TE_SYS_RECV: {
int socktype = te->sys_recv.socktype;
fprintf(fp, "dns_sys_recv:\n");
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
dns_trace_dump_addr(trace, " src: ", &te->sys_recv.src, fp);
dns_trace_dump_addr(trace, " dst: ", &te->sys_recv.dst, fp);
fprintf(fp, " socktype: %d (%s)\n", socktype, ((socktype == SOCK_STREAM)? "SOCK_STREAM" : (socktype == SOCK_DGRAM)? "SOCK_DGRAM" : "?"));
dns_trace_dump_error(trace, " error: ", te->sys_recv.error, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
default:
fprintf(fp, "unknown(0x%.2x):\n", te->type);
dns_trace_dump_meta(trace, " ", te, state.elapsed, fp);
if (data) {
fprintf(fp, " data: |\n");
if ((error = dns_trace_dump_data(trace, " ", data, datasize, fp)))
goto error;
}
break;
}
}
goto epilog;
syerr:
error = errno;
error:
(void)0;
epilog:
free(te);
return error;
}
/*
* H O S T S R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_hosts {
struct dns_hosts_entry {
char host[DNS_D_MAXNAME + 1];
char arpa[73 + 1];
int af;
union {
struct in_addr a4;
struct in6_addr a6;
} addr;
_Bool alias;
struct dns_hosts_entry *next;
} *head, **tail;
dns_atomic_t refcount;
}; /* struct dns_hosts */
struct dns_hosts *dns_hosts_open(int *error) {
static const struct dns_hosts hosts_initializer = { .refcount = 1 };
struct dns_hosts *hosts;
if (!(hosts = malloc(sizeof *hosts)))
goto syerr;
*hosts = hosts_initializer;
hosts->tail = &hosts->head;
return hosts;
syerr:
*error = dns_syerr();
free(hosts);
return 0;
} /* dns_hosts_open() */
void dns_hosts_close(struct dns_hosts *hosts) {
struct dns_hosts_entry *ent, *xnt;
if (!hosts || 1 != dns_hosts_release(hosts))
return;
for (ent = hosts->head; ent; ent = xnt) {
xnt = ent->next;
free(ent);
}
free(hosts);
return;
} /* dns_hosts_close() */
dns_refcount_t dns_hosts_acquire(struct dns_hosts *hosts) {
return dns_atomic_fetch_add(&hosts->refcount);
} /* dns_hosts_acquire() */
dns_refcount_t dns_hosts_release(struct dns_hosts *hosts) {
return dns_atomic_fetch_sub(&hosts->refcount);
} /* dns_hosts_release() */
struct dns_hosts *dns_hosts_mortal(struct dns_hosts *hosts) {
if (hosts)
dns_hosts_release(hosts);
return hosts;
} /* dns_hosts_mortal() */
struct dns_hosts *dns_hosts_local(int *error_) {
struct dns_hosts *hosts;
int error;
if (!(hosts = dns_hosts_open(&error)))
goto error;
if ((error = dns_hosts_loadpath(hosts, "/etc/hosts")))
goto error;
return hosts;
error:
*error_ = error;
dns_hosts_close(hosts);
return 0;
} /* dns_hosts_local() */
#define dns_hosts_issep(ch) (dns_isspace(ch))
#define dns_hosts_iscom(ch) ((ch) == '#' || (ch) == ';')
int dns_hosts_loadfile(struct dns_hosts *hosts, FILE *fp) {
struct dns_hosts_entry ent;
char word[DNS_PP_MAX(INET6_ADDRSTRLEN, DNS_D_MAXNAME) + 1];
unsigned wp, wc, skip;
int ch, error;
rewind(fp);
do {
memset(&ent, '\0', sizeof ent);
wc = 0;
skip = 0;
do {
memset(word, '\0', sizeof word);
wp = 0;
while (EOF != (ch = fgetc(fp)) && ch != '\n') {
skip |= !!dns_hosts_iscom(ch);
if (skip)
continue;
if (dns_hosts_issep(ch))
break;
if (wp < sizeof word - 1)
word[wp] = ch;
wp++;
}
if (!wp)
continue;
wc++;
switch (wc) {
case 0:
break;
case 1:
ent.af = (strchr(word, ':'))? AF_INET6 : AF_INET;
skip = (1 != dns_inet_pton(ent.af, word, &ent.addr));
break;
default:
if (!wp)
break;
dns_d_anchor(ent.host, sizeof ent.host, word, wp);
if ((error = dns_hosts_insert(hosts, ent.af, &ent.addr, ent.host, (wc > 2))))
return error;
break;
} /* switch() */
} while (ch != EOF && ch != '\n');
} while (ch != EOF);
return 0;
} /* dns_hosts_loadfile() */
int dns_hosts_loadpath(struct dns_hosts *hosts, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_hosts_loadfile(hosts, fp);
fclose(fp);
return error;
} /* dns_hosts_loadpath() */
int dns_hosts_dump(struct dns_hosts *hosts, FILE *fp) {
struct dns_hosts_entry *ent, *xnt;
char addr[INET6_ADDRSTRLEN + 1];
unsigned i;
for (ent = hosts->head; ent; ent = xnt) {
xnt = ent->next;
dns_inet_ntop(ent->af, &ent->addr, addr, sizeof addr);
fputs(addr, fp);
for (i = strlen(addr); i < INET_ADDRSTRLEN; i++)
fputc(' ', fp);
fputc(' ', fp);
fputs(ent->host, fp);
fputc('\n', fp);
}
return 0;
} /* dns_hosts_dump() */
int dns_hosts_insert(struct dns_hosts *hosts, int af, const void *addr, const void *host, _Bool alias) {
struct dns_hosts_entry *ent;
int error;
if (!(ent = malloc(sizeof *ent)))
goto syerr;
dns_d_anchor(ent->host, sizeof ent->host, host, strlen(host));
switch ((ent->af = af)) {
case AF_INET6:
memcpy(&ent->addr.a6, addr, sizeof ent->addr.a6);
dns_aaaa_arpa(ent->arpa, sizeof ent->arpa, addr);
break;
case AF_INET:
memcpy(&ent->addr.a4, addr, sizeof ent->addr.a4);
dns_a_arpa(ent->arpa, sizeof ent->arpa, addr);
break;
default:
error = EINVAL;
goto error;
} /* switch() */
ent->alias = alias;
ent->next = 0;
*hosts->tail = ent;
hosts->tail = &ent->next;
return 0;
syerr:
error = dns_syerr();
error:
free(ent);
return error;
} /* dns_hosts_insert() */
struct dns_packet *dns_hosts_query(struct dns_hosts *hosts, struct dns_packet *Q, int *error_) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *P = dns_p_init(&P_instance.p, 512);
struct dns_packet *A = 0;
struct dns_rr rr;
struct dns_hosts_entry *ent;
int error, af;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
if ((error = dns_rr_parse(&rr, 12, Q)))
goto error;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, Q, &error)))
goto error;
else if (qlen >= sizeof qname)
goto toolong;
if ((error = dns_p_push(P, DNS_S_QD, qname, qlen, rr.type, rr.class, 0, 0)))
goto error;
switch (rr.type) {
case DNS_T_PTR:
for (ent = hosts->head; ent; ent = ent->next) {
if (ent->alias || 0 != strcasecmp(qname, ent->arpa))
continue;
if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, ent->host)))
goto error;
}
break;
case DNS_T_AAAA:
af = AF_INET6;
goto loop;
case DNS_T_A:
af = AF_INET;
loop: for (ent = hosts->head; ent; ent = ent->next) {
if (ent->af != af || 0 != strcasecmp(qname, ent->host))
continue;
if ((error = dns_p_push(P, DNS_S_AN, qname, qlen, rr.type, rr.class, 0, &ent->addr)))
goto error;
}
break;
default:
break;
} /* switch() */
if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
goto error;
return A;
toolong:
error = DNS_EILLEGAL;
error:
*error_ = error;
dns_p_free(A);
return 0;
} /* dns_hosts_query() */
/*
* R E S O L V . C O N F R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_resolv_conf *dns_resconf_open(int *error) {
static const struct dns_resolv_conf resconf_initializer = {
.lookup = "bf",
.family = { AF_INET, AF_INET6 },
.options = { .ndots = 1, .timeout = 5, .attempts = 2, .tcp = DNS_RESCONF_TCP_ENABLE, },
.iface = { .ss_family = AF_INET },
};
struct dns_resolv_conf *resconf;
struct sockaddr_in *sin;
if (!(resconf = malloc(sizeof *resconf)))
goto syerr;
*resconf = resconf_initializer;
sin = (struct sockaddr_in *)&resconf->nameserver[0];
sin->sin_family = AF_INET;
sin->sin_addr.s_addr = INADDR_ANY;
sin->sin_port = htons(53);
#if defined(SA_LEN)
sin->sin_len = sizeof *sin;
#endif
if (0 != gethostname(resconf->search[0], sizeof resconf->search[0]))
goto syerr;
/*
* If gethostname() returned a string without any label
* separator, then search[0][0] should be NUL.
*/
if (strchr (resconf->search[0], '.')) {
dns_d_anchor(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
dns_d_cleave(resconf->search[0], sizeof resconf->search[0], resconf->search[0], strlen(resconf->search[0]));
} else {
memset (resconf->search[0], 0, sizeof resconf->search[0]);
}
dns_resconf_acquire(resconf);
return resconf;
syerr:
*error = dns_syerr();
free(resconf);
return 0;
} /* dns_resconf_open() */
void dns_resconf_close(struct dns_resolv_conf *resconf) {
if (!resconf || 1 != dns_resconf_release(resconf))
return /* void */;
free(resconf);
} /* dns_resconf_close() */
dns_refcount_t dns_resconf_acquire(struct dns_resolv_conf *resconf) {
return dns_atomic_fetch_add(&resconf->_.refcount);
} /* dns_resconf_acquire() */
dns_refcount_t dns_resconf_release(struct dns_resolv_conf *resconf) {
return dns_atomic_fetch_sub(&resconf->_.refcount);
} /* dns_resconf_release() */
struct dns_resolv_conf *dns_resconf_mortal(struct dns_resolv_conf *resconf) {
if (resconf)
dns_resconf_release(resconf);
return resconf;
} /* dns_resconf_mortal() */
struct dns_resolv_conf *dns_resconf_local(int *error_) {
struct dns_resolv_conf *resconf;
int error;
if (!(resconf = dns_resconf_open(&error)))
goto error;
if ((error = dns_resconf_loadpath(resconf, "/etc/resolv.conf"))) {
/*
* NOTE: Both the glibc and BIND9 resolvers ignore a missing
* /etc/resolv.conf, defaulting to a nameserver of
* 127.0.0.1. See also dns_hints_insert_resconf, and the
* default initialization of nameserver[0] in
* dns_resconf_open.
*/
if (error != ENOENT)
goto error;
}
if ((error = dns_nssconf_loadpath(resconf, "/etc/nsswitch.conf"))) {
if (error != ENOENT)
goto error;
}
return resconf;
error:
*error_ = error;
dns_resconf_close(resconf);
return 0;
} /* dns_resconf_local() */
struct dns_resolv_conf *dns_resconf_root(int *error) {
struct dns_resolv_conf *resconf;
if ((resconf = dns_resconf_local(error)))
resconf->options.recurse = 1;
return resconf;
} /* dns_resconf_root() */
static time_t dns_resconf_timeout(const struct dns_resolv_conf *resconf) {
return (time_t)DNS_PP_MIN(INT_MAX, resconf->options.timeout);
} /* dns_resconf_timeout() */
enum dns_resconf_keyword {
DNS_RESCONF_NAMESERVER,
DNS_RESCONF_DOMAIN,
DNS_RESCONF_SEARCH,
DNS_RESCONF_LOOKUP,
DNS_RESCONF_FILE,
DNS_RESCONF_BIND,
DNS_RESCONF_CACHE,
DNS_RESCONF_FAMILY,
DNS_RESCONF_INET4,
DNS_RESCONF_INET6,
DNS_RESCONF_OPTIONS,
DNS_RESCONF_EDNS0,
DNS_RESCONF_NDOTS,
DNS_RESCONF_TIMEOUT,
DNS_RESCONF_ATTEMPTS,
DNS_RESCONF_ROTATE,
DNS_RESCONF_RECURSE,
DNS_RESCONF_SMART,
DNS_RESCONF_TCP,
DNS_RESCONF_TCPx,
DNS_RESCONF_INTERFACE,
DNS_RESCONF_ZERO,
DNS_RESCONF_ONE,
DNS_RESCONF_ENABLE,
DNS_RESCONF_ONLY,
DNS_RESCONF_DISABLE,
}; /* enum dns_resconf_keyword */
static enum dns_resconf_keyword dns_resconf_keyword(const char *word) {
static const char *words[] = {
[DNS_RESCONF_NAMESERVER] = "nameserver",
[DNS_RESCONF_DOMAIN] = "domain",
[DNS_RESCONF_SEARCH] = "search",
[DNS_RESCONF_LOOKUP] = "lookup",
[DNS_RESCONF_FILE] = "file",
[DNS_RESCONF_BIND] = "bind",
[DNS_RESCONF_CACHE] = "cache",
[DNS_RESCONF_FAMILY] = "family",
[DNS_RESCONF_INET4] = "inet4",
[DNS_RESCONF_INET6] = "inet6",
[DNS_RESCONF_OPTIONS] = "options",
[DNS_RESCONF_EDNS0] = "edns0",
[DNS_RESCONF_ROTATE] = "rotate",
[DNS_RESCONF_RECURSE] = "recurse",
[DNS_RESCONF_SMART] = "smart",
[DNS_RESCONF_TCP] = "tcp",
[DNS_RESCONF_INTERFACE] = "interface",
[DNS_RESCONF_ZERO] = "0",
[DNS_RESCONF_ONE] = "1",
[DNS_RESCONF_ENABLE] = "enable",
[DNS_RESCONF_ONLY] = "only",
[DNS_RESCONF_DISABLE] = "disable",
};
unsigned i;
for (i = 0; i < lengthof(words); i++) {
if (words[i] && 0 == strcasecmp(words[i], word))
return i;
}
if (0 == strncasecmp(word, "ndots:", sizeof "ndots:" - 1))
return DNS_RESCONF_NDOTS;
if (0 == strncasecmp(word, "timeout:", sizeof "timeout:" - 1))
return DNS_RESCONF_TIMEOUT;
if (0 == strncasecmp(word, "attempts:", sizeof "attempts:" - 1))
return DNS_RESCONF_ATTEMPTS;
if (0 == strncasecmp(word, "tcp:", sizeof "tcp:" - 1))
return DNS_RESCONF_TCPx;
return -1;
} /* dns_resconf_keyword() */
/** OpenBSD-style "[1.2.3.4]:53" nameserver syntax */
int dns_resconf_pton(struct sockaddr_storage *ss, const char *src) {
struct { char buf[128], *p; } addr = { "", addr.buf };
unsigned short port = 0;
int ch, af = AF_INET, error;
memset(ss, 0, sizeof *ss);
while ((ch = *src++)) {
switch (ch) {
case ' ':
/* FALL THROUGH */
case '\t':
break;
case '[':
break;
case ']':
while ((ch = *src++)) {
if (dns_isdigit(ch)) {
port *= 10;
port += ch - '0';
}
}
goto inet;
case ':':
af = AF_INET6;
/* FALL THROUGH */
default:
if (addr.p < endof(addr.buf) - 1)
*addr.p++ = ch;
break;
} /* switch() */
} /* while() */
inet:
if ((error = dns_pton(af, addr.buf, dns_sa_addr(af, ss, NULL))))
return error;
port = (!port)? 53 : port;
*dns_sa_port(af, ss) = htons(port);
dns_sa_family(ss) = af;
return 0;
} /* dns_resconf_pton() */
#define dns_resconf_issep(ch) (dns_isspace(ch) || (ch) == ',')
#define dns_resconf_iscom(ch) ((ch) == '#' || (ch) == ';')
int dns_resconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
unsigned sa_count = 0;
char words[6][DNS_D_MAXNAME + 1];
unsigned wp, wc, i, j, n;
int ch, error;
rewind(fp);
do {
memset(words, '\0', sizeof words);
wp = 0;
wc = 0;
while (EOF != (ch = getc(fp)) && ch != '\n') {
if (dns_resconf_issep(ch)) {
if (wp > 0) {
wp = 0;
if (++wc >= lengthof(words))
goto skip;
}
} else if (dns_resconf_iscom(ch)) {
skip:
do {
ch = getc(fp);
} while (ch != EOF && ch != '\n');
break;
} else if (wp < sizeof words[wc] - 1) {
words[wc][wp++] = ch;
} else {
wp = 0; /* drop word */
goto skip;
}
}
if (wp > 0)
wc++;
if (wc < 2)
continue;
switch (dns_resconf_keyword(words[0])) {
case DNS_RESCONF_NAMESERVER:
if (sa_count >= lengthof(resconf->nameserver))
continue;
if ((error = dns_resconf_pton(&resconf->nameserver[sa_count], words[1])))
continue;
sa_count++;
break;
case DNS_RESCONF_DOMAIN:
case DNS_RESCONF_SEARCH:
memset(resconf->search, '\0', sizeof resconf->search);
for (i = 1, j = 0; i < wc && j < lengthof(resconf->search); i++, j++)
if (words[i][0] == '.') {
/* Ignore invalid search spec. */
j--;
} else {
dns_d_anchor(resconf->search[j], sizeof resconf->search[j], words[i], strlen(words[i]));
}
break;
case DNS_RESCONF_LOOKUP:
for (i = 1, j = 0; i < wc && j < lengthof(resconf->lookup); i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_FILE:
resconf->lookup[j++] = 'f';
break;
case DNS_RESCONF_BIND:
resconf->lookup[j++] = 'b';
break;
case DNS_RESCONF_CACHE:
resconf->lookup[j++] = 'c';
break;
default:
break;
} /* switch() */
} /* for() */
break;
case DNS_RESCONF_FAMILY:
for (i = 1, j = 0; i < wc && j < lengthof(resconf->family); i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_INET4:
resconf->family[j++] = AF_INET;
break;
case DNS_RESCONF_INET6:
resconf->family[j++] = AF_INET6;
break;
default:
break;
}
}
break;
case DNS_RESCONF_OPTIONS:
for (i = 1; i < wc; i++) {
switch (dns_resconf_keyword(words[i])) {
case DNS_RESCONF_EDNS0:
resconf->options.edns0 = 1;
break;
case DNS_RESCONF_NDOTS:
for (j = sizeof "ndots:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.ndots = n;
break;
case DNS_RESCONF_TIMEOUT:
for (j = sizeof "timeout:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.timeout = n;
break;
case DNS_RESCONF_ATTEMPTS:
for (j = sizeof "attempts:" - 1, n = 0; dns_isdigit(words[i][j]); j++) {
n *= 10;
n += words[i][j] - '0';
} /* for() */
resconf->options.attempts = n;
break;
case DNS_RESCONF_ROTATE:
resconf->options.rotate = 1;
break;
case DNS_RESCONF_RECURSE:
resconf->options.recurse = 1;
break;
case DNS_RESCONF_SMART:
resconf->options.smart = 1;
break;
case DNS_RESCONF_TCP:
resconf->options.tcp = DNS_RESCONF_TCP_ONLY;
break;
case DNS_RESCONF_TCPx:
switch (dns_resconf_keyword(&words[i][sizeof "tcp:" - 1])) {
case DNS_RESCONF_ENABLE:
resconf->options.tcp = DNS_RESCONF_TCP_ENABLE;
break;
case DNS_RESCONF_ONE:
case DNS_RESCONF_ONLY:
resconf->options.tcp = DNS_RESCONF_TCP_ONLY;
break;
case DNS_RESCONF_ZERO:
case DNS_RESCONF_DISABLE:
resconf->options.tcp = DNS_RESCONF_TCP_DISABLE;
break;
default:
break;
} /* switch() */
break;
default:
break;
} /* switch() */
} /* for() */
break;
case DNS_RESCONF_INTERFACE:
for (i = 0, n = 0; dns_isdigit(words[2][i]); i++) {
n *= 10;
n += words[2][i] - '0';
}
dns_resconf_setiface(resconf, words[1], n);
break;
default:
break;
} /* switch() */
} while (ch != EOF);
return 0;
} /* dns_resconf_loadfile() */
int dns_resconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_resconf_loadfile(resconf, fp);
fclose(fp);
return error;
} /* dns_resconf_loadpath() */
struct dns_anyconf {
char *token[16];
unsigned count;
char buffer[1024], *tp, *cp;
}; /* struct dns_anyconf */
static void dns_anyconf_reset(struct dns_anyconf *cf) {
cf->count = 0;
cf->tp = cf->cp = cf->buffer;
} /* dns_anyconf_reset() */
static int dns_anyconf_push(struct dns_anyconf *cf) {
if (!(cf->cp < endof(cf->buffer) && cf->count < lengthof(cf->token)))
return ENOMEM;
*cf->cp++ = '\0';
cf->token[cf->count++] = cf->tp;
cf->tp = cf->cp;
return 0;
} /* dns_anyconf_push() */
static void dns_anyconf_pop(struct dns_anyconf *cf) {
if (cf->count > 0) {
--cf->count;
cf->tp = cf->cp = cf->token[cf->count];
cf->token[cf->count] = 0;
}
} /* dns_anyconf_pop() */
static int dns_anyconf_addc(struct dns_anyconf *cf, int ch) {
if (!(cf->cp < endof(cf->buffer)))
return ENOMEM;
*cf->cp++ = ch;
return 0;
} /* dns_anyconf_addc() */
static _Bool dns_anyconf_match(const char *pat, int mc) {
_Bool match;
int pc;
if (*pat == '^') {
match = 0;
++pat;
} else {
match = 1;
}
while ((pc = *(const unsigned char *)pat++)) {
switch (pc) {
case '%':
if (!(pc = *(const unsigned char *)pat++))
return !match;
switch (pc) {
case 'a':
if (dns_isalpha(mc))
return match;
break;
case 'd':
if (dns_isdigit(mc))
return match;
break;
case 'w':
if (dns_isalnum(mc))
return match;
break;
case 's':
if (dns_isspace(mc))
return match;
break;
default:
if (mc == pc)
return match;
break;
} /* switch() */
break;
default:
if (mc == pc)
return match;
break;
} /* switch() */
} /* while() */
return !match;
} /* dns_anyconf_match() */
static int dns_anyconf_peek(FILE *fp) {
int ch;
ch = getc(fp);
ungetc(ch, fp);
return ch;
} /* dns_anyconf_peek() */
static size_t dns_anyconf_skip(const char *pat, FILE *fp) {
size_t count = 0;
int ch;
while (EOF != (ch = getc(fp))) {
if (dns_anyconf_match(pat, ch)) {
count++;
continue;
}
ungetc(ch, fp);
break;
}
return count;
} /* dns_anyconf_skip() */
static size_t dns_anyconf_scan(struct dns_anyconf *cf, const char *pat, FILE *fp, int *error) {
size_t len;
int ch;
while (EOF != (ch = getc(fp))) {
if (dns_anyconf_match(pat, ch)) {
if ((*error = dns_anyconf_addc(cf, ch)))
return 0;
continue;
} else {
ungetc(ch, fp);
break;
}
}
if ((len = cf->cp - cf->tp)) {
if ((*error = dns_anyconf_push(cf)))
return 0;
return len;
} else {
*error = 0;
return 0;
}
} /* dns_anyconf_scan() */
DNS_NOTUSED static void dns_anyconf_dump(struct dns_anyconf *cf, FILE *fp) {
unsigned i;
fprintf(fp, "tokens:");
for (i = 0; i < cf->count; i++) {
fprintf(fp, " %s", cf->token[i]);
}
fputc('\n', fp);
} /* dns_anyconf_dump() */
enum dns_nssconf_keyword {
DNS_NSSCONF_INVALID = 0,
DNS_NSSCONF_HOSTS = 1,
DNS_NSSCONF_SUCCESS,
DNS_NSSCONF_NOTFOUND,
DNS_NSSCONF_UNAVAIL,
DNS_NSSCONF_TRYAGAIN,
DNS_NSSCONF_CONTINUE,
DNS_NSSCONF_RETURN,
DNS_NSSCONF_FILES,
DNS_NSSCONF_DNS,
DNS_NSSCONF_MDNS,
DNS_NSSCONF_LAST,
}; /* enum dns_nssconf_keyword */
static enum dns_nssconf_keyword dns_nssconf_keyword(const char *word) {
static const char *list[] = {
[DNS_NSSCONF_HOSTS] = "hosts",
[DNS_NSSCONF_SUCCESS] = "success",
[DNS_NSSCONF_NOTFOUND] = "notfound",
[DNS_NSSCONF_UNAVAIL] = "unavail",
[DNS_NSSCONF_TRYAGAIN] = "tryagain",
[DNS_NSSCONF_CONTINUE] = "continue",
[DNS_NSSCONF_RETURN] = "return",
[DNS_NSSCONF_FILES] = "files",
[DNS_NSSCONF_DNS] = "dns",
[DNS_NSSCONF_MDNS] = "mdns",
};
unsigned i;
for (i = 1; i < lengthof(list); i++) {
if (list[i] && 0 == strcasecmp(list[i], word))
return i;
}
return DNS_NSSCONF_INVALID;
} /* dns_nssconf_keyword() */
static enum dns_nssconf_keyword dns_nssconf_c2k(int ch) {
static const char map[] = {
['S'] = DNS_NSSCONF_SUCCESS,
['N'] = DNS_NSSCONF_NOTFOUND,
['U'] = DNS_NSSCONF_UNAVAIL,
['T'] = DNS_NSSCONF_TRYAGAIN,
['C'] = DNS_NSSCONF_CONTINUE,
['R'] = DNS_NSSCONF_RETURN,
['f'] = DNS_NSSCONF_FILES,
['F'] = DNS_NSSCONF_FILES,
['d'] = DNS_NSSCONF_DNS,
['D'] = DNS_NSSCONF_DNS,
['b'] = DNS_NSSCONF_DNS,
['B'] = DNS_NSSCONF_DNS,
['m'] = DNS_NSSCONF_MDNS,
['M'] = DNS_NSSCONF_MDNS,
};
return (ch >= 0 && ch < (int)lengthof(map))? map[ch] : DNS_NSSCONF_INVALID;
} /* dns_nssconf_c2k() */
DNS_PRAGMA_PUSH
DNS_PRAGMA_QUIET
static int dns_nssconf_k2c(int k) {
static const char map[DNS_NSSCONF_LAST] = {
[DNS_NSSCONF_SUCCESS] = 'S',
[DNS_NSSCONF_NOTFOUND] = 'N',
[DNS_NSSCONF_UNAVAIL] = 'U',
[DNS_NSSCONF_TRYAGAIN] = 'T',
[DNS_NSSCONF_CONTINUE] = 'C',
[DNS_NSSCONF_RETURN] = 'R',
[DNS_NSSCONF_FILES] = 'f',
[DNS_NSSCONF_DNS] = 'b',
[DNS_NSSCONF_MDNS] = 'm',
};
return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : '?') : '?';
} /* dns_nssconf_k2c() */
static const char *dns_nssconf_k2s(int k) {
static const char *const map[DNS_NSSCONF_LAST] = {
[DNS_NSSCONF_SUCCESS] = "SUCCESS",
[DNS_NSSCONF_NOTFOUND] = "NOTFOUND",
[DNS_NSSCONF_UNAVAIL] = "UNAVAIL",
[DNS_NSSCONF_TRYAGAIN] = "TRYAGAIN",
[DNS_NSSCONF_CONTINUE] = "continue",
[DNS_NSSCONF_RETURN] = "return",
[DNS_NSSCONF_FILES] = "files",
[DNS_NSSCONF_DNS] = "dns",
[DNS_NSSCONF_MDNS] = "mdns",
};
return (k >= 0 && k < (int)lengthof(map))? (map[k]? map[k] : "") : "";
} /* dns_nssconf_k2s() */
DNS_PRAGMA_POP
int dns_nssconf_loadfile(struct dns_resolv_conf *resconf, FILE *fp) {
enum dns_nssconf_keyword source, status, action;
char lookup[sizeof resconf->lookup] = "", *lp;
struct dns_anyconf cf;
size_t i;
int error;
while (!feof(fp) && !ferror(fp)) {
dns_anyconf_reset(&cf);
dns_anyconf_skip("%s", fp);
if (!dns_anyconf_scan(&cf, "%w_", fp, &error))
goto nextent;
if (DNS_NSSCONF_HOSTS != dns_nssconf_keyword(cf.token[0]))
goto nextent;
dns_anyconf_pop(&cf);
if (!dns_anyconf_skip(": \t", fp))
goto nextent;
*(lp = lookup) = '\0';
while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_skip(" \t", fp);
if ('[' == dns_anyconf_peek(fp)) {
dns_anyconf_skip("[! \t", fp);
while (dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_skip("= \t", fp);
if (!dns_anyconf_scan(&cf, "%w_", fp, &error)) {
dns_anyconf_pop(&cf); /* discard status */
dns_anyconf_skip("^#;]\n", fp); /* skip to end of criteria */
break;
}
dns_anyconf_skip(" \t", fp);
}
dns_anyconf_skip("] \t", fp);
}
if ((size_t)(endof(lookup) - lp) < cf.count + 1) /* +1 for '\0' */
goto nextsrc;
source = dns_nssconf_keyword(cf.token[0]);
switch (source) {
case DNS_NSSCONF_DNS:
case DNS_NSSCONF_MDNS:
case DNS_NSSCONF_FILES:
*lp++ = dns_nssconf_k2c(source);
break;
default:
goto nextsrc;
}
for (i = 1; i + 1 < cf.count; i += 2) {
status = dns_nssconf_keyword(cf.token[i]);
action = dns_nssconf_keyword(cf.token[i + 1]);
switch (status) {
case DNS_NSSCONF_SUCCESS:
case DNS_NSSCONF_NOTFOUND:
case DNS_NSSCONF_UNAVAIL:
case DNS_NSSCONF_TRYAGAIN:
*lp++ = dns_nssconf_k2c(status);
break;
default:
continue;
}
switch (action) {
case DNS_NSSCONF_CONTINUE:
case DNS_NSSCONF_RETURN:
break;
default:
action = (status == DNS_NSSCONF_SUCCESS)
? DNS_NSSCONF_RETURN
: DNS_NSSCONF_CONTINUE;
break;
}
*lp++ = dns_nssconf_k2c(action);
}
nextsrc:
*lp = '\0';
dns_anyconf_reset(&cf);
}
nextent:
dns_anyconf_skip("^\n", fp);
}
if (*lookup)
strncpy(resconf->lookup, lookup, sizeof resconf->lookup);
return 0;
} /* dns_nssconf_loadfile() */
int dns_nssconf_loadpath(struct dns_resolv_conf *resconf, const char *path) {
FILE *fp;
int error;
if (!(fp = dns_fopen(path, "rt", &error)))
return error;
error = dns_nssconf_loadfile(resconf, fp);
fclose(fp);
return error;
} /* dns_nssconf_loadpath() */
struct dns_nssconf_source {
enum dns_nssconf_keyword source, success, notfound, unavail, tryagain;
}; /* struct dns_nssconf_source */
typedef unsigned dns_nssconf_i;
static inline int dns_nssconf_peek(const struct dns_resolv_conf *resconf, dns_nssconf_i state) {
return (state < lengthof(resconf->lookup) && resconf->lookup[state])? resconf->lookup[state] : 0;
} /* dns_nssconf_peek() */
static _Bool dns_nssconf_next(struct dns_nssconf_source *src, const struct dns_resolv_conf *resconf, dns_nssconf_i *state) {
int source, status, action;
src->source = DNS_NSSCONF_INVALID;
src->success = DNS_NSSCONF_RETURN;
src->notfound = DNS_NSSCONF_CONTINUE;
src->unavail = DNS_NSSCONF_CONTINUE;
src->tryagain = DNS_NSSCONF_CONTINUE;
while ((source = dns_nssconf_peek(resconf, *state))) {
source = dns_nssconf_c2k(source);
++*state;
switch (source) {
case DNS_NSSCONF_FILES:
case DNS_NSSCONF_DNS:
case DNS_NSSCONF_MDNS:
src->source = source;
break;
default:
continue;
}
while ((status = dns_nssconf_peek(resconf, *state)) && (action = dns_nssconf_peek(resconf, *state + 1))) {
status = dns_nssconf_c2k(status);
action = dns_nssconf_c2k(action);
switch (action) {
case DNS_NSSCONF_RETURN:
case DNS_NSSCONF_CONTINUE:
break;
default:
goto done;
}
switch (status) {
case DNS_NSSCONF_SUCCESS:
src->success = action;
break;
case DNS_NSSCONF_NOTFOUND:
src->notfound = action;
break;
case DNS_NSSCONF_UNAVAIL:
src->unavail = action;
break;
case DNS_NSSCONF_TRYAGAIN:
src->tryagain = action;
break;
default:
goto done;
}
*state += 2;
}
break;
}
done:
return src->source != DNS_NSSCONF_INVALID;
} /* dns_nssconf_next() */
static int dns_nssconf_dump_status(int status, int action, unsigned *count, FILE *fp) {
switch (status) {
case DNS_NSSCONF_SUCCESS:
if (action == DNS_NSSCONF_RETURN)
return 0;
break;
default:
if (action == DNS_NSSCONF_CONTINUE)
return 0;
break;
}
fputc(' ', fp);
if (!*count)
fputc('[', fp);
fprintf(fp, "%s=%s", dns_nssconf_k2s(status), dns_nssconf_k2s(action));
++*count;
return 0;
} /* dns_nssconf_dump_status() */
int dns_nssconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
struct dns_nssconf_source src;
dns_nssconf_i i = 0;
fputs("hosts:", fp);
while (dns_nssconf_next(&src, resconf, &i)) {
unsigned n = 0;
fprintf(fp, " %s", dns_nssconf_k2s(src.source));
dns_nssconf_dump_status(DNS_NSSCONF_SUCCESS, src.success, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_NOTFOUND, src.notfound, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_UNAVAIL, src.unavail, &n, fp);
dns_nssconf_dump_status(DNS_NSSCONF_TRYAGAIN, src.tryagain, &n, fp);
if (n)
fputc(']', fp);
}
fputc('\n', fp);
return 0;
} /* dns_nssconf_dump() */
int dns_resconf_setiface(struct dns_resolv_conf *resconf, const char *addr, unsigned short port) {
int af = (strchr(addr, ':'))? AF_INET6 : AF_INET;
int error;
memset(&resconf->iface, 0, sizeof (struct sockaddr_storage));
if ((error = dns_pton(af, addr, dns_sa_addr(af, &resconf->iface, NULL))))
return error;
*dns_sa_port(af, &resconf->iface) = htons(port);
resconf->iface.ss_family = af;
return 0;
} /* dns_resconf_setiface() */
#define DNS_SM_RESTORE \
do { \
pc = 0xff & (*state >> 0); \
srchi = 0xff & (*state >> 8); \
ndots = 0xff & (*state >> 16); \
} while (0)
#define DNS_SM_SAVE \
do { \
*state = ((0xff & pc) << 0) \
| ((0xff & srchi) << 8) \
| ((0xff & ndots) << 16); \
} while (0)
size_t dns_resconf_search(void *dst, size_t lim, const void *qname, size_t qlen, struct dns_resolv_conf *resconf, dns_resconf_i_t *state) {
unsigned pc, srchi, ndots, len;
DNS_SM_ENTER;
/* if FQDN then return as-is and finish */
if (dns_d_isanchored(qname, qlen)) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
DNS_SM_EXIT;
}
ndots = dns_d_ndots(qname, qlen);
if (ndots >= resconf->options.ndots) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
}
while (srchi < lengthof(resconf->search) && resconf->search[srchi][0]) {
struct dns_buf buf = DNS_B_INTO(dst, lim);
const char *dn = resconf->search[srchi++];
dns_b_put(&buf, qname, qlen);
dns_b_putc(&buf, '.');
dns_b_puts(&buf, dn);
if (!dns_d_isanchored(dn, strlen(dn)))
dns_b_putc(&buf, '.');
len = dns_b_strllen(&buf);
DNS_SM_YIELD(len);
}
if (ndots < resconf->options.ndots) {
len = dns_d_anchor(dst, lim, qname, qlen);
DNS_SM_YIELD(len);
}
DNS_SM_LEAVE;
return dns_strlcpy(dst, "", lim);
} /* dns_resconf_search() */
#undef DNS_SM_SAVE
#undef DNS_SM_RESTORE
int dns_resconf_dump(struct dns_resolv_conf *resconf, FILE *fp) {
unsigned i;
int af;
for (i = 0; i < lengthof(resconf->nameserver) && (af = resconf->nameserver[i].ss_family) != AF_UNSPEC; i++) {
char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
unsigned short port;
dns_inet_ntop(af, dns_sa_addr(af, &resconf->nameserver[i], NULL), addr, sizeof addr);
port = ntohs(*dns_sa_port(af, &resconf->nameserver[i]));
if (port == 53)
fprintf(fp, "nameserver %s\n", addr);
else
fprintf(fp, "nameserver [%s]:%hu\n", addr, port);
}
fprintf(fp, "search");
for (i = 0; i < lengthof(resconf->search) && resconf->search[i][0]; i++)
fprintf(fp, " %s", resconf->search[i]);
fputc('\n', fp);
fputs("; ", fp);
dns_nssconf_dump(resconf, fp);
fprintf(fp, "lookup");
for (i = 0; i < lengthof(resconf->lookup) && resconf->lookup[i]; i++) {
switch (resconf->lookup[i]) {
case 'b':
fprintf(fp, " bind"); break;
case 'f':
fprintf(fp, " file"); break;
case 'c':
fprintf(fp, " cache"); break;
}
}
fputc('\n', fp);
fprintf(fp, "options ndots:%u timeout:%u attempts:%u", resconf->options.ndots, resconf->options.timeout, resconf->options.attempts);
if (resconf->options.edns0)
fprintf(fp, " edns0");
if (resconf->options.rotate)
fprintf(fp, " rotate");
if (resconf->options.recurse)
fprintf(fp, " recurse");
if (resconf->options.smart)
fprintf(fp, " smart");
switch (resconf->options.tcp) {
case DNS_RESCONF_TCP_ENABLE:
break;
case DNS_RESCONF_TCP_ONLY:
fprintf(fp, " tcp");
break;
case DNS_RESCONF_TCP_SOCKS:
fprintf(fp, " tcp:socks");
break;
case DNS_RESCONF_TCP_DISABLE:
fprintf(fp, " tcp:disable");
break;
}
fputc('\n', fp);
if ((af = resconf->iface.ss_family) != AF_UNSPEC) {
char addr[INET6_ADDRSTRLEN + 1] = "[INVALID]";
dns_inet_ntop(af, dns_sa_addr(af, &resconf->iface, NULL), addr, sizeof addr);
fprintf(fp, "interface %s %hu\n", addr, ntohs(*dns_sa_port(af, &resconf->iface)));
}
return 0;
} /* dns_resconf_dump() */
/*
* H I N T S E R V E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_hints_soa {
unsigned char zone[DNS_D_MAXNAME + 1];
struct {
struct sockaddr_storage ss;
unsigned priority;
} addrs[16];
unsigned count;
struct dns_hints_soa *next;
}; /* struct dns_hints_soa */
struct dns_hints {
dns_atomic_t refcount;
struct dns_hints_soa *head;
}; /* struct dns_hints */
struct dns_hints *dns_hints_open(struct dns_resolv_conf *resconf, int *error) {
static const struct dns_hints H_initializer;
struct dns_hints *H;
(void)resconf;
if (!(H = malloc(sizeof *H)))
goto syerr;
*H = H_initializer;
dns_hints_acquire(H);
return H;
syerr:
*error = dns_syerr();
free(H);
return 0;
} /* dns_hints_open() */
void dns_hints_close(struct dns_hints *H) {
struct dns_hints_soa *soa, *nxt;
if (!H || 1 != dns_hints_release(H))
return /* void */;
for (soa = H->head; soa; soa = nxt) {
nxt = soa->next;
free(soa);
}
free(H);
return /* void */;
} /* dns_hints_close() */
dns_refcount_t dns_hints_acquire(struct dns_hints *H) {
return dns_atomic_fetch_add(&H->refcount);
} /* dns_hints_acquire() */
dns_refcount_t dns_hints_release(struct dns_hints *H) {
return dns_atomic_fetch_sub(&H->refcount);
} /* dns_hints_release() */
struct dns_hints *dns_hints_mortal(struct dns_hints *hints) {
if (hints)
dns_hints_release(hints);
return hints;
} /* dns_hints_mortal() */
struct dns_hints *dns_hints_local(struct dns_resolv_conf *resconf, int *error_) {
struct dns_hints *hints = 0;
int error;
if (resconf)
dns_resconf_acquire(resconf);
else if (!(resconf = dns_resconf_local(&error)))
goto error;
if (!(hints = dns_hints_open(resconf, &error)))
goto error;
error = 0;
if (0 == dns_hints_insert_resconf(hints, ".", resconf, &error) && error)
goto error;
dns_resconf_close(resconf);
return hints;
error:
*error_ = error;
dns_resconf_close(resconf);
dns_hints_close(hints);
return 0;
} /* dns_hints_local() */
struct dns_hints *dns_hints_root(struct dns_resolv_conf *resconf, int *error_) {
static const struct {
int af;
char addr[INET6_ADDRSTRLEN];
} root_hints[] = {
{ AF_INET, "198.41.0.4" }, /* A.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:503:ba3e::2:30" }, /* A.ROOT-SERVERS.NET. */
{ AF_INET, "192.228.79.201" }, /* B.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:84::b" }, /* B.ROOT-SERVERS.NET. */
{ AF_INET, "192.33.4.12" }, /* C.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2::c" }, /* C.ROOT-SERVERS.NET. */
{ AF_INET, "199.7.91.13" }, /* D.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2d::d" }, /* D.ROOT-SERVERS.NET. */
{ AF_INET, "192.203.230.10" }, /* E.ROOT-SERVERS.NET. */
{ AF_INET, "192.5.5.241" }, /* F.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:2f::f" }, /* F.ROOT-SERVERS.NET. */
{ AF_INET, "192.112.36.4" }, /* G.ROOT-SERVERS.NET. */
{ AF_INET, "128.63.2.53" }, /* H.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:1::803f:235" }, /* H.ROOT-SERVERS.NET. */
{ AF_INET, "192.36.148.17" }, /* I.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:7FE::53" }, /* I.ROOT-SERVERS.NET. */
{ AF_INET, "192.58.128.30" }, /* J.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:503:c27::2:30" }, /* J.ROOT-SERVERS.NET. */
{ AF_INET, "193.0.14.129" }, /* K.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:7FD::1" }, /* K.ROOT-SERVERS.NET. */
{ AF_INET, "199.7.83.42" }, /* L.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:500:3::42" }, /* L.ROOT-SERVERS.NET. */
{ AF_INET, "202.12.27.33" }, /* M.ROOT-SERVERS.NET. */
{ AF_INET6, "2001:DC3::35" }, /* M.ROOT-SERVERS.NET. */
};
struct dns_hints *hints = 0;
struct sockaddr_storage ss;
unsigned i;
int error, af;
if (!(hints = dns_hints_open(resconf, &error)))
goto error;
for (i = 0; i < lengthof(root_hints); i++) {
af = root_hints[i].af;
memset(&ss, 0, sizeof ss);
if ((error = dns_pton(af, root_hints[i].addr, dns_sa_addr(af, &ss, NULL))))
goto error;
*dns_sa_port(af, &ss) = htons(53);
ss.ss_family = af;
if ((error = dns_hints_insert(hints, ".", (struct sockaddr *)&ss, 1)))
goto error;
}
return hints;
error:
*error_ = error;
dns_hints_close(hints);
return 0;
} /* dns_hints_root() */
static struct dns_hints_soa *dns_hints_fetch(struct dns_hints *H, const char *zone) {
struct dns_hints_soa *soa;
for (soa = H->head; soa; soa = soa->next) {
if (0 == strcasecmp(zone, (char *)soa->zone))
return soa;
}
return 0;
} /* dns_hints_fetch() */
int dns_hints_insert(struct dns_hints *H, const char *zone, const struct sockaddr *sa, unsigned priority) {
static const struct dns_hints_soa soa_initializer;
struct dns_hints_soa *soa;
unsigned i;
if (!(soa = dns_hints_fetch(H, zone))) {
if (!(soa = malloc(sizeof *soa)))
return dns_syerr();
*soa = soa_initializer;
dns_strlcpy((char *)soa->zone, zone, sizeof soa->zone);
soa->next = H->head;
H->head = soa;
}
i = soa->count % lengthof(soa->addrs);
memcpy(&soa->addrs[i].ss, sa, dns_sa_len(sa));
soa->addrs[i].priority = DNS_PP_MAX(1, priority);
if (soa->count < lengthof(soa->addrs))
soa->count++;
return 0;
} /* dns_hints_insert() */
static _Bool dns_hints_isinaddr_any(const void *sa) {
struct in_addr *addr;
if (dns_sa_family(sa) != AF_INET)
return 0;
addr = dns_sa_addr(AF_INET, sa, NULL);
return addr->s_addr == htonl(INADDR_ANY);
}
unsigned dns_hints_insert_resconf(struct dns_hints *H, const char *zone, const struct dns_resolv_conf *resconf, int *error_) {
unsigned i, n, p;
int error;
for (i = 0, n = 0, p = 1; i < lengthof(resconf->nameserver) && resconf->nameserver[i].ss_family != AF_UNSPEC; i++, n++) {
union { struct sockaddr_in sin; } tmp;
struct sockaddr *ns;
/*
* dns_resconf_open initializes nameserver[0] to INADDR_ANY.
*
* Traditionally the semantics of 0.0.0.0 meant the default
* interface, which evolved to mean the loopback interface.
* See comment block preceding resolv/res_init.c:res_init in
* glibc 2.23. As of 2.23, glibc no longer translates
* 0.0.0.0 despite the code comment, but it does default to
* 127.0.0.1 when no nameservers are present.
*
* BIND9 as of 9.10.3 still translates 0.0.0.0 to 127.0.0.1.
* See lib/lwres/lwconfig.c:lwres_create_addr and the
* convert_zero flag. 127.0.0.1 is also the default when no
* nameservers are present.
*/
if (dns_hints_isinaddr_any(&resconf->nameserver[i])) {
memcpy(&tmp.sin, &resconf->nameserver[i], sizeof tmp.sin);
tmp.sin.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
ns = (struct sockaddr *)&tmp.sin;
} else {
ns = (struct sockaddr *)&resconf->nameserver[i];
}
if ((error = dns_hints_insert(H, zone, ns, p)))
goto error;
p += !resconf->options.rotate;
}
return n;
error:
*error_ = error;
return n;
} /* dns_hints_insert_resconf() */
static int dns_hints_i_cmp(unsigned a, unsigned b, struct dns_hints_i *i, struct dns_hints_soa *soa) {
int cmp;
if ((cmp = soa->addrs[a].priority - soa->addrs[b].priority))
return cmp;
return dns_k_shuffle16(a, i->state.seed) - dns_k_shuffle16(b, i->state.seed);
} /* dns_hints_i_cmp() */
static unsigned dns_hints_i_start(struct dns_hints_i *i, struct dns_hints_soa *soa) {
unsigned p0, p;
p0 = 0;
for (p = 1; p < soa->count; p++) {
if (dns_hints_i_cmp(p, p0, i, soa) < 0)
p0 = p;
}
return p0;
} /* dns_hints_i_start() */
static unsigned dns_hints_i_skip(unsigned p0, struct dns_hints_i *i, struct dns_hints_soa *soa) {
unsigned pZ, p;
for (pZ = 0; pZ < soa->count; pZ++) {
if (dns_hints_i_cmp(pZ, p0, i, soa) > 0)
goto cont;
}
return soa->count;
cont:
for (p = pZ + 1; p < soa->count; p++) {
if (dns_hints_i_cmp(p, p0, i, soa) <= 0)
continue;
if (dns_hints_i_cmp(p, pZ, i, soa) >= 0)
continue;
pZ = p;
}
return pZ;
} /* dns_hints_i_skip() */
static struct dns_hints_i *dns_hints_i_init(struct dns_hints_i *i, struct dns_hints *hints) {
static const struct dns_hints_i i_initializer;
struct dns_hints_soa *soa;
i->state = i_initializer.state;
do {
i->state.seed = dns_random();
} while (0 == i->state.seed);
if ((soa = dns_hints_fetch(hints, i->zone))) {
i->state.next = dns_hints_i_start(i, soa);
}
return i;
} /* dns_hints_i_init() */
unsigned dns_hints_grep(struct sockaddr **sa, socklen_t *sa_len, unsigned lim, struct dns_hints_i *i, struct dns_hints *H) {
struct dns_hints_soa *soa;
unsigned n;
if (!(soa = dns_hints_fetch(H, i->zone)))
return 0;
n = 0;
while (i->state.next < soa->count && n < lim) {
*sa = (struct sockaddr *)&soa->addrs[i->state.next].ss;
*sa_len = dns_sa_len(*sa);
sa++;
sa_len++;
n++;
i->state.next = dns_hints_i_skip(i->state.next, i, soa);
}
return n;
} /* dns_hints_grep() */
struct dns_packet *dns_hints_query(struct dns_hints *hints, struct dns_packet *Q, int *error_) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *A, *P;
struct dns_rr rr;
char zone[DNS_D_MAXNAME + 1];
size_t zlen;
struct dns_hints_i i;
struct sockaddr *sa;
socklen_t slen;
int error;
struct dns_rr_i I_instance = { 0 };
I_instance.section = DNS_S_QUESTION;
if (!dns_rr_grep(&rr, 1, &I_instance, Q, &error))
goto error;
if (!(zlen = dns_d_expand(zone, sizeof zone, rr.dn.p, Q, &error)))
goto error;
else if (zlen >= sizeof zone)
goto toolong;
P = dns_p_init(&P_instance.p, 512);
dns_header(P)->qr = 1;
if ((error = dns_rr_copy(P, &rr, Q)))
goto error;
if ((error = dns_p_push(P, DNS_S_AUTHORITY, ".", strlen("."), DNS_T_NS, DNS_C_IN, 0, "hints.local.")))
goto error;
do {
i.zone = zone;
dns_hints_i_init(&i, hints);
while (dns_hints_grep(&sa, &slen, 1, &i, hints)) {
int af = sa->sa_family;
int rtype = (af == AF_INET6)? DNS_T_AAAA : DNS_T_A;
if ((error = dns_p_push(P, DNS_S_ADDITIONAL, "hints.local.", strlen("hints.local."), rtype, DNS_C_IN, 0, dns_sa_addr(af, sa, NULL))))
goto error;
}
} while ((zlen = dns_d_cleave(zone, sizeof zone, zone, zlen)));
if (!(A = dns_p_copy(dns_p_make(P->end, &error), P)))
goto error;
return A;
toolong:
error = DNS_EILLEGAL;
error:
*error_ = error;
return 0;
} /* dns_hints_query() */
/** ugly hack to support specifying ports other than 53 in resolv.conf. */
static unsigned short dns_hints_port(struct dns_hints *hints, int af, void *addr) {
struct dns_hints_soa *soa;
void *addrsoa;
socklen_t addrlen;
unsigned short port;
unsigned i;
for (soa = hints->head; soa; soa = soa->next) {
for (i = 0; i < soa->count; i++) {
if (af != soa->addrs[i].ss.ss_family)
continue;
if (!(addrsoa = dns_sa_addr(af, &soa->addrs[i].ss, &addrlen)))
continue;
if (memcmp(addr, addrsoa, addrlen))
continue;
port = *dns_sa_port(af, &soa->addrs[i].ss);
return (port)? port : htons(53);
}
}
return htons(53);
} /* dns_hints_port() */
int dns_hints_dump(struct dns_hints *hints, FILE *fp) {
struct dns_hints_soa *soa;
char addr[INET6_ADDRSTRLEN];
unsigned i;
int af, error;
for (soa = hints->head; soa; soa = soa->next) {
fprintf(fp, "ZONE \"%s\"\n", soa->zone);
for (i = 0; i < soa->count; i++) {
af = soa->addrs[i].ss.ss_family;
if ((error = dns_ntop(af, dns_sa_addr(af, &soa->addrs[i].ss, NULL), addr, sizeof addr)))
return error;
fprintf(fp, "\t(%d) [%s]:%hu\n", (int)soa->addrs[i].priority, addr, ntohs(*dns_sa_port(af, &soa->addrs[i].ss)));
}
}
return 0;
} /* dns_hints_dump() */
/*
* C A C H E R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static dns_refcount_t dns_cache_acquire(struct dns_cache *cache) {
return dns_atomic_fetch_add(&cache->_.refcount);
} /* dns_cache_acquire() */
static dns_refcount_t dns_cache_release(struct dns_cache *cache) {
return dns_atomic_fetch_sub(&cache->_.refcount);
} /* dns_cache_release() */
static struct dns_packet *dns_cache_query(struct dns_packet *query, struct dns_cache *cache, int *error) {
(void)query;
(void)cache;
(void)error;
return NULL;
} /* dns_cache_query() */
static int dns_cache_submit(struct dns_packet *query, struct dns_cache *cache) {
(void)query;
(void)cache;
return 0;
} /* dns_cache_submit() */
static int dns_cache_check(struct dns_cache *cache) {
(void)cache;
return 0;
} /* dns_cache_check() */
static struct dns_packet *dns_cache_fetch(struct dns_cache *cache, int *error) {
(void)cache;
(void)error;
return NULL;
} /* dns_cache_fetch() */
static int dns_cache_pollfd(struct dns_cache *cache) {
(void)cache;
return -1;
} /* dns_cache_pollfd() */
static short dns_cache_events(struct dns_cache *cache) {
(void)cache;
return 0;
} /* dns_cache_events() */
static void dns_cache_clear(struct dns_cache *cache) {
(void)cache;
return;
} /* dns_cache_clear() */
struct dns_cache *dns_cache_init(struct dns_cache *cache) {
static const struct dns_cache c_init = {
.acquire = &dns_cache_acquire,
.release = &dns_cache_release,
.query = &dns_cache_query,
.submit = &dns_cache_submit,
.check = &dns_cache_check,
.fetch = &dns_cache_fetch,
.pollfd = &dns_cache_pollfd,
.events = &dns_cache_events,
.clear = &dns_cache_clear,
._ = { .refcount = 1, },
};
*cache = c_init;
return cache;
} /* dns_cache_init() */
void dns_cache_close(struct dns_cache *cache) {
if (cache)
cache->release(cache);
} /* dns_cache_close() */
/*
* S O C K E T R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static void dns_socketclose(int *fd, const struct dns_options *opts) {
if (opts && opts->closefd.cb)
opts->closefd.cb(fd, opts->closefd.arg);
if (*fd != -1) {
#if _WIN32
closesocket(*fd);
#else
close(*fd);
#endif
*fd = -1;
}
} /* dns_socketclose() */
#ifndef HAVE_IOCTLSOCKET
#define HAVE_IOCTLSOCKET (_WIN32 || _WIN64)
#endif
#ifndef HAVE_SOCK_CLOEXEC
#ifdef SOCK_CLOEXEC
#define HAVE_SOCK_CLOEXEC 1
#else
#define HAVE_SOCK_CLOEXEC 0
#endif
#endif
#ifndef HAVE_SOCK_NONBLOCK
#ifdef SOCK_NONBLOCK
#define HAVE_SOCK_NONBLOCK 1
#else
#define HAVE_SOCK_NONBLOCK 0
#endif
#endif
#define DNS_SO_MAXTRY 7
static int dns_socket(struct sockaddr *local, int type, int *error_) {
int fd = -1, flags, error;
#if defined FIONBIO
unsigned long opt;
#endif
flags = 0;
#if HAVE_SOCK_CLOEXEC
flags |= SOCK_CLOEXEC;
#endif
#if HAVE_SOCK_NONBLOCK
flags |= SOCK_NONBLOCK;
#endif
if (-1 == (fd = socket(local->sa_family, type|flags, 0)))
goto soerr;
#if defined F_SETFD && !HAVE_SOCK_CLOEXEC
if (-1 == fcntl(fd, F_SETFD, 1))
goto syerr;
#endif
#if defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK
if (-1 == (flags = fcntl(fd, F_GETFL)))
goto syerr;
if (-1 == fcntl(fd, F_SETFL, flags | O_NONBLOCK))
goto syerr;
#elif defined FIONBIO && HAVE_IOCTLSOCKET
opt = 1;
if (0 != ioctlsocket(fd, FIONBIO, &opt))
goto soerr;
#endif
#if defined SO_NOSIGPIPE
if (type != SOCK_DGRAM) {
const int v = 1;
if (0 != setsockopt(fd, SOL_SOCKET, SO_NOSIGPIPE, &v, sizeof (int)))
goto soerr;
}
#endif
if (local->sa_family != AF_INET && local->sa_family != AF_INET6)
return fd;
if (type != SOCK_DGRAM)
return fd;
#define LEAVE_SELECTION_OF_PORT_TO_KERNEL
#if !defined(LEAVE_SELECTION_OF_PORT_TO_KERNEL)
/*
* FreeBSD, Linux, OpenBSD, OS X, and Solaris use random ports by
* default. Though the ephemeral range is quite small on OS X
* (49152-65535 on 10.10) and Linux (32768-60999 on 4.4.0, Ubuntu
* Xenial). See also RFC 6056.
*
* TODO: Optionally rely on the kernel to select a random port.
*/
if (*dns_sa_port(local->sa_family, local) == 0) {
struct sockaddr_storage tmp;
unsigned i, port;
memcpy(&tmp, local, dns_sa_len(local));
for (i = 0; i < DNS_SO_MAXTRY; i++) {
port = 1025 + (dns_random() % 64510);
*dns_sa_port(tmp.ss_family, &tmp) = htons(port);
if (0 == bind(fd, (struct sockaddr *)&tmp, dns_sa_len(&tmp)))
return fd;
}
/* NB: continue to next bind statement */
}
#endif
if (0 == bind(fd, local, dns_sa_len(local)))
return fd;
/* FALL THROUGH */
soerr:
error = dns_soerr();
goto error;
#if (defined F_SETFD && !HAVE_SOCK_CLOEXEC) || (defined O_NONBLOCK && !HAVE_SOCK_NONBLOCK)
syerr:
error = dns_syerr();
goto error;
#endif
error:
*error_ = error;
dns_socketclose(&fd, NULL);
return -1;
} /* dns_socket() */
enum {
DNS_SO_UDP_INIT = 1,
DNS_SO_UDP_CONN,
DNS_SO_UDP_SEND,
DNS_SO_UDP_RECV,
DNS_SO_UDP_DONE,
DNS_SO_TCP_INIT,
DNS_SO_TCP_CONN,
DNS_SO_TCP_SEND,
DNS_SO_TCP_RECV,
DNS_SO_TCP_DONE,
DNS_SO_SOCKS_INIT,
DNS_SO_SOCKS_CONN,
DNS_SO_SOCKS_HELLO_SEND,
DNS_SO_SOCKS_HELLO_RECV,
DNS_SO_SOCKS_AUTH_SEND,
DNS_SO_SOCKS_AUTH_RECV,
DNS_SO_SOCKS_REQUEST_PREPARE,
DNS_SO_SOCKS_REQUEST_SEND,
DNS_SO_SOCKS_REQUEST_RECV,
DNS_SO_SOCKS_REQUEST_RECV_V6,
DNS_SO_SOCKS_HANDSHAKE_DONE,
};
struct dns_socket {
struct dns_options opts;
int udp;
int tcp;
int *old;
unsigned onum, olim;
int type;
struct sockaddr_storage local, remote;
struct dns_k_permutor qids;
struct dns_stat stat;
struct dns_trace *trace;
/*
* NOTE: dns_so_reset() zeroes everything from here down.
*/
int state;
unsigned short qid;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
enum dns_class qclass;
struct dns_packet *query;
size_t qout;
/* During a SOCKS handshake the query is temporarily stored
* here. */
struct dns_packet *query_backup;
struct dns_clock elapsed;
struct dns_packet *answer;
size_t alen, apos;
}; /* struct dns_socket */
/*
* NOTE: Actual closure delayed so that kqueue(2) and epoll(2) callers have
* a chance to recognize a state change after installing a persistent event
* and where sequential descriptors with the same integer value returned
* from _pollfd() would be ambiguous. See dns_so_closefds().
*/
static int dns_so_closefd(struct dns_socket *so, int *fd) {
int error;
if (*fd == -1)
return 0;
if (so->opts.closefd.cb) {
if ((error = so->opts.closefd.cb(fd, so->opts.closefd.arg))) {
return error;
} else if (*fd == -1)
return 0;
}
if (!(so->onum < so->olim)) {
unsigned olim = DNS_PP_MAX(4, so->olim * 2);
void *old;
if (!(old = realloc(so->old, sizeof so->old[0] * olim)))
return dns_syerr();
so->old = old;
so->olim = olim;
}
so->old[so->onum++] = *fd;
*fd = -1;
return 0;
} /* dns_so_closefd() */
#define DNS_SO_CLOSE_UDP 0x01
#define DNS_SO_CLOSE_TCP 0x02
#define DNS_SO_CLOSE_OLD 0x04
#define DNS_SO_CLOSE_ALL (DNS_SO_CLOSE_UDP|DNS_SO_CLOSE_TCP|DNS_SO_CLOSE_OLD)
static void dns_so_closefds(struct dns_socket *so, int which) {
if (DNS_SO_CLOSE_UDP & which)
dns_socketclose(&so->udp, &so->opts);
if (DNS_SO_CLOSE_TCP & which)
dns_socketclose(&so->tcp, &so->opts);
if (DNS_SO_CLOSE_OLD & which) {
unsigned i;
for (i = 0; i < so->onum; i++)
dns_socketclose(&so->old[i], &so->opts);
so->onum = 0;
free(so->old);
so->old = 0;
so->olim = 0;
}
} /* dns_so_closefds() */
static void dns_so_destroy(struct dns_socket *);
static struct dns_socket *dns_so_init(struct dns_socket *so, const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
static const struct dns_socket so_initializer = { .opts = DNS_OPTS_INITIALIZER, .udp = -1, .tcp = -1, };
*so = so_initializer;
so->type = type;
if (opts)
so->opts = *opts;
if (local)
memcpy(&so->local, local, dns_sa_len(local));
if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, error)))
goto error;
dns_k_permutor_init(&so->qids, 1, 65535);
return so;
error:
dns_so_destroy(so);
return 0;
} /* dns_so_init() */
struct dns_socket *dns_so_open(const struct sockaddr *local, int type, const struct dns_options *opts, int *error) {
struct dns_socket *so;
if (!(so = malloc(sizeof *so)))
goto syerr;
if (!dns_so_init(so, local, type, opts, error))
goto error;
return so;
syerr:
*error = dns_syerr();
error:
dns_so_close(so);
return 0;
} /* dns_so_open() */
static void dns_so_destroy(struct dns_socket *so) {
dns_so_reset(so);
dns_so_closefds(so, DNS_SO_CLOSE_ALL);
dns_trace_close(so->trace);
} /* dns_so_destroy() */
void dns_so_close(struct dns_socket *so) {
if (!so)
return;
dns_so_destroy(so);
free(so);
} /* dns_so_close() */
void dns_so_reset(struct dns_socket *so) {
dns_p_setptr(&so->answer, NULL);
memset(&so->state, '\0', sizeof *so - offsetof(struct dns_socket, state));
} /* dns_so_reset() */
unsigned short dns_so_mkqid(struct dns_socket *so) {
return dns_k_permutor_step(&so->qids);
} /* dns_so_mkqid() */
#define DNS_SO_MINBUF 768
static int dns_so_newanswer(struct dns_socket *so, size_t len) {
size_t size = offsetof(struct dns_packet, data) + DNS_PP_MAX(len, DNS_SO_MINBUF);
void *p;
if (!(p = realloc(so->answer, size)))
return dns_syerr();
so->answer = dns_p_init(p, size);
return 0;
} /* dns_so_newanswer() */
int dns_so_submit(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host) {
struct dns_rr rr;
int error = DNS_EUNKNOWN;
dns_so_reset(so);
if ((error = dns_rr_parse(&rr, 12, Q)))
goto error;
if (!(so->qlen = dns_d_expand(so->qname, sizeof so->qname, rr.dn.p, Q, &error)))
goto error;
/*
* NOTE: Don't bail if expansion is too long; caller may be
* intentionally sending long names. However, we won't be able to
* verify it on return.
*/
so->qtype = rr.type;
so->qclass = rr.class;
if ((error = dns_so_newanswer(so, (Q->memo.opt.maxudp)? Q->memo.opt.maxudp : DNS_SO_MINBUF)))
goto syerr;
memcpy(&so->remote, host, dns_sa_len(host));
so->query = Q;
so->qout = 0;
dns_begin(&so->elapsed);
if (dns_header(so->query)->qid == 0)
dns_header(so->query)->qid = dns_so_mkqid(so);
so->qid = dns_header(so->query)->qid;
so->state = (so->opts.socks_host && so->opts.socks_host->ss_family) ? DNS_SO_SOCKS_INIT :
(so->type == SOCK_STREAM)? DNS_SO_TCP_INIT : DNS_SO_UDP_INIT;
so->stat.queries++;
dns_trace_so_submit(so->trace, Q, host, 0);
return 0;
syerr:
error = dns_syerr();
error:
dns_so_reset(so);
dns_trace_so_submit(so->trace, Q, host, error);
return error;
} /* dns_so_submit() */
static int dns_so_verify(struct dns_socket *so, struct dns_packet *P) {
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
struct dns_rr rr;
int error = -1;
if (P->end < 12)
goto reject;
if (so->qid != dns_header(P)->qid)
goto reject;
if (!dns_p_count(P, DNS_S_QD))
goto reject;
if (0 != dns_rr_parse(&rr, 12, P))
goto reject;
if (rr.type != so->qtype || rr.class != so->qclass)
goto reject;
if (!(qlen = dns_d_expand(qname, sizeof qname, rr.dn.p, P, &error)))
goto error;
else if (qlen >= sizeof qname || qlen != so->qlen)
goto reject;
if (0 != strcasecmp(so->qname, qname))
goto reject;
dns_trace_so_verify(so->trace, P, 0);
return 0;
reject:
error = DNS_EVERIFY;
error:
DNS_SHOW(P, "rejecting packet (%s)", dns_strerror(error));
dns_trace_so_verify(so->trace, P, error);
return error;
} /* dns_so_verify() */
static _Bool dns_so_tcp_keep(struct dns_socket *so) {
struct sockaddr_storage remote;
socklen_t l = sizeof remote;
if (so->tcp == -1)
return 0;
if (0 != getpeername(so->tcp, (struct sockaddr *)&remote, &l))
return 0;
return 0 == dns_sa_cmp(&remote, &so->remote);
} /* dns_so_tcp_keep() */
/* Convenience functions for sending non-DNS data. */
/* Set up everything for sending LENGTH octets. Returns the buffer
for the data. */
static unsigned char *dns_so_tcp_send_buffer(struct dns_socket *so, size_t length) {
/* Skip the length octets, we are not doing DNS. */
so->qout = 2;
so->query->end = length;
return so->query->data;
}
/* Set up everything for receiving LENGTH octets. */
static void dns_so_tcp_recv_expect(struct dns_socket *so, size_t length) {
/* Skip the length octets, we are not doing DNS. */
so->apos = 2;
so->alen = length;
}
/* Returns the buffer containing the received data. */
static unsigned char *dns_so_tcp_recv_buffer(struct dns_socket *so) {
return so->answer->data;
}
#if GPGRT_GCC_VERSION >= 80000
# pragma GCC diagnostic push
# pragma GCC diagnostic ignored "-Warray-bounds"
#elif defined __clang__
# pragma clang diagnostic push
# pragma clang diagnostic ignored "-Warray-bounds"
#endif
static int dns_so_tcp_send(struct dns_socket *so) {
unsigned char *qsrc;
size_t qend;
int error;
size_t n;
so->query->data[-2] = 0xff & (so->query->end >> 8);
so->query->data[-1] = 0xff & (so->query->end >> 0);
qend = so->query->end + 2;
while (so->qout < qend) {
qsrc = &so->query->data[-2] + so->qout;
n = dns_send_nopipe(so->tcp, (void *)qsrc, qend - so->qout, 0, &error);
dns_trace_sys_send(so->trace, so->tcp, SOCK_STREAM, qsrc, n, error);
if (error)
return error;
so->qout += n;
so->stat.tcp.sent.bytes += n;
}
so->stat.tcp.sent.count++;
return 0;
} /* dns_so_tcp_send() */
static int dns_so_tcp_recv(struct dns_socket *so) {
unsigned char *asrc;
size_t aend, alen, n;
int error;
aend = so->alen + 2;
while (so->apos < aend) {
asrc = &so->answer->data[-2];
n = dns_recv(so->tcp, (void *)&asrc[so->apos], aend - so->apos, 0, &error);
dns_trace_sys_recv(so->trace, so->tcp, SOCK_STREAM, &asrc[so->apos], n, error);
if (error)
return error;
so->apos += n;
so->stat.tcp.rcvd.bytes += n;
if (so->alen == 0 && so->apos >= 2) {
alen = ((0xff & so->answer->data[-2]) << 8)
| ((0xff & so->answer->data[-1]) << 0);
if ((error = dns_so_newanswer(so, alen)))
return error;
so->alen = alen;
aend = alen + 2;
}
}
so->answer->end = so->alen;
so->stat.tcp.rcvd.count++;
return 0;
} /* dns_so_tcp_recv() */
#if GPGRT_GCC_VERSION >= 80000
# pragma GCC diagnostic pop
#elif __clang__
# pragma clang diagnostic pop
#endif
int dns_so_check(struct dns_socket *so) {
int error;
size_t n;
unsigned char *buffer;
retry:
switch (so->state) {
case DNS_SO_UDP_INIT:
if (so->remote.ss_family != so->local.ss_family) {
/* Family mismatch. Reinitialize. */
if ((error = dns_so_closefd(so, &so->udp)))
goto error;
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
/* If the user supplied an interface
statement, that is gone now. Sorry. */
memset(&so->local, 0, sizeof so->local);
so->local.ss_family = so->remote.ss_family;
if (-1 == (so->udp = dns_socket((struct sockaddr *)&so->local, SOCK_DGRAM, &error)))
goto error;
}
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_CONN:
udp_connect_retry:
error = dns_connect(so->udp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
dns_trace_sys_connect(so->trace, so->udp, SOCK_DGRAM, (struct sockaddr *)&so->remote, error);
/* Linux returns EINVAL when address was bound to
localhost and it's external IP address now. */
if (error == EINVAL) {
struct sockaddr unspec_addr;
memset (&unspec_addr, 0, sizeof unspec_addr);
unspec_addr.sa_family = AF_UNSPEC;
connect(so->udp, &unspec_addr, sizeof unspec_addr);
goto udp_connect_retry;
} else if (error == ECONNREFUSED)
/* Error for previous socket operation may
be reserved(?) asynchronously. */
goto udp_connect_retry;
if (error)
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_SEND:
n = dns_send(so->udp, (void *)so->query->data, so->query->end, 0, &error);
dns_trace_sys_send(so->trace, so->udp, SOCK_DGRAM, so->query->data, n, error);
if (error)
goto error;
so->stat.udp.sent.bytes += n;
so->stat.udp.sent.count++;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_RECV:
n = dns_recv(so->udp, (void *)so->answer->data, so->answer->size, 0, &error);
dns_trace_sys_recv(so->trace, so->udp, SOCK_DGRAM, so->answer->data, n, error);
if (error)
goto error;
so->answer->end = n;
so->stat.udp.rcvd.bytes += n;
so->stat.udp.rcvd.count++;
if ((error = dns_so_verify(so, so->answer)))
goto trash;
so->state++; /* FALL THROUGH */
case DNS_SO_UDP_DONE:
if (!dns_header(so->answer)->tc || so->type == SOCK_DGRAM)
return 0;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_INIT:
if (so->remote.ss_family != so->local.ss_family) {
/* Family mismatch. Reinitialize. */
if ((error = dns_so_closefd(so, &so->udp)))
goto error;
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
/* If the user supplied an interface
statement, that is gone now. Sorry. */
memset(&so->local, 0, sizeof so->local);
so->local.ss_family = so->remote.ss_family;
}
if (dns_so_tcp_keep(so)) {
so->state = DNS_SO_TCP_SEND;
goto retry;
}
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_CONN:
error = dns_connect(so->tcp, (struct sockaddr *)&so->remote, dns_sa_len(&so->remote));
dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)&so->remote, error);
if (error && error != DNS_EISCONN)
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_TCP_DONE:
/* close unless DNS_RESCONF_TCP_ONLY (see dns_res_tcp2type) */
if (so->type != SOCK_STREAM) {
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
}
if ((error = dns_so_verify(so, so->answer)))
goto error;
return 0;
case DNS_SO_SOCKS_INIT:
if ((error = dns_so_closefd(so, &so->tcp)))
goto error;
if (-1 == (so->tcp = dns_socket((struct sockaddr *)&so->local, SOCK_STREAM, &error)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_CONN: {
unsigned char method;
error = dns_connect(so->tcp, (struct sockaddr *)so->opts.socks_host, dns_sa_len(so->opts.socks_host));
dns_trace_sys_connect(so->trace, so->tcp, SOCK_STREAM, (struct sockaddr *)so->opts.socks_host, error);
if (error && error != DNS_EISCONN)
goto error;
/* We need to do a handshake with the SOCKS server,
* but the query is already in the buffer. Move it
* out of the way. */
dns_p_movptr(&so->query_backup, &so->query);
/* Create a new buffer for the handshake. */
dns_p_grow(&so->query);
/* Negotiate method. */
buffer = dns_so_tcp_send_buffer(so, 3);
buffer[0] = 5; /* RFC-1928 VER field. */
buffer[1] = 1; /* NMETHODS */
if (so->opts.socks_user)
method = 2; /* Method: username/password authentication. */
else
method = 0; /* Method: No authentication required. */
buffer[2] = method;
so->state++;
} /* FALL THROUGH */
case DNS_SO_SOCKS_HELLO_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
dns_so_tcp_recv_expect(so, 2);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_HELLO_RECV: {
unsigned char method;
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
method = so->opts.socks_user ? 2 : 0;
if (buffer[0] != 5 || buffer[1] != method) {
/* Socks server returned wrong version or does
not support our requested method. */
error = ENOTSUP; /* Fixme: Is there a better errno? */
goto error;
}
if (method == 0) {
/* No authentication, go ahead and send the
request. */
so->state = DNS_SO_SOCKS_REQUEST_PREPARE;
goto retry;
}
/* Prepare username/password sub-negotiation. */
if (! so->opts.socks_password) {
error = EINVAL; /* No password given. */
goto error;
} else {
size_t buflen, ulen, plen;
ulen = strlen(so->opts.socks_user);
plen = strlen(so->opts.socks_password);
if (!ulen || ulen > 255 || !plen || plen > 255) {
error = EINVAL; /* Credentials too long or too short. */
goto error;
}
buffer = dns_so_tcp_send_buffer(so, 3 + ulen + plen);
buffer[0] = 1; /* VER of the sub-negotiation. */
buffer[1] = (unsigned char) ulen;
buflen = 2;
memcpy (buffer+buflen, so->opts.socks_user, ulen);
buflen += ulen;
buffer[buflen++] = (unsigned char) plen;
memcpy (buffer+buflen, so->opts.socks_password, plen);
}
so->state++;
} /* FALL THROUGH */
case DNS_SO_SOCKS_AUTH_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
/* Skip the two length octets, and receive two octets. */
dns_so_tcp_recv_expect(so, 2);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_AUTH_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
if (buffer[0] != 1) {
/* SOCKS server returned wrong version. */
error = EPROTO;
goto error;
}
if (buffer[1]) {
/* SOCKS server denied access. */
error = EACCES;
goto error;
}
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_PREPARE:
/* Send request details (rfc-1928, 4). */
buffer = dns_so_tcp_send_buffer(so, so->remote.ss_family == AF_INET6 ? 22 : 10);
buffer[0] = 5; /* VER */
buffer[1] = 1; /* CMD = CONNECT */
buffer[2] = 0; /* RSV */
if (so->remote.ss_family == AF_INET6) {
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)&so->remote;
buffer[3] = 4; /* ATYP = IPv6 */
memcpy (buffer+ 4, &addr_in6->sin6_addr.s6_addr, 16); /* DST.ADDR */
memcpy (buffer+20, &addr_in6->sin6_port, 2); /* DST.PORT */
} else {
struct sockaddr_in *addr_in = (struct sockaddr_in *)&so->remote;
buffer[3] = 1; /* ATYP = IPv4 */
memcpy (buffer+4, &addr_in->sin_addr.s_addr, 4); /* DST.ADDR */
memcpy (buffer+8, &addr_in->sin_port, 2); /* DST.PORT */
}
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_SEND:
if ((error = dns_so_tcp_send(so)))
goto error;
/* Expect ten octets. This is the length of the
* response assuming a IPv4 address is used. */
dns_so_tcp_recv_expect(so, 10);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_RECV:
if ((error = dns_so_tcp_recv(so)))
goto error;
buffer = dns_so_tcp_recv_buffer(so);
if (buffer[0] != 5 || buffer[2] != 0) {
/* Socks server returned wrong version or the
reserved field is not zero. */
error = EPROTO;
goto error;
}
if (buffer[1]) {
switch (buffer[1]) {
case 0x01: /* general SOCKS server failure. */
error = ENETDOWN;
break;
case 0x02: /* connection not allowed by ruleset. */
error = EACCES;
break;
case 0x03: /* Network unreachable */
error = ENETUNREACH;
break;
case 0x04: /* Host unreachable */
error = EHOSTUNREACH;
break;
case 0x05: /* Connection refused */
error = ECONNREFUSED;
break;
case 0x06: /* TTL expired */
error = ETIMEDOUT;
break;
case 0x08: /* Address type not supported */
error = EPROTONOSUPPORT;
break;
case 0x07: /* Command not supported */
default:
error = ENOTSUP; /* Fixme: Is there a better error? */
break;
}
goto error;
}
if (buffer[3] == 1) {
/* This was indeed an IPv4 address. */
so->state = DNS_SO_SOCKS_HANDSHAKE_DONE;
goto retry;
}
if (buffer[3] != 4) {
error = ENOTSUP;
goto error;
}
/* Expect receive twelve octets. This accounts for
* the remaining bytes assuming an IPv6 address is
* used. */
dns_so_tcp_recv_expect(so, 12);
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_REQUEST_RECV_V6:
if ((error = dns_so_tcp_recv(so)))
goto error;
so->state++; /* FALL THROUGH */
case DNS_SO_SOCKS_HANDSHAKE_DONE:
/* We have not way to store the actual address used by
* the server. Then again, we don't really care. */
/* Restore the query. */
dns_p_movptr(&so->query, &so->query_backup);
/* Reset cursors. */
so->qout = 0;
so->apos = 0;
so->alen = 0;
/* SOCKS handshake is done. Proceed with the
* lookup. */
so->state = DNS_SO_TCP_SEND;
goto retry;
default:
error = DNS_EUNKNOWN;
goto error;
} /* switch() */
trash:
DNS_CARP("discarding packet");
goto retry;
error:
switch (error) {
case DNS_EINTR:
goto retry;
case DNS_EINPROGRESS:
/* FALL THROUGH */
case DNS_EALREADY:
/* FALL THROUGH */
#if DNS_EWOULDBLOCK != DNS_EAGAIN
case DNS_EWOULDBLOCK:
/* FALL THROUGH */
#endif
error = DNS_EAGAIN;
break;
} /* switch() */
return error;
} /* dns_so_check() */
struct dns_packet *dns_so_fetch(struct dns_socket *so, int *error) {
struct dns_packet *answer;
switch (so->state) {
case DNS_SO_UDP_DONE:
case DNS_SO_TCP_DONE:
answer = so->answer;
so->answer = 0;
dns_trace_so_fetch(so->trace, answer, 0);
return answer;
default:
*error = DNS_EUNKNOWN;
dns_trace_so_fetch(so->trace, NULL, *error);
return 0;
}
} /* dns_so_fetch() */
struct dns_packet *dns_so_query(struct dns_socket *so, struct dns_packet *Q, struct sockaddr *host, int *error_) {
struct dns_packet *A;
int error;
if (!so->state) {
if ((error = dns_so_submit(so, Q, host)))
goto error;
}
if ((error = dns_so_check(so)))
goto error;
if (!(A = dns_so_fetch(so, &error)))
goto error;
dns_so_reset(so);
return A;
error:
*error_ = error;
return 0;
} /* dns_so_query() */
time_t dns_so_elapsed(struct dns_socket *so) {
return dns_elapsed(&so->elapsed);
} /* dns_so_elapsed() */
void dns_so_clear(struct dns_socket *so) {
dns_so_closefds(so, DNS_SO_CLOSE_OLD);
} /* dns_so_clear() */
static int dns_so_events2(struct dns_socket *so, enum dns_events type) {
int events = 0;
switch (so->state) {
case DNS_SO_UDP_CONN:
case DNS_SO_UDP_SEND:
events |= DNS_POLLOUT;
break;
case DNS_SO_UDP_RECV:
events |= DNS_POLLIN;
break;
case DNS_SO_TCP_CONN:
case DNS_SO_TCP_SEND:
events |= DNS_POLLOUT;
break;
case DNS_SO_TCP_RECV:
events |= DNS_POLLIN;
break;
} /* switch() */
switch (type) {
case DNS_LIBEVENT:
return DNS_POLL2EV(events);
default:
return events;
} /* switch() */
} /* dns_so_events2() */
int dns_so_events(struct dns_socket *so) {
return dns_so_events2(so, so->opts.events);
} /* dns_so_events() */
int dns_so_pollfd(struct dns_socket *so) {
switch (so->state) {
case DNS_SO_UDP_CONN:
case DNS_SO_UDP_SEND:
case DNS_SO_UDP_RECV:
return so->udp;
case DNS_SO_TCP_CONN:
case DNS_SO_TCP_SEND:
case DNS_SO_TCP_RECV:
return so->tcp;
} /* switch() */
return -1;
} /* dns_so_pollfd() */
int dns_so_poll(struct dns_socket *so, int timeout) {
return dns_poll(dns_so_pollfd(so), dns_so_events2(so, DNS_SYSPOLL), timeout);
} /* dns_so_poll() */
const struct dns_stat *dns_so_stat(struct dns_socket *so) {
return &so->stat;
} /* dns_so_stat() */
struct dns_trace *dns_so_trace(struct dns_socket *so) {
return so->trace;
} /* dns_so_trace() */
void dns_so_settrace(struct dns_socket *so, struct dns_trace *trace) {
struct dns_trace *otrace = so->trace;
so->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
} /* dns_so_settrace() */
/*
* R E S O L V E R R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
enum dns_res_state {
DNS_R_INIT,
DNS_R_GLUE,
DNS_R_SWITCH, /* (B)IND, (F)ILE, (C)ACHE */
DNS_R_FILE, /* Lookup in local hosts database */
DNS_R_CACHE, /* Lookup in application cache */
DNS_R_SUBMIT,
DNS_R_CHECK,
DNS_R_FETCH,
DNS_R_BIND, /* Lookup in the network */
DNS_R_SEARCH,
DNS_R_HINTS,
DNS_R_ITERATE,
DNS_R_FOREACH_NS,
DNS_R_RESOLV0_NS, /* Prologue: Setup next frame and recurse */
DNS_R_RESOLV1_NS, /* Epilog: Inspect answer */
DNS_R_FOREACH_A,
DNS_R_QUERY_A,
DNS_R_FOREACH_AAAA,
DNS_R_QUERY_AAAA,
DNS_R_CNAME0_A,
DNS_R_CNAME1_A,
DNS_R_FINISH,
DNS_R_SMART0_A,
DNS_R_SMART1_A,
DNS_R_DONE,
DNS_R_SERVFAIL,
}; /* enum dns_res_state */
#define DNS_R_MAXDEPTH 8
#define DNS_R_ENDFRAME (DNS_R_MAXDEPTH - 1)
struct dns_resolver {
struct dns_socket so;
struct dns_resolv_conf *resconf;
struct dns_hosts *hosts;
struct dns_hints *hints;
struct dns_cache *cache;
struct dns_trace *trace;
dns_atomic_t refcount;
/* Reset zeroes everything below here. */
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
enum dns_class qclass;
struct dns_clock elapsed;
dns_resconf_i_t search;
struct dns_rr_i smart;
struct dns_packet *nodata; /* answer if nothing better */
unsigned sp;
struct dns_res_frame {
enum dns_res_state state;
int error;
int which; /* (B)IND, (F)ILE; index into resconf->lookup */
int qflags;
unsigned attempts;
struct dns_packet *query, *answer, *hints;
struct dns_rr_i hints_i, hints_j;
struct dns_rr hints_ns, ans_cname;
} stack[DNS_R_MAXDEPTH];
}; /* struct dns_resolver */
static int dns_res_tcp2type(int tcp) {
switch (tcp) {
case DNS_RESCONF_TCP_ONLY:
case DNS_RESCONF_TCP_SOCKS:
return SOCK_STREAM;
case DNS_RESCONF_TCP_DISABLE:
return SOCK_DGRAM;
default:
return 0;
}
} /* dns_res_tcp2type() */
struct dns_resolver *dns_res_open(struct dns_resolv_conf *resconf, struct dns_hosts *hosts, struct dns_hints *hints, struct dns_cache *cache, const struct dns_options *opts, int *_error) {
static const struct dns_resolver R_initializer
= { .refcount = 1, };
struct dns_resolver *R = 0;
int type, error;
/*
* Grab ref count early because the caller may have passed us a mortal
* reference, and we want to do the right thing if we return early
* from an error.
*/
if (resconf)
dns_resconf_acquire(resconf);
if (hosts)
dns_hosts_acquire(hosts);
if (hints)
dns_hints_acquire(hints);
if (cache)
dns_cache_acquire(cache);
/*
* Don't try to load it ourselves because a NULL object might be an
* error from, say, dns_resconf_root(), and loading
* dns_resconf_local() by default would create undesirable surprises.
*/
if (!resconf || !hosts || !hints) {
if (!*_error)
*_error = EINVAL;
goto _error;
}
if (!(R = malloc(sizeof *R)))
goto syerr;
*R = R_initializer;
type = dns_res_tcp2type(resconf->options.tcp);
if (!dns_so_init(&R->so, (struct sockaddr *)&resconf->iface, type, opts, &error))
goto error;
R->resconf = resconf;
R->hosts = hosts;
R->hints = hints;
R->cache = cache;
return R;
syerr:
error = dns_syerr();
error:
*_error = error;
_error:
dns_res_close(R);
dns_resconf_close(resconf);
dns_hosts_close(hosts);
dns_hints_close(hints);
dns_cache_close(cache);
return 0;
} /* dns_res_open() */
struct dns_resolver *dns_res_stub(const struct dns_options *opts, int *error) {
struct dns_resolv_conf *resconf = 0;
struct dns_hosts *hosts = 0;
struct dns_hints *hints = 0;
struct dns_resolver *res = 0;
if (!(resconf = dns_resconf_local(error)))
goto epilog;
if (!(hosts = dns_hosts_local(error)))
goto epilog;
if (!(hints = dns_hints_local(resconf, error)))
goto epilog;
/* RESCONF is closed by dns_hints_local, so, get it again. */
if (!(resconf = dns_resconf_local(error)))
goto epilog;
if (!(res = dns_res_open(resconf, hosts, hints, NULL, opts, error)))
goto epilog;
else
return res;
epilog:
dns_resconf_close(resconf);
dns_hosts_close(hosts);
dns_hints_close(hints);
return res;
} /* dns_res_stub() */
static void dns_res_frame_destroy(struct dns_resolver *R, struct dns_res_frame *frame) {
(void)R;
dns_p_setptr(&frame->query, NULL);
dns_p_setptr(&frame->answer, NULL);
dns_p_setptr(&frame->hints, NULL);
} /* dns_res_frame_destroy() */
static void dns_res_frame_init(struct dns_resolver *R, struct dns_res_frame *frame) {
memset(frame, '\0', sizeof *frame);
/*
* NB: Can be invoked from dns_res_open, before R->resconf has been
* initialized.
*/
if (R->resconf) {
if (!R->resconf->options.recurse)
frame->qflags |= DNS_Q_RD;
if (R->resconf->options.edns0)
frame->qflags |= DNS_Q_EDNS0;
}
} /* dns_res_frame_init() */
static void dns_res_frame_reset(struct dns_resolver *R, struct dns_res_frame *frame) {
dns_res_frame_destroy(R, frame);
dns_res_frame_init(R, frame);
} /* dns_res_frame_reset() */
static dns_error_t dns_res_frame_prepare(struct dns_resolver *R, struct dns_res_frame *F, const char *qname, enum dns_type qtype, enum dns_class qclass) {
struct dns_packet *P = NULL;
if (!(F < endof(R->stack)))
return DNS_EUNKNOWN;
dns_p_movptr(&P, &F->query);
dns_res_frame_reset(R, F);
dns_p_movptr(&F->query, &P);
return dns_q_make(&F->query, qname, qtype, qclass, F->qflags);
} /* dns_res_frame_prepare() */
void dns_res_reset(struct dns_resolver *R) {
unsigned i;
dns_so_reset(&R->so);
dns_p_setptr(&R->nodata, NULL);
for (i = 0; i < lengthof(R->stack); i++)
dns_res_frame_destroy(R, &R->stack[i]);
memset(&R->qname, '\0', sizeof *R - offsetof(struct dns_resolver, qname));
for (i = 0; i < lengthof(R->stack); i++)
dns_res_frame_init(R, &R->stack[i]);
} /* dns_res_reset() */
void dns_res_close(struct dns_resolver *R) {
if (!R || 1 < dns_res_release(R))
return;
dns_res_reset(R);
dns_so_destroy(&R->so);
dns_hints_close(R->hints);
dns_hosts_close(R->hosts);
dns_resconf_close(R->resconf);
dns_cache_close(R->cache);
dns_trace_close(R->trace);
free(R);
} /* dns_res_close() */
dns_refcount_t dns_res_acquire(struct dns_resolver *R) {
return dns_atomic_fetch_add(&R->refcount);
} /* dns_res_acquire() */
dns_refcount_t dns_res_release(struct dns_resolver *R) {
return dns_atomic_fetch_sub(&R->refcount);
} /* dns_res_release() */
struct dns_resolver *dns_res_mortal(struct dns_resolver *res) {
if (res)
dns_res_release(res);
return res;
} /* dns_res_mortal() */
static struct dns_packet *dns_res_merge(struct dns_packet *P0, struct dns_packet *P1, int *error_) {
size_t bufsiz = P0->end + P1->end;
struct dns_packet *P[3] = { P0, P1, 0 };
struct dns_rr rr[3];
int error, copy, i;
enum dns_section section;
retry:
if (!(P[2] = dns_p_make(bufsiz, &error)))
goto error;
dns_rr_foreach(&rr[0], P[0], .section = DNS_S_QD) {
if ((error = dns_rr_copy(P[2], &rr[0], P[0])))
goto error;
}
for (section = DNS_S_AN; (DNS_S_ALL & section); section <<= 1) {
for (i = 0; i < 2; i++) {
dns_rr_foreach(&rr[i], P[i], .section = section) {
copy = 1;
dns_rr_foreach(&rr[2], P[2], .type = rr[i].type, .section = (DNS_S_ALL & ~DNS_S_QD)) {
if (0 == dns_rr_cmp(&rr[i], P[i], &rr[2], P[2])) {
copy = 0;
break;
}
}
if (copy && (error = dns_rr_copy(P[2], &rr[i], P[i]))) {
if (error == DNS_ENOBUFS && bufsiz < 65535) {
dns_p_setptr(&P[2], NULL);
bufsiz = DNS_PP_MAX(65535, bufsiz * 2);
goto retry;
}
goto error;
}
} /* foreach(rr) */
} /* foreach(packet) */
} /* foreach(section) */
return P[2];
error:
*error_ = error;
dns_p_free(P[2]);
return 0;
} /* dns_res_merge() */
static struct dns_packet *dns_res_glue(struct dns_resolver *R, struct dns_packet *Q) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *P = dns_p_init(&P_instance.p, 512);
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
enum dns_type qtype;
struct dns_rr rr;
unsigned sp;
int error;
if (!(qlen = dns_d_expand(qname, sizeof qname, 12, Q, &error))
|| qlen >= sizeof qname)
return 0;
if (!(qtype = dns_rr_type(12, Q)))
return 0;
if ((error = dns_p_push(P, DNS_S_QD, qname, strlen(qname), qtype, DNS_C_IN, 0, 0)))
return 0;
for (sp = 0; sp <= R->sp; sp++) {
if (!R->stack[sp].answer)
continue;
dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = qtype, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AN;
if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
return 0;
}
}
if (dns_p_count(P, DNS_S_AN) > 0)
goto copy;
/* Otherwise, look for a CNAME */
for (sp = 0; sp <= R->sp; sp++) {
if (!R->stack[sp].answer)
continue;
dns_rr_foreach(&rr, R->stack[sp].answer, .name = qname, .type = DNS_T_CNAME, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AN;
if ((error = dns_rr_copy(P, &rr, R->stack[sp].answer)))
return 0;
}
}
if (!dns_p_count(P, DNS_S_AN))
return 0;
copy:
return dns_p_copy(dns_p_make(P->end, &error), P);
} /* dns_res_glue() */
/*
* Sort NS records by three criteria:
*
* 1) Whether glue is present.
* 2) Whether glue record is original or of recursive lookup.
* 3) Randomly shuffle records which share the above criteria.
*
* NOTE: Assumes only NS records passed, AND ASSUMES no new NS records will
* be added during an iteration.
*
* FIXME: Only groks A glue, not AAAA glue.
*/
static int dns_res_nameserv_cmp(struct dns_rr *a, struct dns_rr *b, struct dns_rr_i *i, struct dns_packet *P) {
_Bool glued[2] = { 0 };
struct dns_rr x = { 0 }, y = { 0 };
struct dns_ns ns;
int cmp, error;
if (!(error = dns_ns_parse(&ns, a, P))) {
struct dns_rr_i I_instance = { 0 };
I_instance.section = (DNS_S_ALL & ~DNS_S_QD);
I_instance.name = ns.host;
I_instance.type = DNS_T_A;
glued[0] = !!dns_rr_grep(&x, 1, &I_instance, P, &error);
}
if (!(error = dns_ns_parse(&ns, b, P))) {
struct dns_rr_i I_instance = { 0 };
I_instance.section = (DNS_S_ALL & ~DNS_S_QD);
I_instance.name = ns.host;
I_instance.type = DNS_T_A;
glued[1] = !!dns_rr_grep(&y, 1, &I_instance, P, &error);
}
if ((cmp = glued[1] - glued[0])) {
return cmp;
} else if ((cmp = (dns_rr_offset(&y) < i->args[0]) - (dns_rr_offset(&x) < i->args[0]))) {
return cmp;
} else {
return dns_rr_i_shuffle(a, b, i, P);
}
} /* dns_res_nameserv_cmp() */
#define dgoto(sp, i) \
do { R->stack[(sp)].state = (i); goto exec; } while (0)
static int dns_res_exec(struct dns_resolver *R) {
struct dns_res_frame *F;
struct dns_packet *P;
union {
char host[DNS_D_MAXNAME + 1];
char name[DNS_D_MAXNAME + 1];
struct dns_ns ns;
struct dns_cname cname;
} u;
size_t len;
struct dns_rr rr;
int error;
exec:
F = &R->stack[R->sp];
switch (F->state) {
case DNS_R_INIT:
F->state++; /* FALL THROUGH */
case DNS_R_GLUE:
if (R->sp == 0)
dgoto(R->sp, DNS_R_SWITCH);
if (!F->query)
goto noquery;
if (!(F->answer = dns_res_glue(R, F->query)))
dgoto(R->sp, DNS_R_SWITCH);
if (!(len = dns_d_expand(u.name, sizeof u.name, 12, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .name = u.name, .type = dns_rr_type(12, F->query), .section = DNS_S_AN) {
dgoto(R->sp, DNS_R_FINISH);
}
dns_rr_foreach(&rr, F->answer, .name = u.name, .type = DNS_T_CNAME, .section = DNS_S_AN) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
F->state++;
case DNS_R_SWITCH:
while (F->which < (int)sizeof R->resconf->lookup && R->resconf->lookup[F->which]) {
switch (R->resconf->lookup[F->which++]) {
case 'b': case 'B':
dgoto(R->sp, DNS_R_BIND);
case 'f': case 'F':
dgoto(R->sp, DNS_R_FILE);
case 'c': case 'C':
if (R->cache)
dgoto(R->sp, DNS_R_CACHE);
break;
default:
break;
}
}
/*
* FIXME: Examine more closely whether our logic is correct
* and DNS_R_SERVFAIL is the correct default response.
*
* Case 1: We got here because we never got an answer on the
* wire. All queries timed-out and we reached maximum
* attempts count. See DNS_R_FOREACH_NS. In that case
* DNS_R_SERVFAIL is the correct state, unless we want to
* return DNS_ETIMEDOUT.
*
* Case 2: We were a stub resolver and got an unsatisfactory
* answer (empty ANSWER section) which caused us to jump
* back to DNS_R_SEARCH and ultimately to DNS_R_SWITCH. We
* return the answer returned from the wire, which we
* stashed in R->nodata.
*
* Case 3: We reached maximum attempts count as in case #1,
* but never got an authoritative response which caused us
* to short-circuit. See end of DNS_R_QUERY_A case. We
* should probably prepare R->nodata as in case #2.
*/
if (R->sp == 0 && R->nodata) { /* XXX: can we just return nodata regardless? */
dns_p_movptr(&F->answer, &R->nodata);
dgoto(R->sp, DNS_R_FINISH);
}
dgoto(R->sp, DNS_R_SERVFAIL);
case DNS_R_FILE:
if (R->sp > 0) {
if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
goto error;
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
} else {
R->search = 0;
while ((len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search))) {
if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
goto error;
if (!dns_p_setptr(&F->answer, dns_hosts_query(R->hosts, F->query, &error)))
goto error;
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
}
}
dgoto(R->sp, DNS_R_SWITCH);
case DNS_R_CACHE:
error = 0;
if (!F->query && (error = dns_q_make(&F->query, R->qname, R->qtype, R->qclass, F->qflags)))
goto error;
if (dns_p_setptr(&F->answer, R->cache->query(F->query, R->cache, &error))) {
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
dgoto(R->sp, DNS_R_SWITCH);
} else if (error)
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_SUBMIT:
if ((error = R->cache->submit(F->query, R->cache)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_CHECK:
if ((error = R->cache->check(R->cache)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_FETCH:
error = 0;
if (dns_p_setptr(&F->answer, R->cache->fetch(R->cache, &error))) {
if (dns_p_count(F->answer, DNS_S_AN) > 0)
dgoto(R->sp, DNS_R_FINISH);
dns_p_setptr(&F->answer, NULL);
dgoto(R->sp, DNS_R_SWITCH);
} else if (error)
goto error;
dgoto(R->sp, DNS_R_SWITCH);
case DNS_R_BIND:
if (R->sp > 0) {
if (!F->query)
goto noquery;
dgoto(R->sp, DNS_R_HINTS);
}
R->search = 0;
F->state++; /* FALL THROUGH */
case DNS_R_SEARCH:
/*
* XXX: We probably should only apply the domain search
* algorithm if R->sp == 0.
*/
if (!(len = dns_resconf_search(u.name, sizeof u.name, R->qname, R->qlen, R->resconf, &R->search)))
dgoto(R->sp, DNS_R_SWITCH);
if ((error = dns_q_make2(&F->query, u.name, len, R->qtype, R->qclass, F->qflags)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_HINTS:
if (!dns_p_setptr(&F->hints, dns_hints_query(R->hints, F->query, &error)))
goto error;
F->state++; /* FALL THROUGH */
case DNS_R_ITERATE:
dns_rr_i_init(&F->hints_i);
F->hints_i.section = DNS_S_AUTHORITY;
F->hints_i.type = DNS_T_NS;
F->hints_i.sort = &dns_res_nameserv_cmp;
F->hints_i.args[0] = F->hints->end;
F->state++; /* FALL THROUGH */
case DNS_R_FOREACH_NS:
dns_rr_i_save(&F->hints_i);
/* Load our next nameserver host. */
if (!dns_rr_grep(&F->hints_ns, 1, &F->hints_i, F->hints, &error)) {
if (++F->attempts < R->resconf->options.attempts)
dgoto(R->sp, DNS_R_ITERATE);
dgoto(R->sp, DNS_R_SWITCH);
}
dns_rr_i_init(&F->hints_j);
/* Assume there are glue records */
dgoto(R->sp, DNS_R_FOREACH_A);
case DNS_R_RESOLV0_NS:
/* Have we reached our max depth? */
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_FOREACH_NS);
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
if ((error = dns_res_frame_prepare(R, &F[1], u.ns.host, DNS_T_A, DNS_C_IN)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
case DNS_R_RESOLV1_NS:
if (!(len = dns_d_expand(u.host, sizeof u.host, 12, F[1].query, &error)))
goto error;
else if (len >= sizeof u.host)
goto toolong;
dns_rr_foreach(&rr, F[1].answer, .name = u.host, .type = DNS_T_A, .section = (DNS_S_ALL & ~DNS_S_QD)) {
rr.section = DNS_S_AR;
if ((error = dns_rr_copy(F->hints, &rr, F[1].answer)))
goto error;
dns_rr_i_rewind(&F->hints_i); /* Now there's glue. */
}
dgoto(R->sp, DNS_R_FOREACH_NS);
case DNS_R_FOREACH_A: {
struct dns_a a;
struct sockaddr_in sin;
/*
* NOTE: Iterator initialized in DNS_R_FOREACH_NS because
* this state is re-entrant, but we need to reset
* .name to a valid pointer each time.
*/
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_A;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
if (!dns_rr_i_count(&F->hints_j)) {
/* Check if we have in fact servers
with an IPv6 address. */
dns_rr_i_init(&F->hints_j);
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_AAAA;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
/* We do. Reinitialize
iterator and handle it. */
dns_rr_i_init(&F->hints_j);
dgoto(R->sp, DNS_R_FOREACH_AAAA);
}
dgoto(R->sp, DNS_R_RESOLV0_NS);
}
dgoto(R->sp, DNS_R_FOREACH_NS);
}
if ((error = dns_a_parse(&a, &rr, F->hints)))
goto error;
memset(&sin, '\0', sizeof sin); /* NB: silence valgrind */
sin.sin_family = AF_INET;
sin.sin_addr = a.addr;
if (R->sp == 0)
sin.sin_port = dns_hints_port(R->hints, AF_INET, &sin.sin_addr);
else
sin.sin_port = htons(53);
if (DNS_DEBUG) {
char addr[INET_ADDRSTRLEN + 1];
dns_a_print(addr, sizeof addr, &a);
dns_header(F->query)->qid = dns_so_mkqid(&R->so);
DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp);
}
dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin);
if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin)))
goto error;
F->state++;
} /* FALL THROUGH */
case DNS_R_QUERY_A:
if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf))
dgoto(R->sp, DNS_R_FOREACH_A);
error = dns_so_check(&R->so);
if (R->so.state != DNS_SO_SOCKS_CONN && error == ECONNREFUSED)
dgoto(R->sp, DNS_R_FOREACH_A);
else if (error)
goto error;
if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error)))
goto error;
if (DNS_DEBUG) {
DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp);
}
if (dns_p_rcode(F->answer) == DNS_RC_FORMERR ||
dns_p_rcode(F->answer) == DNS_RC_NOTIMP ||
dns_p_rcode(F->answer) == DNS_RC_BADVERS) {
/* Temporarily disable EDNS0 and try again. */
if (F->qflags & DNS_Q_EDNS0) {
F->qflags &= ~DNS_Q_EDNS0;
if ((error = dns_q_remake(&F->query, F->qflags)))
goto error;
dgoto(R->sp, DNS_R_FOREACH_A);
}
}
if ((error = dns_rr_parse(&rr, 12, F->query)))
goto error;
if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) {
dgoto(R->sp, DNS_R_FINISH); /* Found */
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
/*
* XXX: The condition here should probably check whether
* R->sp == 0, because DNS_R_SEARCH runs regardless of
* options.recurse. See DNS_R_BIND.
*/
if (!R->resconf->options.recurse) {
/* Make first answer our tentative answer */
if (!R->nodata)
dns_p_movptr(&R->nodata, &F->answer);
dgoto(R->sp, DNS_R_SEARCH);
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) {
dns_p_movptr(&F->hints, &F->answer);
dgoto(R->sp, DNS_R_ITERATE);
}
/* XXX: Should this go further up? */
if (dns_header(F->answer)->aa)
dgoto(R->sp, DNS_R_FINISH);
/* XXX: Should we copy F->answer to R->nodata? */
dgoto(R->sp, DNS_R_FOREACH_A);
case DNS_R_FOREACH_AAAA: {
struct dns_aaaa aaaa;
struct sockaddr_in6 sin6;
/*
* NOTE: Iterator initialized in DNS_R_FOREACH_NS because
* this state is re-entrant, but we need to reset
* .name to a valid pointer each time.
*/
if ((error = dns_ns_parse(&u.ns, &F->hints_ns, F->hints)))
goto error;
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_AAAA;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (!dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
if (!dns_rr_i_count(&F->hints_j)) {
/* Check if we have in fact servers
with an IPv4 address. */
dns_rr_i_init(&F->hints_j);
F->hints_j.name = u.ns.host;
F->hints_j.type = DNS_T_A;
F->hints_j.section = DNS_S_ALL & ~DNS_S_QD;
if (dns_rr_grep(&rr, 1, &F->hints_j, F->hints, &error)) {
/* We do. Reinitialize
iterator and handle it. */
dns_rr_i_init(&F->hints_j);
dgoto(R->sp, DNS_R_FOREACH_A);
}
dgoto(R->sp, DNS_R_RESOLV0_NS);
}
dgoto(R->sp, DNS_R_FOREACH_NS);
}
if ((error = dns_aaaa_parse(&aaaa, &rr, F->hints)))
goto error;
memset(&sin6, '\0', sizeof sin6); /* NB: silence valgrind */
sin6.sin6_family = AF_INET6;
sin6.sin6_addr = aaaa.addr;
if (R->sp == 0)
sin6.sin6_port = dns_hints_port(R->hints, AF_INET, &sin6.sin6_addr);
else
sin6.sin6_port = htons(53);
if (DNS_DEBUG) {
char addr[INET6_ADDRSTRLEN + 1];
dns_aaaa_print(addr, sizeof addr, &aaaa);
dns_header(F->query)->qid = dns_so_mkqid(&R->so);
DNS_SHOW(F->query, "ASKING: %s/%s @ DEPTH: %u)", u.ns.host, addr, R->sp);
}
dns_trace_setcname(R->trace, u.ns.host, (struct sockaddr *)&sin6);
if ((error = dns_so_submit(&R->so, F->query, (struct sockaddr *)&sin6)))
goto error;
F->state++;
} /* FALL THROUGH */
case DNS_R_QUERY_AAAA:
if (dns_so_elapsed(&R->so) >= dns_resconf_timeout(R->resconf))
dgoto(R->sp, DNS_R_FOREACH_AAAA);
error = dns_so_check(&R->so);
if (error == ECONNREFUSED)
dgoto(R->sp, DNS_R_FOREACH_AAAA);
else if (error)
goto error;
if (!dns_p_setptr(&F->answer, dns_so_fetch(&R->so, &error)))
goto error;
if (DNS_DEBUG) {
DNS_SHOW(F->answer, "ANSWER @ DEPTH: %u)", R->sp);
}
if (dns_p_rcode(F->answer) == DNS_RC_FORMERR ||
dns_p_rcode(F->answer) == DNS_RC_NOTIMP ||
dns_p_rcode(F->answer) == DNS_RC_BADVERS) {
/* Temporarily disable EDNS0 and try again. */
if (F->qflags & DNS_Q_EDNS0) {
F->qflags &= ~DNS_Q_EDNS0;
if ((error = dns_q_remake(&F->query, F->qflags)))
goto error;
dgoto(R->sp, DNS_R_FOREACH_AAAA);
}
}
if ((error = dns_rr_parse(&rr, 12, F->query)))
goto error;
if (!(len = dns_d_expand(u.name, sizeof u.name, rr.dn.p, F->query, &error)))
goto error;
else if (len >= sizeof u.name)
goto toolong;
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = rr.type) {
dgoto(R->sp, DNS_R_FINISH); /* Found */
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_AN, .name = u.name, .type = DNS_T_CNAME) {
F->ans_cname = rr;
dgoto(R->sp, DNS_R_CNAME0_A);
}
/*
* XXX: The condition here should probably check whether
* R->sp == 0, because DNS_R_SEARCH runs regardless of
* options.recurse. See DNS_R_BIND.
*/
if (!R->resconf->options.recurse) {
/* Make first answer our tentative answer */
if (!R->nodata)
dns_p_movptr(&R->nodata, &F->answer);
dgoto(R->sp, DNS_R_SEARCH);
}
dns_rr_foreach(&rr, F->answer, .section = DNS_S_NS, .type = DNS_T_NS) {
dns_p_movptr(&F->hints, &F->answer);
dgoto(R->sp, DNS_R_ITERATE);
}
/* XXX: Should this go further up? */
if (dns_header(F->answer)->aa)
dgoto(R->sp, DNS_R_FINISH);
/* XXX: Should we copy F->answer to R->nodata? */
dgoto(R->sp, DNS_R_FOREACH_AAAA);
case DNS_R_CNAME0_A:
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_FINISH);
if ((error = dns_cname_parse(&u.cname, &F->ans_cname, F->answer)))
goto error;
if ((error = dns_res_frame_prepare(R, &F[1], u.cname.host, dns_rr_type(12, F->query), DNS_C_IN)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
case DNS_R_CNAME1_A:
if (!(P = dns_res_merge(F->answer, F[1].answer, &error)))
goto error;
dns_p_setptr(&F->answer, P);
dgoto(R->sp, DNS_R_FINISH);
case DNS_R_FINISH:
if (!F->answer)
goto noanswer;
if (!R->resconf->options.smart || R->sp > 0)
dgoto(R->sp, DNS_R_DONE);
R->smart.section = DNS_S_AN;
R->smart.type = R->qtype;
dns_rr_i_init(&R->smart);
F->state++; /* FALL THROUGH */
case DNS_R_SMART0_A:
if (&F[1] >= endof(R->stack))
dgoto(R->sp, DNS_R_DONE);
while (dns_rr_grep(&rr, 1, &R->smart, F->answer, &error)) {
union {
struct dns_ns ns;
struct dns_mx mx;
struct dns_srv srv;
} rd;
const char *qname;
enum dns_type qtype;
enum dns_class qclass;
switch (rr.type) {
case DNS_T_NS:
if ((error = dns_ns_parse(&rd.ns, &rr, F->answer)))
goto error;
qname = rd.ns.host;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
case DNS_T_MX:
if ((error = dns_mx_parse(&rd.mx, &rr, F->answer)))
goto error;
qname = rd.mx.host;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
case DNS_T_SRV:
if ((error = dns_srv_parse(&rd.srv, &rr, F->answer)))
goto error;
qname = rd.srv.target;
qtype = DNS_T_A;
qclass = DNS_C_IN;
break;
default:
continue;
} /* switch() */
if ((error = dns_res_frame_prepare(R, &F[1], qname, qtype, qclass)))
goto error;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
} /* while() */
/*
* NOTE: SMTP specification says to fallback to A record.
*
* XXX: Should we add a mock MX answer?
*/
if (R->qtype == DNS_T_MX && R->smart.state.count == 0) {
if ((error = dns_res_frame_prepare(R, &F[1], R->qname, DNS_T_A, DNS_C_IN)))
goto error;
R->smart.state.count++;
F->state++;
dgoto(++R->sp, DNS_R_INIT);
}
dgoto(R->sp, DNS_R_DONE);
case DNS_R_SMART1_A:
if (!F[1].answer)
goto noanswer;
/*
* FIXME: For CNAME chains (which are typically illegal in
* this context), we should rewrite the record host name
* to the original smart qname. All the user cares about
* is locating that A/AAAA record.
*/
dns_rr_foreach(&rr, F[1].answer, .section = DNS_S_AN, .type = DNS_T_A) {
rr.section = DNS_S_AR;
if (dns_rr_exists(&rr, F[1].answer, F->answer))
continue;
while ((error = dns_rr_copy(F->answer, &rr, F[1].answer))) {
if (error != DNS_ENOBUFS)
goto error;
if ((error = dns_p_grow(&F->answer)))
goto error;
}
}
dgoto(R->sp, DNS_R_SMART0_A);
case DNS_R_DONE:
if (!F->answer)
goto noanswer;
if (R->sp > 0)
dgoto(--R->sp, F[-1].state);
break;
case DNS_R_SERVFAIL:
if (!dns_p_setptr(&F->answer, dns_p_make(DNS_P_QBUFSIZ, &error)))
goto error;
dns_header(F->answer)->qr = 1;
dns_header(F->answer)->rcode = DNS_RC_SERVFAIL;
if ((error = dns_p_push(F->answer, DNS_S_QD, R->qname, strlen(R->qname), R->qtype, R->qclass, 0, 0)))
goto error;
dgoto(R->sp, DNS_R_DONE);
default:
error = EINVAL;
goto error;
} /* switch () */
return 0;
noquery:
error = DNS_ENOQUERY;
goto error;
noanswer:
error = DNS_ENOANSWER;
goto error;
toolong:
error = DNS_EILLEGAL;
/* FALL THROUGH */
error:
return error;
} /* dns_res_exec() */
#undef goto
void dns_res_clear(struct dns_resolver *R) {
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
R->cache->clear(R->cache);
break;
default:
dns_so_clear(&R->so);
break;
}
} /* dns_res_clear() */
static int dns_res_events2(struct dns_resolver *R, enum dns_events type) {
int events;
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
events = R->cache->events(R->cache);
return (type == DNS_LIBEVENT)? DNS_POLL2EV(events) : events;
default:
return dns_so_events2(&R->so, type);
}
} /* dns_res_events2() */
int dns_res_events(struct dns_resolver *R) {
return dns_res_events2(R, R->so.opts.events);
} /* dns_res_events() */
int dns_res_pollfd(struct dns_resolver *R) {
switch (R->stack[R->sp].state) {
case DNS_R_CHECK:
return R->cache->pollfd(R->cache);
default:
return dns_so_pollfd(&R->so);
}
} /* dns_res_pollfd() */
time_t dns_res_timeout(struct dns_resolver *R) {
time_t elapsed;
switch (R->stack[R->sp].state) {
#if 0
case DNS_R_QUERY_AAAA:
#endif
case DNS_R_QUERY_A:
elapsed = dns_so_elapsed(&R->so);
if (elapsed <= dns_resconf_timeout(R->resconf))
return R->resconf->options.timeout - elapsed;
break;
default:
break;
} /* switch() */
/*
* NOTE: We're not in a pollable state, or the user code hasn't
* called dns_res_check properly. The calling code is probably
* broken. Put them into a slow-burn pattern.
*/
return 1;
} /* dns_res_timeout() */
time_t dns_res_elapsed(struct dns_resolver *R) {
return dns_elapsed(&R->elapsed);
} /* dns_res_elapsed() */
int dns_res_poll(struct dns_resolver *R, int timeout) {
return dns_poll(dns_res_pollfd(R), dns_res_events2(R, DNS_SYSPOLL), timeout);
} /* dns_res_poll() */
int dns_res_submit2(struct dns_resolver *R, const char *qname, size_t qlen, enum dns_type qtype, enum dns_class qclass) {
dns_res_reset(R);
/* Don't anchor; that can conflict with searchlist generation. */
dns_d_init(R->qname, sizeof R->qname, qname, (R->qlen = qlen), 0);
R->qtype = qtype;
R->qclass = qclass;
dns_begin(&R->elapsed);
dns_trace_res_submit(R->trace, R->qname, R->qtype, R->qclass, 0);
return 0;
} /* dns_res_submit2() */
int dns_res_submit(struct dns_resolver *R, const char *qname, enum dns_type qtype, enum dns_class qclass) {
return dns_res_submit2(R, qname, strlen(qname), qtype, qclass);
} /* dns_res_submit() */
int dns_res_check(struct dns_resolver *R) {
int error;
if (R->stack[0].state != DNS_R_DONE) {
if ((error = dns_res_exec(R)))
return error;
}
return 0;
} /* dns_res_check() */
struct dns_packet *dns_res_fetch(struct dns_resolver *R, int *_error) {
struct dns_packet *P = NULL;
int error;
if (R->stack[0].state != DNS_R_DONE) {
error = DNS_EUNKNOWN;
goto error;
}
if (!dns_p_movptr(&P, &R->stack[0].answer)) {
error = DNS_EFETCHED;
goto error;
}
dns_trace_res_fetch(R->trace, P, 0);
return P;
error:
*_error = error;
dns_trace_res_fetch(R->trace, NULL, error);
return NULL;
} /* dns_res_fetch() */
static struct dns_packet *dns_res_fetch_and_study(struct dns_resolver *R, int *_error) {
struct dns_packet *P = NULL;
int error;
if (!(P = dns_res_fetch(R, &error)))
goto error;
if ((error = dns_p_study(P)))
goto error;
return P;
error:
*_error = error;
dns_p_free(P);
return NULL;
} /* dns_res_fetch_and_study() */
struct dns_packet *dns_res_query(struct dns_resolver *res, const char *qname, enum dns_type qtype, enum dns_class qclass, int timeout, int *error_) {
int error;
if ((error = dns_res_submit(res, qname, qtype, qclass)))
goto error;
while ((error = dns_res_check(res))) {
if (dns_res_elapsed(res) > timeout)
error = DNS_ETIMEDOUT;
if (error != DNS_EAGAIN)
goto error;
if ((error = dns_res_poll(res, 1)))
goto error;
}
return dns_res_fetch(res, error_);
error:
*error_ = error;
return 0;
} /* dns_res_query() */
const struct dns_stat *dns_res_stat(struct dns_resolver *res) {
return dns_so_stat(&res->so);
} /* dns_res_stat() */
void dns_res_sethints(struct dns_resolver *res, struct dns_hints *hints) {
dns_hints_acquire(hints); /* acquire first in case same hints object */
dns_hints_close(res->hints);
res->hints = hints;
} /* dns_res_sethints() */
struct dns_trace *dns_res_trace(struct dns_resolver *res) {
return res->trace;
} /* dns_res_trace() */
void dns_res_settrace(struct dns_resolver *res, struct dns_trace *trace) {
struct dns_trace *otrace = res->trace;
res->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
dns_so_settrace(&res->so, trace);
} /* dns_res_settrace() */
/*
* A D D R I N F O R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
struct dns_addrinfo {
struct addrinfo hints;
struct dns_resolver *res;
struct dns_trace *trace;
char qname[DNS_D_MAXNAME + 1];
enum dns_type qtype;
unsigned short qport, port;
struct {
unsigned long todo;
int state;
int atype;
enum dns_type qtype;
} af;
struct dns_packet *answer;
struct dns_packet *glue;
struct dns_rr_i i, g;
struct dns_rr rr;
char cname[DNS_D_MAXNAME + 1];
char i_cname[DNS_D_MAXNAME + 1], g_cname[DNS_D_MAXNAME + 1];
int g_depth;
int state;
int found;
struct dns_stat st;
}; /* struct dns_addrinfo */
#define DNS_AI_AFMAX 32
#define DNS_AI_AF2INDEX(af) (1UL << ((af) - 1))
static inline unsigned long dns_ai_af2index(int af) {
dns_static_assert(dns_same_type(unsigned long, DNS_AI_AF2INDEX(1), 1), "internal type mismatch");
dns_static_assert(dns_same_type(unsigned long, ((struct dns_addrinfo *)0)->af.todo, 1), "internal type mismatch");
return (af > 0 && af <= DNS_AI_AFMAX)? DNS_AI_AF2INDEX(af) : 0;
}
static int dns_ai_setaf(struct dns_addrinfo *ai, int af, int qtype) {
ai->af.atype = af;
ai->af.qtype = qtype;
ai->af.todo &= ~dns_ai_af2index(af);
return af;
} /* dns_ai_setaf() */
#define DNS_SM_RESTORE \
do { pc = 0xff & (ai->af.state >> 0); i = 0xff & (ai->af.state >> 8); } while (0)
#define DNS_SM_SAVE \
do { ai->af.state = ((0xff & pc) << 0) | ((0xff & i) << 8); } while (0)
static int dns_ai_nextaf(struct dns_addrinfo *ai) {
int i, pc;
dns_static_assert(AF_UNSPEC == 0, "AF_UNSPEC constant not 0");
dns_static_assert(AF_INET <= DNS_AI_AFMAX, "AF_INET constant too large");
dns_static_assert(AF_INET6 <= DNS_AI_AFMAX, "AF_INET6 constant too large");
DNS_SM_ENTER;
if (ai->res) {
/*
* NB: On OpenBSD, at least, the types of entries resolved
* is the intersection of the /etc/resolv.conf families and
* the families permitted by the .ai_type hint. So if
* /etc/resolv.conf has "family inet4" and .ai_type
* is AF_INET6, then the address ::1 will return 0 entries
* even if AI_NUMERICHOST is specified in .ai_flags.
*/
while (i < (int)lengthof(ai->res->resconf->family)) {
int af = ai->res->resconf->family[i++];
if (af == AF_UNSPEC) {
DNS_SM_EXIT;
} else if (af < 0 || af > DNS_AI_AFMAX) {
continue;
} else if (!(DNS_AI_AF2INDEX(af) & ai->af.todo)) {
continue;
} else if (af == AF_INET) {
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
} else if (af == AF_INET6) {
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
}
}
} else {
/*
* NB: If we get here than AI_NUMERICFLAGS should be set and
* order shouldn't matter.
*/
if (DNS_AI_AF2INDEX(AF_INET) & ai->af.todo)
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET, DNS_T_A));
if (DNS_AI_AF2INDEX(AF_INET6) & ai->af.todo)
DNS_SM_YIELD(dns_ai_setaf(ai, AF_INET6, DNS_T_AAAA));
}
DNS_SM_LEAVE;
return dns_ai_setaf(ai, AF_UNSPEC, 0);
} /* dns_ai_nextaf() */
#undef DNS_SM_RESTORE
#undef DNS_SM_SAVE
static enum dns_type dns_ai_qtype(struct dns_addrinfo *ai) {
return (ai->qtype)? ai->qtype : ai->af.qtype;
} /* dns_ai_qtype() */
/* JW: This is not defined on mingw. */
#ifndef AI_NUMERICSERV
#define AI_NUMERICSERV 0
#endif
static dns_error_t dns_ai_parseport(unsigned short *port, const char *serv, const struct addrinfo *hints) {
const char *cp = serv;
unsigned long n = 0;
while (*cp >= '0' && *cp <= '9' && n < 65536) {
n *= 10;
n += *cp++ - '0';
}
if (*cp == '\0') {
if (cp == serv || n >= 65536)
return DNS_ESERVICE;
*port = n;
return 0;
}
if (hints->ai_flags & AI_NUMERICSERV)
return DNS_ESERVICE;
/* TODO: try getaddrinfo(NULL, serv, { .ai_flags = AI_NUMERICSERV }) */
return DNS_ESERVICE;
} /* dns_ai_parseport() */
struct dns_addrinfo *dns_ai_open(const char *host, const char *serv, enum dns_type qtype, const struct addrinfo *hints, struct dns_resolver *res, int *_error) {
static const struct dns_addrinfo ai_initializer;
struct dns_addrinfo *ai;
int error;
if (res) {
dns_res_acquire(res);
} else if (!(hints->ai_flags & AI_NUMERICHOST)) {
/*
* NOTE: it's assumed that *_error is set from a previous
* API function call, such as dns_res_stub(). Should change
* this semantic, but it's applied elsewhere, too.
*/
if (!*_error)
*_error = EINVAL;
return NULL;
}
if (!(ai = malloc(sizeof *ai)))
goto syerr;
*ai = ai_initializer;
ai->hints = *hints;
ai->res = res;
res = NULL;
if (sizeof ai->qname <= dns_strlcpy(ai->qname, host, sizeof ai->qname))
{ error = ENAMETOOLONG; goto error; }
ai->qtype = qtype;
ai->qport = 0;
if (serv && (error = dns_ai_parseport(&ai->qport, serv, hints)))
goto error;
ai->port = ai->qport;
/*
* FIXME: If an explicit A or AAAA record type conflicts with
* .ai_family or with resconf.family (i.e. AAAA specified but
- * AF_INET6 not in interection of .ai_family and resconf.family),
+ * AF_INET6 not in intersection of .ai_family and resconf.family),
* then what?
*/
switch (ai->qtype) {
case DNS_T_A:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
break;
case DNS_T_AAAA:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
break;
default: /* 0, MX, SRV, etc */
switch (ai->hints.ai_family) {
case AF_UNSPEC:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET) | DNS_AI_AF2INDEX(AF_INET6);
break;
case AF_INET:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET);
break;
case AF_INET6:
ai->af.todo = DNS_AI_AF2INDEX(AF_INET6);
break;
default:
break;
}
}
return ai;
syerr:
error = dns_syerr();
error:
*_error = error;
dns_ai_close(ai);
dns_res_close(res);
return NULL;
} /* dns_ai_open() */
void dns_ai_close(struct dns_addrinfo *ai) {
if (!ai)
return;
dns_res_close(ai->res);
dns_trace_close(ai->trace);
if (ai->answer != ai->glue)
dns_p_free(ai->glue);
dns_p_free(ai->answer);
free(ai);
} /* dns_ai_close() */
static int dns_ai_setent(struct addrinfo **ent, union dns_any *any, enum dns_type type, struct dns_addrinfo *ai) {
union u {
struct sockaddr_in sin;
struct sockaddr_in6 sin6;
struct sockaddr_storage ss;
} addr;
const char *cname;
size_t clen;
switch (type) {
case DNS_T_A:
memset(&addr.sin, '\0', sizeof addr.sin);
addr.sin.sin_family = AF_INET;
addr.sin.sin_port = htons(ai->port);
memcpy(&addr.sin.sin_addr, any, sizeof addr.sin.sin_addr);
break;
case DNS_T_AAAA:
memset(&addr.sin6, '\0', sizeof addr.sin6);
addr.sin6.sin6_family = AF_INET6;
addr.sin6.sin6_port = htons(ai->port);
memcpy(&addr.sin6.sin6_addr, any, sizeof addr.sin6.sin6_addr);
break;
default:
return EINVAL;
} /* switch() */
if (ai->hints.ai_flags & AI_CANONNAME) {
cname = (*ai->cname)? ai->cname : ai->qname;
clen = strlen(cname);
} else {
cname = NULL;
clen = 0;
}
if (!(*ent = malloc(sizeof **ent + dns_sa_len(&addr) + ((ai->hints.ai_flags & AI_CANONNAME)? clen + 1 : 0))))
return dns_syerr();
memset(*ent, '\0', sizeof **ent);
(*ent)->ai_family = addr.ss.ss_family;
(*ent)->ai_socktype = ai->hints.ai_socktype;
(*ent)->ai_protocol = ai->hints.ai_protocol;
(*ent)->ai_addr = memcpy((unsigned char *)*ent + sizeof **ent, &addr, dns_sa_len(&addr));
(*ent)->ai_addrlen = dns_sa_len(&addr);
if (ai->hints.ai_flags & AI_CANONNAME)
(*ent)->ai_canonname = memcpy((unsigned char *)*ent + sizeof **ent + dns_sa_len(&addr), cname, clen + 1);
ai->found++;
return 0;
} /* dns_ai_setent() */
enum dns_ai_state {
DNS_AI_S_INIT,
DNS_AI_S_NEXTAF,
DNS_AI_S_NUMERIC,
DNS_AI_S_SUBMIT,
DNS_AI_S_CHECK,
DNS_AI_S_FETCH,
DNS_AI_S_FOREACH_I,
DNS_AI_S_INIT_G,
DNS_AI_S_ITERATE_G,
DNS_AI_S_FOREACH_G,
DNS_AI_S_SUBMIT_G,
DNS_AI_S_CHECK_G,
DNS_AI_S_FETCH_G,
DNS_AI_S_DONE,
}; /* enum dns_ai_state */
#define dns_ai_goto(which) do { ai->state = (which); goto exec; } while (0)
int dns_ai_nextent(struct addrinfo **ent, struct dns_addrinfo *ai) {
struct dns_packet *ans, *glue;
struct dns_rr rr;
char qname[DNS_D_MAXNAME + 1];
union dns_any any;
size_t qlen, clen;
int error;
*ent = 0;
exec:
switch (ai->state) {
case DNS_AI_S_INIT:
ai->state++; /* FALL THROUGH */
case DNS_AI_S_NEXTAF:
if (!dns_ai_nextaf(ai))
dns_ai_goto(DNS_AI_S_DONE);
ai->state++; /* FALL THROUGH */
case DNS_AI_S_NUMERIC:
if (1 == dns_inet_pton(AF_INET, ai->qname, &any.a)) {
if (ai->af.atype == AF_INET) {
ai->state = DNS_AI_S_NEXTAF;
return dns_ai_setent(ent, &any, DNS_T_A, ai);
} else {
dns_ai_goto(DNS_AI_S_NEXTAF);
}
}
if (1 == dns_inet_pton(AF_INET6, ai->qname, &any.aaaa)) {
if (ai->af.atype == AF_INET6) {
ai->state = DNS_AI_S_NEXTAF;
return dns_ai_setent(ent, &any, DNS_T_AAAA, ai);
} else {
dns_ai_goto(DNS_AI_S_NEXTAF);
}
}
if (ai->hints.ai_flags & AI_NUMERICHOST)
dns_ai_goto(DNS_AI_S_NEXTAF);
ai->state++; /* FALL THROUGH */
case DNS_AI_S_SUBMIT:
assert(ai->res);
if ((error = dns_res_submit(ai->res, ai->qname, dns_ai_qtype(ai), DNS_C_IN)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_CHECK:
if ((error = dns_res_check(ai->res)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FETCH:
if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
return error;
if (ai->glue != ai->answer)
dns_p_free(ai->glue);
ai->glue = dns_p_movptr(&ai->answer, &ans);
/* Search generator may have changed the qname. */
if (!(qlen = dns_d_expand(qname, sizeof qname, 12, ai->answer, &error)))
return error;
else if (qlen >= sizeof qname)
return DNS_EILLEGAL;
if (!dns_d_cname(ai->cname, sizeof ai->cname, qname, qlen, ai->answer, &error))
return error;
dns_strlcpy(ai->i_cname, ai->cname, sizeof ai->i_cname);
dns_rr_i_init(&ai->i);
ai->i.section = DNS_S_AN;
ai->i.name = ai->i_cname;
ai->i.type = dns_ai_qtype(ai);
ai->i.sort = &dns_rr_i_order;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FOREACH_I:
if (!dns_rr_grep(&rr, 1, &ai->i, ai->answer, &error))
dns_ai_goto(DNS_AI_S_NEXTAF);
if ((error = dns_any_parse(&any, &rr, ai->answer)))
return error;
ai->port = ai->qport;
switch (rr.type) {
case DNS_T_A:
case DNS_T_AAAA:
return dns_ai_setent(ent, &any, rr.type, ai);
default:
if (!(clen = dns_any_cname(ai->cname, sizeof ai->cname, &any, rr.type)))
dns_ai_goto(DNS_AI_S_FOREACH_I);
/*
* Find the "real" canonical name. Some authorities
* publish aliases where an RFC defines a canonical
* name. We trust that the resolver followed any
* CNAME chains on it's own, regardless of whether
* the "smart" option is enabled.
*/
if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->cname, clen, ai->answer, &error))
return error;
if (rr.type == DNS_T_SRV)
ai->port = any.srv.port;
break;
} /* switch() */
ai->state++; /* FALL THROUGH */
case DNS_AI_S_INIT_G:
ai->g_depth = 0;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_ITERATE_G:
dns_strlcpy(ai->g_cname, ai->cname, sizeof ai->g_cname);
dns_rr_i_init(&ai->g);
ai->g.section = DNS_S_ALL & ~DNS_S_QD;
ai->g.name = ai->g_cname;
ai->g.type = ai->af.qtype;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FOREACH_G:
if (!dns_rr_grep(&rr, 1, &ai->g, ai->glue, &error)) {
if (dns_rr_i_count(&ai->g) > 0)
dns_ai_goto(DNS_AI_S_FOREACH_I);
else
dns_ai_goto(DNS_AI_S_SUBMIT_G);
}
if ((error = dns_any_parse(&any, &rr, ai->glue)))
return error;
return dns_ai_setent(ent, &any, rr.type, ai);
case DNS_AI_S_SUBMIT_G:
{
struct dns_rr_i I_instance = { 0 };
I_instance.section = DNS_S_QD;
I_instance.name = ai->g.name;
I_instance.type = ai->g.type;
/* skip if already queried */
if (dns_rr_grep(&rr, 1, &I_instance, ai->glue, &error))
dns_ai_goto(DNS_AI_S_FOREACH_I);
/* skip if we recursed (CNAME chains should have been handled in the resolver) */
if (++ai->g_depth > 1)
dns_ai_goto(DNS_AI_S_FOREACH_I);
if ((error = dns_res_submit(ai->res, ai->g.name, ai->g.type, DNS_C_IN)))
return error;
ai->state++;
} /* FALL THROUGH */
case DNS_AI_S_CHECK_G:
if ((error = dns_res_check(ai->res)))
return error;
ai->state++; /* FALL THROUGH */
case DNS_AI_S_FETCH_G:
if (!(ans = dns_res_fetch_and_study(ai->res, &error)))
return error;
glue = dns_p_merge(ai->glue, DNS_S_ALL, ans, DNS_S_ALL, &error);
dns_p_setptr(&ans, NULL);
if (!glue)
return error;
if (ai->glue != ai->answer)
dns_p_free(ai->glue);
ai->glue = glue;
if (!dns_d_cname(ai->cname, sizeof ai->cname, ai->g.name, strlen(ai->g.name), ai->glue, &error))
dns_ai_goto(DNS_AI_S_FOREACH_I);
dns_ai_goto(DNS_AI_S_ITERATE_G);
case DNS_AI_S_DONE:
if (ai->found) {
return ENOENT; /* TODO: Just return 0 */
} else if (ai->answer) {
switch (dns_p_rcode(ai->answer)) {
case DNS_RC_NOERROR:
/* FALL THROUGH */
case DNS_RC_NXDOMAIN:
return DNS_ENONAME;
default:
return DNS_EFAIL;
}
} else {
return DNS_EFAIL;
}
default:
return EINVAL;
} /* switch() */
} /* dns_ai_nextent() */
time_t dns_ai_elapsed(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_elapsed(ai->res) : 0;
} /* dns_ai_elapsed() */
void dns_ai_clear(struct dns_addrinfo *ai) {
if (ai->res)
dns_res_clear(ai->res);
} /* dns_ai_clear() */
int dns_ai_events(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_events(ai->res) : 0;
} /* dns_ai_events() */
int dns_ai_pollfd(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_pollfd(ai->res) : -1;
} /* dns_ai_pollfd() */
time_t dns_ai_timeout(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_timeout(ai->res) : 0;
} /* dns_ai_timeout() */
int dns_ai_poll(struct dns_addrinfo *ai, int timeout) {
return (ai->res)? dns_res_poll(ai->res, timeout) : 0;
} /* dns_ai_poll() */
size_t dns_ai_print(void *_dst, size_t lim, struct addrinfo *ent, struct dns_addrinfo *ai) {
struct dns_buf dst = DNS_B_INTO(_dst, lim);
char addr[DNS_PP_MAX(INET_ADDRSTRLEN, INET6_ADDRSTRLEN) + 1];
char __dst[DNS_STRMAXLEN + 1] = { 0 };
dns_b_puts(&dst, "[ ");
dns_b_puts(&dst, ai->qname);
dns_b_puts(&dst, " IN ");
if (ai->qtype) {
dns_b_puts(&dst, dns_strtype(ai->qtype, __dst));
} else if (ent->ai_family == AF_INET) {
dns_b_puts(&dst, dns_strtype(DNS_T_A, __dst));
} else if (ent->ai_family == AF_INET6) {
dns_b_puts(&dst, dns_strtype(DNS_T_AAAA, __dst));
} else {
dns_b_puts(&dst, "0");
}
dns_b_puts(&dst, " ]\n");
dns_b_puts(&dst, ".ai_family = ");
switch (ent->ai_family) {
case AF_INET:
dns_b_puts(&dst, "AF_INET");
break;
case AF_INET6:
dns_b_puts(&dst, "AF_INET6");
break;
default:
dns_b_fmtju(&dst, ent->ai_family, 0);
break;
}
dns_b_putc(&dst, '\n');
dns_b_puts(&dst, ".ai_socktype = ");
switch (ent->ai_socktype) {
case SOCK_STREAM:
dns_b_puts(&dst, "SOCK_STREAM");
break;
case SOCK_DGRAM:
dns_b_puts(&dst, "SOCK_DGRAM");
break;
default:
dns_b_fmtju(&dst, ent->ai_socktype, 0);
break;
}
dns_b_putc(&dst, '\n');
dns_inet_ntop(dns_sa_family(ent->ai_addr), dns_sa_addr(dns_sa_family(ent->ai_addr), ent->ai_addr, NULL), addr, sizeof addr);
dns_b_puts(&dst, ".ai_addr = [");
dns_b_puts(&dst, addr);
dns_b_puts(&dst, "]:");
dns_b_fmtju(&dst, ntohs(*dns_sa_port(dns_sa_family(ent->ai_addr), ent->ai_addr)), 0);
dns_b_putc(&dst, '\n');
dns_b_puts(&dst, ".ai_canonname = ");
dns_b_puts(&dst, (ent->ai_canonname)? ent->ai_canonname : "[NULL]");
dns_b_putc(&dst, '\n');
return dns_b_strllen(&dst);
} /* dns_ai_print() */
const struct dns_stat *dns_ai_stat(struct dns_addrinfo *ai) {
return (ai->res)? dns_res_stat(ai->res) : &ai->st;
} /* dns_ai_stat() */
struct dns_trace *dns_ai_trace(struct dns_addrinfo *ai) {
return ai->trace;
} /* dns_ai_trace() */
void dns_ai_settrace(struct dns_addrinfo *ai, struct dns_trace *trace) {
struct dns_trace *otrace = ai->trace;
ai->trace = dns_trace_acquire_p(trace);
dns_trace_close(otrace);
if (ai->res)
dns_res_settrace(ai->res, trace);
} /* dns_ai_settrace() */
/*
* M I S C E L L A N E O U S R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
static const struct {
char name[16];
enum dns_section type;
} dns_sections[] = {
{ "QUESTION", DNS_S_QUESTION },
{ "QD", DNS_S_QUESTION },
{ "ANSWER", DNS_S_ANSWER },
{ "AN", DNS_S_ANSWER },
{ "AUTHORITY", DNS_S_AUTHORITY },
{ "NS", DNS_S_AUTHORITY },
{ "ADDITIONAL", DNS_S_ADDITIONAL },
{ "AR", DNS_S_ADDITIONAL },
};
const char *(dns_strsection)(enum dns_section section, char *_dst) {
struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1);
unsigned i;
for (i = 0; i < lengthof(dns_sections); i++) {
if (dns_sections[i].type & section) {
dns_b_puts(&dst, dns_sections[i].name);
section &= ~dns_sections[i].type;
if (section)
dns_b_putc(&dst, '|');
}
}
if (section || dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & section), 0);
return dns_b_tostring(&dst);
} /* dns_strsection() */
enum dns_section dns_isection(const char *src) {
enum dns_section section = 0;
char sbuf[128];
char *name, *next;
unsigned i;
dns_strlcpy(sbuf, src, sizeof sbuf);
next = sbuf;
while ((name = dns_strsep(&next, "|+, \t"))) {
for (i = 0; i < lengthof(dns_sections); i++) {
if (!strcasecmp(dns_sections[i].name, name)) {
section |= dns_sections[i].type;
break;
}
}
}
return section;
} /* dns_isection() */
static const struct {
char name[8];
enum dns_class type;
} dns_classes[] = {
{ "IN", DNS_C_IN },
};
const char *(dns_strclass)(enum dns_class type, char *_dst) {
struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1);
unsigned i;
for (i = 0; i < lengthof(dns_classes); i++) {
if (dns_classes[i].type == type) {
dns_b_puts(&dst, dns_classes[i].name);
break;
}
}
if (dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & type), 0);
return dns_b_tostring(&dst);
} /* dns_strclass() */
enum dns_class dns_iclass(const char *name) {
unsigned i, class;
for (i = 0; i < lengthof(dns_classes); i++) {
if (!strcasecmp(dns_classes[i].name, name))
return dns_classes[i].type;
}
class = 0;
while (dns_isdigit(*name)) {
class *= 10;
class += *name++ - '0';
}
return DNS_PP_MIN(class, 0xffff);
} /* dns_iclass() */
const char *(dns_strtype)(enum dns_type type, char *_dst) {
struct dns_buf dst = DNS_B_INTO(_dst, DNS_STRMAXLEN + 1);
unsigned i;
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (dns_rrtypes[i].type == type) {
dns_b_puts(&dst, dns_rrtypes[i].name);
break;
}
}
if (dst.p == dst.base)
dns_b_fmtju(&dst, (0xffff & type), 0);
return dns_b_tostring(&dst);
} /* dns_strtype() */
enum dns_type dns_itype(const char *name) {
unsigned i, type;
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (!strcasecmp(dns_rrtypes[i].name, name))
return dns_rrtypes[i].type;
}
type = 0;
while (dns_isdigit(*name)) {
type *= 10;
type += *name++ - '0';
}
return DNS_PP_MIN(type, 0xffff);
} /* dns_itype() */
static char dns_opcodes[16][16] = {
[DNS_OP_QUERY] = "QUERY",
[DNS_OP_IQUERY] = "IQUERY",
[DNS_OP_STATUS] = "STATUS",
[DNS_OP_NOTIFY] = "NOTIFY",
[DNS_OP_UPDATE] = "UPDATE",
};
static const char *dns__strcode(int code, volatile char *dst, size_t lim) {
char _tmp[48] = "";
struct dns_buf tmp;
size_t p;
assert(lim > 0);
dns_b_fmtju(dns_b_into(&tmp, _tmp, DNS_PP_MIN(sizeof _tmp, lim - 1)), code, 0);
/* copy downwards so first byte is copied last (see below) */
p = (size_t)(tmp.p - tmp.base);
dst[p] = '\0';
while (p--)
dst[p] = _tmp[p];
return (const char *)dst;
}
const char *dns_stropcode(enum dns_opcode opcode) {
opcode = (unsigned)opcode % lengthof(dns_opcodes);
if ('\0' == dns_opcodes[opcode][0])
return dns__strcode(opcode, dns_opcodes[opcode], sizeof dns_opcodes[opcode]);
return dns_opcodes[opcode];
} /* dns_stropcode() */
enum dns_opcode dns_iopcode(const char *name) {
unsigned opcode;
for (opcode = 0; opcode < lengthof(dns_opcodes); opcode++) {
if (!strcasecmp(name, dns_opcodes[opcode]))
return opcode;
}
opcode = 0;
while (dns_isdigit(*name)) {
opcode *= 10;
opcode += *name++ - '0';
}
return DNS_PP_MIN(opcode, 0x0f);
} /* dns_iopcode() */
static char dns_rcodes[32][16] = {
[DNS_RC_NOERROR] = "NOERROR",
[DNS_RC_FORMERR] = "FORMERR",
[DNS_RC_SERVFAIL] = "SERVFAIL",
[DNS_RC_NXDOMAIN] = "NXDOMAIN",
[DNS_RC_NOTIMP] = "NOTIMP",
[DNS_RC_REFUSED] = "REFUSED",
[DNS_RC_YXDOMAIN] = "YXDOMAIN",
[DNS_RC_YXRRSET] = "YXRRSET",
[DNS_RC_NXRRSET] = "NXRRSET",
[DNS_RC_NOTAUTH] = "NOTAUTH",
[DNS_RC_NOTZONE] = "NOTZONE",
/* EDNS(0) extended RCODEs ... */
[DNS_RC_BADVERS] = "BADVERS",
};
const char *dns_strrcode(enum dns_rcode rcode) {
rcode = (unsigned)rcode % lengthof(dns_rcodes);
if ('\0' == dns_rcodes[rcode][0])
return dns__strcode(rcode, dns_rcodes[rcode], sizeof dns_rcodes[rcode]);
return dns_rcodes[rcode];
} /* dns_strrcode() */
enum dns_rcode dns_ircode(const char *name) {
unsigned rcode;
for (rcode = 0; rcode < lengthof(dns_rcodes); rcode++) {
if (!strcasecmp(name, dns_rcodes[rcode]))
return rcode;
}
rcode = 0;
while (dns_isdigit(*name)) {
rcode *= 10;
rcode += *name++ - '0';
}
return DNS_PP_MIN(rcode, 0xfff);
} /* dns_ircode() */
/*
* C O M M A N D - L I N E / R E G R E S S I O N R O U T I N E S
*
* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
#if DNS_MAIN
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
#if _WIN32
#include <getopt.h>
#endif
#if !_WIN32
#include <err.h>
#endif
struct {
struct {
const char *path[8];
unsigned count;
} resconf, nssconf, hosts, cache;
const char *qname;
enum dns_type qtype;
int (*sort)();
const char *trace;
int verbose;
struct {
struct dns_resolv_conf *resconf;
struct dns_hosts *hosts;
struct dns_trace *trace;
} memo;
struct sockaddr_storage socks_host;
const char *socks_user;
const char *socks_password;
} MAIN = {
.sort = &dns_rr_i_packet,
};
static void hexdump(const unsigned char *src, size_t len, FILE *fp) {
struct dns_hxd_lines_i lines = { 0 };
char line[128];
while (dns_hxd_lines(line, sizeof line, src, len, &lines)) {
fputs(line, fp);
}
} /* hexdump() */
DNS_NORETURN static void panic(const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
#if _WIN32
vfprintf(stderr, fmt, ap);
exit(EXIT_FAILURE);
#else
verrx(EXIT_FAILURE, fmt, ap);
#endif
} /* panic() */
#define panic_(fn, ln, fmt, ...) \
panic(fmt "%0s", (fn), (ln), __VA_ARGS__)
#define panic(...) \
panic_(__func__, __LINE__, "(%s:%d) " __VA_ARGS__, "")
static void *grow(unsigned char *p, size_t size) {
void *tmp;
if (!(tmp = realloc(p, size)))
panic("realloc(%"PRIuZ"): %s", size, dns_strerror(errno));
return tmp;
} /* grow() */
static size_t add(size_t a, size_t b) {
if (~a < b)
panic("%"PRIuZ" + %"PRIuZ": integer overflow", a, b);
return a + b;
} /* add() */
static size_t append(unsigned char **dst, size_t osize, const void *src, size_t len) {
size_t size = add(osize, len);
*dst = grow(*dst, size);
memcpy(*dst + osize, src, len);
return size;
} /* append() */
static size_t slurp(unsigned char **dst, size_t osize, FILE *fp, const char *path) {
size_t size = osize;
unsigned char buf[1024];
size_t count;
while ((count = fread(buf, 1, sizeof buf, fp)))
size = append(dst, size, buf, count);
if (ferror(fp))
panic("%s: %s", path, dns_strerror(errno));
return size;
} /* slurp() */
static struct dns_resolv_conf *resconf(void) {
struct dns_resolv_conf **resconf = &MAIN.memo.resconf;
const char *path;
unsigned i;
int error;
if (*resconf)
return *resconf;
if (!(*resconf = dns_resconf_open(&error)))
panic("dns_resconf_open: %s", dns_strerror(error));
if (!MAIN.resconf.count)
MAIN.resconf.path[MAIN.resconf.count++] = "/etc/resolv.conf";
for (i = 0; i < MAIN.resconf.count; i++) {
path = MAIN.resconf.path[i];
if (0 == strcmp(path, "-"))
error = dns_resconf_loadfile(*resconf, stdin);
else
error = dns_resconf_loadpath(*resconf, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
for (i = 0; i < MAIN.nssconf.count; i++) {
path = MAIN.nssconf.path[i];
if (0 == strcmp(path, "-"))
error = dns_nssconf_loadfile(*resconf, stdin);
else
error = dns_nssconf_loadpath(*resconf, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
if (!MAIN.nssconf.count) {
path = "/etc/nsswitch.conf";
if (!(error = dns_nssconf_loadpath(*resconf, path)))
MAIN.nssconf.path[MAIN.nssconf.count++] = path;
else if (error != ENOENT)
panic("%s: %s", path, dns_strerror(error));
}
return *resconf;
} /* resconf() */
static struct dns_hosts *hosts(void) {
struct dns_hosts **hosts = &MAIN.memo.hosts;
const char *path;
unsigned i;
int error;
if (*hosts)
return *hosts;
if (!MAIN.hosts.count) {
MAIN.hosts.path[MAIN.hosts.count++] = "/etc/hosts";
/* Explicitly test dns_hosts_local() */
if (!(*hosts = dns_hosts_local(&error)))
panic("%s: %s", "/etc/hosts", dns_strerror(error));
return *hosts;
}
if (!(*hosts = dns_hosts_open(&error)))
panic("dns_hosts_open: %s", dns_strerror(error));
for (i = 0; i < MAIN.hosts.count; i++) {
path = MAIN.hosts.path[i];
if (0 == strcmp(path, "-"))
error = dns_hosts_loadfile(*hosts, stdin);
else
error = dns_hosts_loadpath(*hosts, path);
if (error)
panic("%s: %s", path, dns_strerror(error));
}
return *hosts;
} /* hosts() */
#if DNS_CACHE
#include "cache.h"
static struct dns_cache *cache(void) {
static struct cache *cache;
const char *path;
unsigned i;
int error;
if (cache)
return cache_resi(cache);
if (!MAIN.cache.count)
return NULL;
if (!(cache = cache_open(&error)))
panic("%s: %s", MAIN.cache.path[0], dns_strerror(error));
for (i = 0; i < MAIN.cache.count; i++) {
path = MAIN.cache.path[i];
if (!strcmp(path, "-")) {
if ((error = cache_loadfile(cache, stdin, NULL, 0)))
panic("%s: %s", path, dns_strerror(error));
} else if ((error = cache_loadpath(cache, path, NULL, 0)))
panic("%s: %s", path, dns_strerror(error));
}
return cache_resi(cache);
} /* cache() */
#else
static struct dns_cache *cache(void) { return NULL; }
#endif
static struct dns_trace *trace(const char *mode) {
static char omode[64] = "";
struct dns_trace **trace = &MAIN.memo.trace;
FILE *fp;
int error;
if (*trace && 0 == strcmp(omode, mode))
return *trace;
if (!MAIN.trace)
return NULL;
if (!(fp = fopen(MAIN.trace, mode)))
panic("%s: %s", MAIN.trace, strerror(errno));
dns_trace_close(*trace);
if (!(*trace = dns_trace_open(fp, &error)))
panic("%s: %s", MAIN.trace, dns_strerror(error));
dns_strlcpy(omode, mode, sizeof omode);
return *trace;
}
static void print_packet(struct dns_packet *P, FILE *fp) {
struct dns_rr_i I_instance = { 0 };
I.sort = MAIN.sort;
dns_p_dump3(P, &I, fp);
if (MAIN.verbose > 2)
hexdump(P->data, P->end, fp);
} /* print_packet() */
static int parse_packet(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
struct dns_packet *P = dns_p_init(&P_instance.p, 512);
struct dns_packet *Q = dns_p_init(&Q_instance.p, 512);
enum dns_section section;
struct dns_rr rr;
int error;
union dns_any any;
char pretty[sizeof any * 2];
size_t len;
char __dst[DNS_STRMAXLEN + 1] = { 0 };
P->end = fread(P->data, 1, P->size, stdin);
fputs(";; [HEADER]\n", stdout);
fprintf(stdout, ";; qr : %s(%d)\n", (dns_header(P)->qr)? "RESPONSE" : "QUERY", dns_header(P)->qr);
fprintf(stdout, ";; opcode : %s(%d)\n", dns_stropcode(dns_header(P)->opcode), dns_header(P)->opcode);
fprintf(stdout, ";; aa : %s(%d)\n", (dns_header(P)->aa)? "AUTHORITATIVE" : "NON-AUTHORITATIVE", dns_header(P)->aa);
fprintf(stdout, ";; tc : %s(%d)\n", (dns_header(P)->tc)? "TRUNCATED" : "NOT-TRUNCATED", dns_header(P)->tc);
fprintf(stdout, ";; rd : %s(%d)\n", (dns_header(P)->rd)? "RECURSION-DESIRED" : "RECURSION-NOT-DESIRED", dns_header(P)->rd);
fprintf(stdout, ";; ra : %s(%d)\n", (dns_header(P)->ra)? "RECURSION-ALLOWED" : "RECURSION-NOT-ALLOWED", dns_header(P)->ra);
fprintf(stdout, ";; rcode : %s(%d)\n", dns_strrcode(dns_p_rcode(P)), dns_p_rcode(P));
section = 0;
dns_rr_foreach(&rr, P, .sort = MAIN.sort) {
if (section != rr.section)
fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section, __dst), dns_p_count(P, rr.section));
if ((len = dns_rr_print(pretty, sizeof pretty, &rr, P, &error)))
fprintf(stdout, "%s\n", pretty);
dns_rr_copy(Q, &rr, P);
section = rr.section;
}
fputs("; ; ; ; ; ; ; ;\n\n", stdout);
section = 0;
#if 0
dns_rr_foreach(&rr, Q, .name = "ns8.yahoo.com.") {
#else
char _p[DNS_D_MAXNAME + 1] = { 0 };
const char *dn = "ns8.yahoo.com";
char *_name = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR);
struct dns_rr rrset[32];
struct dns_rr_i I_instance = { 0 };
struct dns_rr_i *rri = &I;
unsigned rrcount = dns_rr_grep(rrset, lengthof(rrset), rri, Q, &error);
I.name = _name;
I.sort = MAIN.sort;
for (unsigned i = 0; i < rrcount; i++) {
rr = rrset[i];
#endif
if (section != rr.section)
fprintf(stdout, "\n;; [%s:%d]\n", dns_strsection(rr.section, __dst), dns_p_count(Q, rr.section));
if ((len = dns_rr_print(pretty, sizeof pretty, &rr, Q, &error)))
fprintf(stdout, "%s\n", pretty);
section = rr.section;
}
if (MAIN.verbose > 1) {
fprintf(stderr, "orig:%"PRIuZ"\n", P->end);
hexdump(P->data, P->end, stdout);
fprintf(stderr, "copy:%"PRIuZ"\n", Q->end);
hexdump(Q->data, Q->end, stdout);
}
return 0;
} /* parse_packet() */
static int parse_domain(int argc, char *argv[]) {
char _p[DNS_D_MAXNAME + 1] = { 0 };
char *dn;
dn = (argc > 1)? argv[1] : "f.l.google.com";
printf("[%s]\n", dn);
dn = dns_d_init(_p, sizeof _p, dn, strlen (dn), DNS_D_ANCHOR);
do {
puts(dn);
} while (dns_d_cleave(dn, strlen(dn) + 1, dn, strlen(dn)));
return 0;
} /* parse_domain() */
static int trim_domain(int argc, char **argv) {
for (argc--, argv++; argc > 0; argc--, argv++) {
char name[DNS_D_MAXNAME + 1];
dns_d_trim(name, sizeof name, *argv, strlen(*argv), DNS_D_ANCHOR);
puts(name);
}
return 0;
} /* trim_domain() */
static int expand_domain(int argc, char *argv[]) {
unsigned short rp = 0;
unsigned char *src = NULL;
unsigned char *dst;
struct dns_packet *pkt;
size_t lim = 0, len;
int error;
if (argc > 1)
rp = atoi(argv[1]);
len = slurp(&src, 0, stdin, "-");
if (!(pkt = dns_p_make(len, &error)))
panic("malloc(%"PRIuZ"): %s", len, dns_strerror(error));
memcpy(pkt->data, src, len);
pkt->end = len;
lim = 1;
dst = grow(NULL, lim);
while (lim <= (len = dns_d_expand(dst, lim, rp, pkt, &error))) {
lim = add(len, 1);
dst = grow(dst, lim);
}
if (!len)
panic("expand: %s", dns_strerror(error));
fwrite(dst, 1, len, stdout);
fflush(stdout);
free(src);
free(dst);
free(pkt);
return 0;
} /* expand_domain() */
static int show_resconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
resconf(); /* load it */
fputs("; SOURCES\n", stdout);
for (i = 0; i < MAIN.resconf.count; i++)
fprintf(stdout, "; %s\n", MAIN.resconf.path[i]);
for (i = 0; i < MAIN.nssconf.count; i++)
fprintf(stdout, "; %s\n", MAIN.nssconf.path[i]);
fputs(";\n", stdout);
dns_resconf_dump(resconf(), stdout);
return 0;
} /* show_resconf() */
static int show_nssconf(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
resconf();
fputs("# SOURCES\n", stdout);
for (i = 0; i < MAIN.resconf.count; i++)
fprintf(stdout, "# %s\n", MAIN.resconf.path[i]);
for (i = 0; i < MAIN.nssconf.count; i++)
fprintf(stdout, "# %s\n", MAIN.nssconf.path[i]);
fputs("#\n", stdout);
dns_nssconf_dump(resconf(), stdout);
return 0;
} /* show_nssconf() */
static int show_hosts(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
unsigned i;
hosts();
fputs("# SOURCES\n", stdout);
for (i = 0; i < MAIN.hosts.count; i++)
fprintf(stdout, "# %s\n", MAIN.hosts.path[i]);
fputs("#\n", stdout);
dns_hosts_dump(hosts(), stdout);
return 0;
} /* show_hosts() */
static int query_hosts(int argc, char *argv[]) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
struct dns_packet *Q = dns_p_init(&Q_instance.p, 512);
struct dns_packet *A;
char qname[DNS_D_MAXNAME + 1];
size_t qlen;
int error;
if (!MAIN.qname)
MAIN.qname = (argc > 1)? argv[1] : "localhost";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_A;
hosts();
if (MAIN.qtype == DNS_T_PTR && !strstr(MAIN.qname, "arpa")) {
union { struct in_addr a; struct in6_addr a6; } addr;
int af = (strchr(MAIN.qname, ':'))? AF_INET6 : AF_INET;
if ((error = dns_pton(af, MAIN.qname, &addr)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
qlen = dns_ptr_qname(qname, sizeof qname, af, &addr);
} else
qlen = dns_strlcpy(qname, MAIN.qname, sizeof qname);
if ((error = dns_p_push(Q, DNS_S_QD, qname, qlen, MAIN.qtype, DNS_C_IN, 0, 0)))
panic("%s: %s", qname, dns_strerror(error));
if (!(A = dns_hosts_query(hosts(), Q, &error)))
panic("%s: %s", qname, dns_strerror(error));
print_packet(A, stdout);
free(A);
return 0;
} /* query_hosts() */
static int search_list(int argc, char *argv[]) {
const char *qname = (argc > 1)? argv[1] : "f.l.google.com";
unsigned long i = 0;
char name[DNS_D_MAXNAME + 1];
printf("[%s]\n", qname);
while (dns_resconf_search(name, sizeof name, qname, strlen(qname), resconf(), &i))
puts(name);
return 0;
} /* search_list() */
static int permute_set(int argc, char *argv[]) {
unsigned lo, hi, i;
struct dns_k_permutor p;
hi = (--argc > 0)? atoi(argv[argc]) : 8;
lo = (--argc > 0)? atoi(argv[argc]) : 0;
fprintf(stderr, "[%u .. %u]\n", lo, hi);
dns_k_permutor_init(&p, lo, hi);
for (i = lo; i <= hi; i++)
fprintf(stdout, "%u\n", dns_k_permutor_step(&p));
// printf("%u -> %u -> %u\n", i, dns_k_permutor_E(&p, i), dns_k_permutor_D(&p, dns_k_permutor_E(&p, i)));
return 0;
} /* permute_set() */
static int shuffle_16(int argc, char *argv[]) {
unsigned n, r;
if (--argc > 0) {
n = 0xffff & atoi(argv[argc]);
r = (--argc > 0)? (unsigned)atoi(argv[argc]) : dns_random();
fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
} else {
r = dns_random();
for (n = 0; n < 65536; n++)
fprintf(stdout, "%hu\n", dns_k_shuffle16(n, r));
}
return 0;
} /* shuffle_16() */
static int dump_random(int argc, char *argv[]) {
unsigned char b[32];
unsigned i, j, n, r;
n = (argc > 1)? atoi(argv[1]) : 32;
while (n) {
i = 0;
do {
r = dns_random();
for (j = 0; j < sizeof r && i < n && i < sizeof b; i++, j++) {
b[i] = 0xff & r;
r >>= 8;
}
} while (i < n && i < sizeof b);
hexdump(b, i, stdout);
n -= i;
}
return 0;
} /* dump_random() */
static int send_query(int argc, char *argv[]) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } Q_instance = { 0 };
struct dns_packet *A, *Q = dns_p_init(&Q_instance.p, 512);
char host[INET6_ADDRSTRLEN + 1];
struct sockaddr_storage ss;
struct dns_socket *so;
int error, type;
struct dns_options opts = { 0 };
char __dst[DNS_STRMAXLEN + 1] = { 0 };
memset(&ss, 0, sizeof ss);
if (argc > 1) {
ss.ss_family = (strchr(argv[1], ':'))? AF_INET6 : AF_INET;
if ((error = dns_pton(ss.ss_family, argv[1], dns_sa_addr(ss.ss_family, &ss, NULL))))
panic("%s: %s", argv[1], dns_strerror(error));
*dns_sa_port(ss.ss_family, &ss) = htons(53);
} else
memcpy(&ss, &resconf()->nameserver[0], dns_sa_len(&resconf()->nameserver[0]));
if (!dns_inet_ntop(ss.ss_family, dns_sa_addr(ss.ss_family, &ss, NULL), host, sizeof host))
panic("bad host address, or none provided");
if (!MAIN.qname)
MAIN.qname = "ipv6.google.com";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_AAAA;
if ((error = dns_p_push(Q, DNS_S_QD, MAIN.qname, strlen(MAIN.qname), MAIN.qtype, DNS_C_IN, 0, 0)))
panic("dns_p_push: %s", dns_strerror(error));
dns_header(Q)->rd = 1;
if (strstr(argv[0], "udp"))
type = SOCK_DGRAM;
else if (strstr(argv[0], "tcp"))
type = SOCK_STREAM;
else
type = dns_res_tcp2type(resconf()->options.tcp);
fprintf(stderr, "querying %s for %s IN %s\n", host, MAIN.qname, dns_strtype(MAIN.qtype, __dst));
if (!(so = dns_so_open((struct sockaddr *)&resconf()->iface, type, &opts, &error)))
panic("dns_so_open: %s", dns_strerror(error));
while (!(A = dns_so_query(so, Q, (struct sockaddr *)&ss, &error))) {
if (error != DNS_EAGAIN)
panic("dns_so_query: %s (%d)", dns_strerror(error), error);
if (dns_so_elapsed(so) > 10)
panic("query timed-out");
dns_so_poll(so, 1);
}
print_packet(A, stdout);
dns_so_close(so);
return 0;
} /* send_query() */
static int print_arpa(int argc, char *argv[]) {
const char *ip = (argc > 1)? argv[1] : "::1";
int af = (strchr(ip, ':'))? AF_INET6 : AF_INET;
union { struct in_addr a4; struct in6_addr a6; } addr;
char host[DNS_D_MAXNAME + 1];
if (1 != dns_inet_pton(af, ip, &addr) || 0 == dns_ptr_qname(host, sizeof host, af, &addr))
panic("%s: invalid address", ip);
fprintf(stdout, "%s\n", host);
return 0;
} /* print_arpa() */
static int show_hints(int argc, char *argv[]) {
struct dns_hints *(*load)(struct dns_resolv_conf *, int *);
const char *which, *how, *who;
struct dns_hints *hints;
int error;
which = (argc > 1)? argv[1] : "local";
how = (argc > 2)? argv[2] : "plain";
who = (argc > 3)? argv[3] : "google.com";
load = (0 == strcmp(which, "local"))
? &dns_hints_local
: &dns_hints_root;
if (!(hints = load(resconf(), &error)))
panic("%s: %s", argv[0], dns_strerror(error));
if (0 == strcmp(how, "plain")) {
dns_hints_dump(hints, stdout);
} else {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *query, *answer;
query = dns_p_init(&P_instance.p, 512);
if ((error = dns_p_push(query, DNS_S_QUESTION, who, strlen(who), DNS_T_A, DNS_C_IN, 0, 0)))
panic("%s: %s", who, dns_strerror(error));
if (!(answer = dns_hints_query(hints, query, &error)))
panic("%s: %s", who, dns_strerror(error));
print_packet(answer, stdout);
free(answer);
}
dns_hints_close(hints);
return 0;
} /* show_hints() */
static int resolve_query(int argc DNS_NOTUSED, char *argv[]) {
_Bool recurse = !!strstr(argv[0], "recurse");
struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
struct dns_resolver *R;
struct dns_packet *ans;
const struct dns_stat *st;
int error;
struct dns_options opts = { 0 };
opts.socks_host = &MAIN.socks_host;
opts.socks_user = MAIN.socks_user;
opts.socks_password = MAIN.socks_password;
if (!MAIN.qname)
MAIN.qname = "www.google.com";
if (!MAIN.qtype)
MAIN.qtype = DNS_T_A;
resconf()->options.recurse = recurse;
if (!(R = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(),
&opts, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
dns_res_settrace(R, trace("w+b"));
if ((error = dns_res_submit(R, MAIN.qname, MAIN.qtype, DNS_C_IN)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
while ((error = dns_res_check(R))) {
if (error != DNS_EAGAIN)
panic("dns_res_check: %s (%d)", dns_strerror(error), error);
if (dns_res_elapsed(R) > 30)
panic("query timed-out");
dns_res_poll(R, 1);
}
ans = dns_res_fetch(R, &error);
print_packet(ans, stdout);
free(ans);
st = dns_res_stat(R);
putchar('\n');
printf(";; queries: %"PRIuZ"\n", st->queries);
printf(";; udp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.sent.count, st->udp.sent.bytes);
printf(";; udp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->udp.rcvd.count, st->udp.rcvd.bytes);
printf(";; tcp sent: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.sent.count, st->tcp.sent.bytes);
printf(";; tcp rcvd: %"PRIuZ" in %"PRIuZ" bytes\n", st->tcp.rcvd.count, st->tcp.rcvd.bytes);
dns_res_close(R);
return 0;
} /* resolve_query() */
static int resolve_addrinfo(int argc DNS_NOTUSED, char *argv[]) {
_Bool recurse = !!strstr(argv[0], "recurse");
struct dns_hints *(*hints)() = (recurse)? &dns_hints_root : &dns_hints_local;
struct dns_resolver *res = NULL;
struct dns_addrinfo *ai = NULL;
struct addrinfo ai_hints = { .ai_family = PF_UNSPEC, .ai_socktype = SOCK_STREAM, .ai_flags = AI_CANONNAME };
struct addrinfo *ent;
char pretty[512];
int error;
struct dns_options opts = { 0 };
if (!MAIN.qname)
MAIN.qname = "www.google.com";
/* NB: MAIN.qtype of 0 means obey hints.ai_family */
resconf()->options.recurse = recurse;
if (!(res = dns_res_open(resconf(), hosts(), dns_hints_mortal(hints(resconf(), &error)), cache(), &opts, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
if (!(ai = dns_ai_open(MAIN.qname, "80", MAIN.qtype, &ai_hints, res, &error)))
panic("%s: %s", MAIN.qname, dns_strerror(error));
dns_ai_settrace(ai, trace("w+b"));
do {
switch (error = dns_ai_nextent(&ent, ai)) {
case 0:
dns_ai_print(pretty, sizeof pretty, ent, ai);
fputs(pretty, stdout);
free(ent);
break;
case ENOENT:
break;
case DNS_EAGAIN:
if (dns_ai_elapsed(ai) > 30)
panic("query timed-out");
dns_ai_poll(ai, 1);
break;
default:
panic("dns_ai_nextent: %s (%d)", dns_strerror(error), error);
}
} while (error != ENOENT);
dns_res_close(res);
dns_ai_close(ai);
return 0;
} /* resolve_addrinfo() */
static int dump_trace(int argc DNS_NOTUSED, char *argv[]) {
int error;
if (!MAIN.trace)
panic("no trace file specified");
if ((error = dns_trace_dump(trace("r"), stdout)))
panic("dump_trace: %s", dns_strerror(error));
return 0;
} /* dump_trace() */
static int echo_port(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
union {
struct sockaddr sa;
struct sockaddr_in sin;
} port;
int fd;
memset(&port, 0, sizeof port);
port.sin.sin_family = AF_INET;
port.sin.sin_port = htons(5354);
port.sin.sin_addr.s_addr = inet_addr("127.0.0.1");
if (-1 == (fd = socket(PF_INET, SOCK_DGRAM, 0)))
panic("socket: %s", strerror(errno));
if (0 != bind(fd, &port.sa, sizeof port.sa))
panic("127.0.0.1:5353: %s", dns_strerror(errno));
for (;;) {
union { unsigned char b[dns_p_calcsize((512))]; struct dns_packet p; } P_instance = { 0 };
struct dns_packet *pkt = dns_p_init(&P_instance.p, 512);
struct sockaddr_storage ss;
socklen_t slen = sizeof ss;
ssize_t count;
#if defined(MSG_WAITALL) /* MinGW issue */
int rflags = MSG_WAITALL;
#else
int rflags = 0;
#endif
count = recvfrom(fd, (char *)pkt->data, pkt->size, rflags, (struct sockaddr *)&ss, &slen);
if (!count || count < 0)
panic("recvfrom: %s", strerror(errno));
pkt->end = count;
dns_p_dump(pkt, stdout);
(void)sendto(fd, (char *)pkt->data, pkt->end, 0, (struct sockaddr *)&ss, slen);
}
return 0;
} /* echo_port() */
static int isection(int argc, char *argv[]) {
char __dst[DNS_STRMAXLEN + 1] = { 0 };
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_isection(name);
name = dns_strsection(type, __dst);
printf("%s (%d)\n", name, type);
return 0;
} /* isection() */
static int iclass(int argc, char *argv[]) {
char __dst[DNS_STRMAXLEN + 1] = { 0 };
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_iclass(name);
name = dns_strclass(type, __dst);
printf("%s (%d)\n", name, type);
return 0;
} /* iclass() */
static int itype(int argc, char *argv[]) {
char __dst[DNS_STRMAXLEN + 1] = { 0 };
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_itype(name);
name = dns_strtype(type, __dst);
printf("%s (%d)\n", name, type);
return 0;
} /* itype() */
static int iopcode(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_iopcode(name);
name = dns_stropcode(type);
printf("%s (%d)\n", name, type);
return 0;
} /* iopcode() */
static int ircode(int argc, char *argv[]) {
const char *name = (argc > 1)? argv[1] : "";
int type;
type = dns_ircode(name);
name = dns_strrcode(type);
printf("%s (%d)\n", name, type);
return 0;
} /* ircode() */
#define SIZE1(x) { DNS_PP_STRINGIFY(x), sizeof (x) }
#define SIZE2(x, ...) SIZE1(x), SIZE1(__VA_ARGS__)
#define SIZE3(x, ...) SIZE1(x), SIZE2(__VA_ARGS__)
#define SIZE4(x, ...) SIZE1(x), SIZE3(__VA_ARGS__)
#define SIZE(...) DNS_PP_CALL(DNS_PP_XPASTE(SIZE, DNS_PP_NARG(__VA_ARGS__)), __VA_ARGS__)
static int sizes(int argc DNS_NOTUSED, char *argv[] DNS_NOTUSED) {
static const struct { const char *name; size_t size; } type[] = {
SIZE(struct dns_header, struct dns_packet, struct dns_rr, struct dns_rr_i),
SIZE(struct dns_a, struct dns_aaaa, struct dns_mx, struct dns_ns),
SIZE(struct dns_cname, struct dns_soa, struct dns_ptr, struct dns_srv),
SIZE(struct dns_sshfp, struct dns_txt, union dns_any),
SIZE(struct dns_resolv_conf, struct dns_hosts, struct dns_hints, struct dns_hints_i),
SIZE(struct dns_options, struct dns_socket, struct dns_resolver, struct dns_addrinfo),
SIZE(struct dns_cache), SIZE(size_t), SIZE(void *), SIZE(long)
};
unsigned i, max;
for (i = 0, max = 0; i < lengthof(type); i++)
max = DNS_PP_MAX(max, strlen(type[i].name));
for (i = 0; i < lengthof(type); i++)
printf("%*s : %"PRIuZ"\n", max, type[i].name, type[i].size);
return 0;
} /* sizes() */
static const struct { const char *cmd; int (*run)(); const char *help; } cmds[] = {
{ "parse-packet", &parse_packet, "parse binary packet from stdin" },
{ "parse-domain", &parse_domain, "anchor and iteratively cleave domain" },
{ "trim-domain", &trim_domain, "trim and anchor domain name" },
{ "expand-domain", &expand_domain, "expand domain at offset NN in packet from stdin" },
{ "show-resconf", &show_resconf, "show resolv.conf data" },
{ "show-hosts", &show_hosts, "show hosts data" },
{ "show-nssconf", &show_nssconf, "show nsswitch.conf data" },
{ "query-hosts", &query_hosts, "query A, AAAA or PTR in hosts data" },
{ "search-list", &search_list, "generate query search list from domain" },
{ "permute-set", &permute_set, "generate random permutation -> (0 .. N or N .. M)" },
{ "shuffle-16", &shuffle_16, "simple 16-bit permutation" },
{ "dump-random", &dump_random, "generate random bytes" },
{ "send-query", &send_query, "send query to host" },
{ "send-query-udp", &send_query, "send udp query to host" },
{ "send-query-tcp", &send_query, "send tcp query to host" },
{ "print-arpa", &print_arpa, "print arpa. zone name of address" },
{ "show-hints", &show_hints, "print hints: show-hints [local|root] [plain|packet]" },
{ "resolve-stub", &resolve_query, "resolve as stub resolver" },
{ "resolve-recurse", &resolve_query, "resolve as recursive resolver" },
{ "addrinfo-stub", &resolve_addrinfo, "resolve through getaddrinfo clone" },
{ "addrinfo-recurse", &resolve_addrinfo, "resolve through getaddrinfo clone" },
/* { "resolve-nameinfo", &resolve_query, "resolve as recursive resolver" }, */
{ "dump-trace", &dump_trace, "dump the contents of a trace file" },
{ "echo", &echo_port, "server echo mode, for nmap fuzzing" },
{ "isection", &isection, "parse section string" },
{ "iclass", &iclass, "parse class string" },
{ "itype", &itype, "parse type string" },
{ "iopcode", &iopcode, "parse opcode string" },
{ "ircode", &ircode, "parse rcode string" },
{ "sizes", &sizes, "print data structure sizes" },
};
static void print_usage(const char *progname, FILE *fp) {
static const char *usage =
" [OPTIONS] COMMAND [ARGS]\n"
" -c PATH Path to resolv.conf\n"
" -n PATH Path to nsswitch.conf\n"
" -l PATH Path to local hosts\n"
" -z PATH Path to zone cache\n"
" -q QNAME Query name\n"
" -t QTYPE Query type\n"
" -s HOW Sort records\n"
" -S ADDR Address of SOCKS server to use\n"
" -P PORT Port of SOCKS server to use\n"
" -A USER:PASSWORD Credentials for the SOCKS server\n"
" -f PATH Path to trace file\n"
" -v Be more verbose (-vv show packets; -vvv hexdump packets)\n"
" -V Print version info\n"
" -h Print this usage message\n"
"\n";
unsigned i, n, m;
fputs(progname, fp);
fputs(usage, fp);
for (i = 0, m = 0; i < lengthof(cmds); i++) {
if (strlen(cmds[i].cmd) > m)
m = strlen(cmds[i].cmd);
}
for (i = 0; i < lengthof(cmds); i++) {
fprintf(fp, " %s ", cmds[i].cmd);
for (n = strlen(cmds[i].cmd); n < m; n++)
putc(' ', fp);
fputs(cmds[i].help, fp);
putc('\n', fp);
}
fputs("\nReport bugs to William Ahern <william@25thandClement.com>\n", fp);
} /* print_usage() */
static void print_version(const char *progname, FILE *fp) {
fprintf(fp, "%s (dns.c) %.8X\n", progname, dns_v_rel());
fprintf(fp, "vendor %s\n", dns_vendor());
fprintf(fp, "release %.8X\n", dns_v_rel());
fprintf(fp, "abi %.8X\n", dns_v_abi());
fprintf(fp, "api %.8X\n", dns_v_api());
} /* print_version() */
static void main_exit(void) {
dns_trace_close(MAIN.memo.trace);
MAIN.memo.trace = NULL;
dns_hosts_close(MAIN.memo.hosts);
MAIN.memo.hosts = NULL;
dns_resconf_close(MAIN.memo.resconf);
MAIN.memo.resconf = NULL;
} /* main_exit() */
int main(int argc, char **argv) {
extern int optind;
extern char *optarg;
const char *progname = argv[0];
unsigned i;
int ch;
atexit(&main_exit);
while (-1 != (ch = getopt(argc, argv, "q:t:c:n:l:z:s:S:P:A:f:vVh"))) {
switch (ch) {
case 'c':
assert(MAIN.resconf.count < lengthof(MAIN.resconf.path));
MAIN.resconf.path[MAIN.resconf.count++] = optarg;
break;
case 'n':
assert(MAIN.nssconf.count < lengthof(MAIN.nssconf.path));
MAIN.nssconf.path[MAIN.nssconf.count++] = optarg;
break;
case 'l':
assert(MAIN.hosts.count < lengthof(MAIN.hosts.path));
MAIN.hosts.path[MAIN.hosts.count++] = optarg;
break;
case 'z':
assert(MAIN.cache.count < lengthof(MAIN.cache.path));
MAIN.cache.path[MAIN.cache.count++] = optarg;
break;
case 'q':
MAIN.qname = optarg;
break;
case 't':
for (i = 0; i < lengthof(dns_rrtypes); i++) {
if (0 == strcasecmp(dns_rrtypes[i].name, optarg))
{ MAIN.qtype = dns_rrtypes[i].type; break; }
}
if (MAIN.qtype)
break;
for (i = 0; dns_isdigit(optarg[i]); i++) {
MAIN.qtype *= 10;
MAIN.qtype += optarg[i] - '0';
}
if (!MAIN.qtype)
panic("%s: invalid query type", optarg);
break;
case 's':
if (0 == strcasecmp(optarg, "packet"))
MAIN.sort = &dns_rr_i_packet;
else if (0 == strcasecmp(optarg, "shuffle"))
MAIN.sort = &dns_rr_i_shuffle;
else if (0 == strcasecmp(optarg, "order"))
MAIN.sort = &dns_rr_i_order;
else
panic("%s: invalid sort method", optarg);
break;
case 'S': {
dns_error_t error;
struct dns_resolv_conf *conf = resconf();
conf->options.tcp = DNS_RESCONF_TCP_SOCKS;
MAIN.socks_host.ss_family = (strchr(optarg, ':')) ? AF_INET6 : AF_INET;
if ((error = dns_pton(MAIN.socks_host.ss_family, optarg,
dns_sa_addr(MAIN.socks_host.ss_family,
&MAIN.socks_host, NULL))))
panic("%s: %s", optarg, dns_strerror(error));
*dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(1080);
break;
}
case 'P':
if (! MAIN.socks_host.ss_family)
panic("-P without prior -S");
*dns_sa_port(MAIN.socks_host.ss_family, &MAIN.socks_host) = htons(atoi(optarg));
break;
case 'A': {
char *password;
if (! MAIN.socks_host.ss_family)
panic("-A without prior -S");
if (! (password = strchr(optarg, ':')))
panic("Usage: -A USER:PASSWORD");
*password = 0;
password += 1;
MAIN.socks_user = optarg;
MAIN.socks_password = password;
break;
}
case 'f':
MAIN.trace = optarg;
break;
case 'v':
dns_debug = ++MAIN.verbose;
break;
case 'V':
print_version(progname, stdout);
return 0;
case 'h':
print_usage(progname, stdout);
return 0;
default:
print_usage(progname, stderr);
return EXIT_FAILURE;
} /* switch() */
} /* while() */
argc -= optind;
argv += optind;
for (i = 0; i < lengthof(cmds) && argv[0]; i++) {
if (0 == strcmp(cmds[i].cmd, argv[0]))
return cmds[i].run(argc, argv);
}
print_usage(progname, stderr);
return EXIT_FAILURE;
} /* main() */
#endif /* DNS_MAIN */
/*
* pop file-scoped compiler annotations
*/
#if __clang__
#pragma clang diagnostic pop
#elif DNS_GNUC_PREREQ(4,6,0)
#pragma GCC diagnostic pop
#endif
diff --git a/dirmngr/http-ntbtls.c b/dirmngr/http-ntbtls.c
index 2191acb60..c251f57ef 100644
--- a/dirmngr/http-ntbtls.c
+++ b/dirmngr/http-ntbtls.c
@@ -1,136 +1,136 @@
/* http-ntbtls.c - Support for using NTBTLS with http.c
* Copyright (C) 2017 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "dirmngr.h"
#include "certcache.h"
#include "validate.h"
#include "http-common.h"
#ifdef HTTP_USE_NTBTLS
# include <ntbtls.h>
/* The callback used to verify the peer's certificate. */
gpg_error_t
gnupg_http_tls_verify_cb (void *opaque,
http_t http,
http_session_t session,
unsigned int http_flags,
void *tls_context)
{
ctrl_t ctrl = opaque;
ntbtls_t tls = tls_context;
gpg_error_t err;
int idx;
ksba_cert_t cert;
ksba_cert_t hostcert = NULL;
unsigned int validate_flags;
/* const char *hostname; */
(void)http;
(void)session;
log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC);
log_assert (!ntbtls_check_context (tls));
/* Get the peer's certs from ntbtls. */
for (idx = 0;
(cert = ntbtls_x509_get_peer_cert (tls, idx)); idx++)
{
if (!idx)
hostcert = cert;
else
{
/* Quick hack to make verification work by inserting the supplied
* certs into the cache. FIXME! */
cache_cert (cert);
ksba_cert_release (cert);
}
}
if (!idx)
{
err = gpg_error (GPG_ERR_MISSING_CERT);
goto leave;
}
validate_flags = VALIDATE_FLAG_TLS;
/* If we are using the standard hkps:// pool use the dedicated root
- * certificate. Note that this differes from the GnuTLS
+ * certificate. Note that this differs from the GnuTLS
* implementation which uses this special certificate only if no
* other certificates are configured. */
/* Disabled for 2.3.2 to due problems with the standard hkps pool. */
/* hostname = ntbtls_get_hostname (tls); */
/* if (hostname */
/* && !ascii_strcasecmp (hostname, get_default_keyserver (1))) */
/* { */
/* validate_flags |= VALIDATE_FLAG_TRUST_HKPSPOOL; */
/* } */
/* else */
{
/* Use the certificates as requested from the HTTP module. */
if ((http_flags & HTTP_FLAG_TRUST_CFG))
validate_flags |= VALIDATE_FLAG_TRUST_CONFIG;
if ((http_flags & HTTP_FLAG_TRUST_DEF))
validate_flags |= VALIDATE_FLAG_TRUST_HKP;
if ((http_flags & HTTP_FLAG_TRUST_SYS))
validate_flags |= VALIDATE_FLAG_TRUST_SYSTEM;
/* If HKP trust is requested and there are no HKP certificates
* configured, also try the standard system certificates. */
if ((validate_flags & VALIDATE_FLAG_TRUST_HKP)
&& !cert_cache_any_in_class (CERTTRUST_CLASS_HKP))
validate_flags |= VALIDATE_FLAG_TRUST_SYSTEM;
}
if ((http_flags & HTTP_FLAG_NO_CRL))
validate_flags |= VALIDATE_FLAG_NOCRLCHECK;
err = validate_cert_chain (ctrl, hostcert, NULL, validate_flags, NULL);
leave:
ksba_cert_release (hostcert);
return err;
}
#else /*!HTTP_USE_NTBTLS*/
/* Dummy function used when not build without ntbtls support. */
gpg_error_t
gnupg_http_tls_verify_cb (void *opaque,
http_t http,
http_session_t session,
unsigned int flags,
void *tls_context)
{
(void)opaque;
(void)http;
(void)session;
(void)flags;
(void)tls_context;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
#endif /*!HTTP_USE_NTBTLS*/
diff --git a/dirmngr/http.c b/dirmngr/http.c
index 6ae9029be..5723a13f2 100644
--- a/dirmngr/http.c
+++ b/dirmngr/http.c
@@ -1,4606 +1,4606 @@
/* http.c - HTTP protocol handler
* Copyright (C) 1999, 2001-2004, 2006, 2009, 2010,
* 2011 Free Software Foundation, Inc.
* Copyright (C) 1999, 2001-2004, 2006, 2009, 2010, 2011, 2014 Werner Koch
* Copyright (C) 2015-2017, 2021, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of either
*
* - the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 3 of the License, or (at
* your option) any later version.
*
* or
*
* - the GNU General Public License as published by the Free
* Software Foundation; either version 2 of the License, or (at
* your option) any later version.
*
* or both in parallel, as here.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Simple HTTP client implementation. We try to keep the code as
self-contained as possible. There are some constraints however:
- estream is required. We now require estream because it provides a
very useful and portable asprintf implementation and the fopencookie
function.
- stpcpy is required
- fixme: list other requirements.
- Either HTTP_USE_NTBTLS or HTTP_USE_GNUTLS must be defined to select
which TLS library to use.
- With HTTP_NO_WSASTARTUP the socket initialization is not done
under Windows. This is useful if the socket layer has already
been initialized elsewhere. This also avoids the installation of
an exit handler to cleanup the socket layer.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <unistd.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
# include <winhttp.h>
# define SECURITY_WIN32 1
# include <security.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/types.h>
# include <sys/socket.h>
# include <sys/time.h>
# include <time.h>
# include <fcntl.h>
# include <netinet/in.h>
# include <arpa/inet.h>
# include <netdb.h>
#endif /*!HAVE_W32_SYSTEM*/
#ifdef WITHOUT_NPTH /* Give the Makefile a chance to build without Pth. */
# undef USE_NPTH
#endif
#ifdef USE_NPTH
# include <npth.h>
#endif
#if defined (HTTP_USE_GNUTLS) && defined (HTTP_USE_NTBTLS)
# error Both, HTTP_USE_GNUTLS and HTTP_USE_NTBTLS, are defined.
#endif
#ifdef HTTP_USE_NTBTLS
# include <ntbtls.h>
#elif HTTP_USE_GNUTLS
# include <gnutls/gnutls.h>
# include <gnutls/x509.h>
#endif /*HTTP_USE_GNUTLS*/
#include <assuan.h> /* We need the socket wrapper. */
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/sysutils.h" /* (gnupg_fd_t) */
#include "dns-stuff.h"
#include "dirmngr-status.h" /* (dirmngr_status_printf) */
#include "http.h"
#include "http-common.h"
#ifdef USE_NPTH
# define my_select(a,b,c,d,e) npth_select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) npth_accept ((a), (b), (c))
#else
# define my_select(a,b,c,d,e) select ((a), (b), (c), (d), (e))
# define my_accept(a,b,c) accept ((a), (b), (c))
#endif
#ifdef HAVE_W32_SYSTEM
#define sock_close(a) closesocket(a)
#else
#define sock_close(a) close(a)
#endif
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
#ifndef INADDR_NONE /* Slowaris is missing that. */
#define INADDR_NONE ((unsigned long)(-1))
#endif /*INADDR_NONE*/
#define HTTP_PROXY_ENV "http_proxy"
#define MAX_LINELEN 20000 /* Max. length of a HTTP header line. */
#define VALID_URI_CHARS "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"01234567890@" \
"!\"#$%&'()*+,-./:;<=>?[\\]^_{|}~"
#if HTTP_USE_NTBTLS
typedef ntbtls_t tls_session_t;
#elif HTTP_USE_GNUTLS
typedef gnutls_session_t tls_session_t;
#else
# error building without TLS is not supported
#endif
static gpg_err_code_t do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls);
static gpg_error_t parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls);
static int remove_escapes (char *string);
static int insert_escapes (char *buffer, const char *string,
const char *special);
static uri_tuple_t parse_tuple (char *string);
static gpg_error_t send_request (ctrl_t ctrl, http_t hd, const char *httphost,
const char *auth,const char *proxy,
const char *srvtag, unsigned int timeout,
strlist_t headers);
static char *build_rel_path (parsed_uri_t uri);
static gpg_error_t parse_response (http_t hd);
static gpg_error_t connect_server (ctrl_t ctrl,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag,
unsigned int timeout, assuan_fd_t *r_sock);
static gpgrt_ssize_t read_server (assuan_fd_t sock, void *buffer, size_t size);
static gpg_error_t write_server (assuan_fd_t sock, const char *data, size_t length);
static gpgrt_ssize_t cookie_read (void *cookie, void *buffer, size_t size);
static gpgrt_ssize_t cookie_write (void *cookie,
const void *buffer, size_t size);
static int cookie_close (void *cookie);
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t simple_cookie_read (void *cookie,
void *buffer, size_t size);
static gpgrt_ssize_t simple_cookie_write (void *cookie,
const void *buffer, size_t size);
#endif
/* A socket object used to a allow ref counting of sockets. */
struct my_socket_s
{
assuan_fd_t fd; /* The actual socket - shall never be ASSUAN_INVALID_FD. */
int refcount; /* Number of references to this socket. */
};
typedef struct my_socket_s *my_socket_t;
/* Cookie function structure and cookie object. */
static es_cookie_io_functions_t cookie_functions =
{
cookie_read,
cookie_write,
NULL,
cookie_close
};
struct cookie_s
{
/* Socket object or NULL if already closed. */
my_socket_t sock;
/* The session object or NULL if not used. */
http_session_t session;
/* True if TLS is to be used. */
int use_tls;
/* Optional malloced buffer holding pending bytes for the read
* function. LEN gives the used length, SIZE the allocated length.
* Used by the up_to_empty_line machinery. */
struct {
size_t size;
size_t len;
char *data;
} pending;
/* The remaining content length and a flag telling whether to use
the content length. */
uint64_t content_length;
unsigned int content_length_valid:1;
/* If the next flag is set the read function will limit the returned
* buffer to an empty line. That is the the pattern "\n\r\n" is
* detected and any further bytes are not returned to the caller.
* The flag is then reset. For technical reason we might have
* already read more which will be then saved for the next call in
* the PENDING buffer. */
unsigned int up_to_empty_line:1;
unsigned int last_was_lf:1; /* Helper to detect empty line. */
unsigned int last_was_lfcr:1; /* Helper to detect empty line. */
};
typedef struct cookie_s *cookie_t;
/* Simple cookie functions. Here the cookie is an int with the
* socket. */
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static es_cookie_io_functions_t simple_cookie_functions =
{
simple_cookie_read,
simple_cookie_write,
NULL,
NULL
};
#endif
enum auth_negotiate_states
{
AUTH_NGT_NONE = 0,
AUTH_NGT_RCVD = 1,
AUTH_NGT_SENT = 2
};
/* An object to store information about a proxy. */
struct proxy_info_s
{
parsed_uri_t uri; /* The parsed proxy URL. */
int is_http_proxy; /* This is an http proxy. */
#ifdef HAVE_W32_SYSTEM
CredHandle cred_handle; /* Credential handle. */
wchar_t *spn; /* Service principal name. */
CtxtHandle ctxt_handle; /* Security context. */
unsigned long token_size; /* Max. length of a token. */
unsigned int cred_handle_valid:1;
unsigned int ctxt_handle_valid:1;
#endif /*HAVE_W32_SYSTEM*/
unsigned char *outtoken; /* The output token allocated with token_size. */
unsigned long outtoklen; /* The current length of the token. */
};
typedef struct proxy_info_s *proxy_info_t;
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_SESSION_MAGIC 0x0068545470534553 /* "hTTpSES" */
#else
# define HTTP_SESSION_MAGIC 0x68547365 /* "hTse" */
#endif
/* The session object. */
struct http_session_s
{
unsigned long magic;
int refcount; /* Number of references to this object. */
tls_session_t tls_session;
struct {
int done; /* Verifciation has been done. */
int rc; /* TLS verification return code. */
unsigned int status; /* Verification status. */
} verify;
char *servername; /* Malloced server name. */
- /* A callback function to log details of TLS certifciates. */
+ /* A callback function to log details of TLS certificates. */
void (*cert_log_cb) (http_session_t, gpg_error_t, const char *,
const void **, size_t *);
/* The flags passed to the session object. */
unsigned int flags;
/* A per-session TLS verification callback. */
http_verify_cb_t verify_cb;
void *verify_cb_value;
/* The connect timeout */
unsigned int connect_timeout;
#ifdef HTTP_USE_GNUTLS
gnutls_certificate_credentials_t certcred;
#endif /*HTTP_USE_GNUTLS*/
};
/* An object to save header lines. */
struct header_s
{
struct header_s *next;
char *value; /* The value of the header (malloced). */
char name[1]; /* The name of the header (canonicalized). */
};
typedef struct header_s *header_t;
#if SIZEOF_UNSIGNED_LONG == 8
# define HTTP_CONTEXT_MAGIC 0x0068545470435458 /* "hTTpCTX" */
#else
# define HTTP_CONTEXT_MAGIC 0x68546378 /* "hTcx" */
#endif
/* Our handle context. */
struct http_context_s
{
unsigned long magic;
unsigned int status_code;
my_socket_t sock;
unsigned int in_data:1;
unsigned int is_http_0_9:1;
unsigned int keep_alive:1; /* Keep the connection alive. */
estream_t fp_read;
estream_t fp_write;
void *write_cookie;
void *read_cookie;
http_session_t session;
parsed_uri_t uri;
http_req_t req_type;
char *buffer; /* Line buffer. */
size_t buffer_size;
unsigned int flags;
header_t headers; /* Received headers. */
};
/* Two flags to enable verbose and debug mode. Although currently not
* set-able a value > 1 for OPT_DEBUG enables debugging of the session
* reference counting. */
static int opt_verbose;
static int opt_debug;
/* The global callback for the verification function for GNUTLS. */
static gpg_error_t (*tls_callback) (http_t, http_session_t, int);
/* The list of files with trusted CA certificates for GNUTLS. */
static strlist_t tls_ca_certlist;
/* The list of files with extra trusted CA certificates for GNUTLS. */
static strlist_t cfg_ca_certlist;
/* The global callback for net activity. */
static void (*netactivity_cb)(void);
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
#if GNUPG_MAJOR_VERSION == 1
#define REQ_WINSOCK_MAJOR 1
#define REQ_WINSOCK_MINOR 1
#else
#define REQ_WINSOCK_MAJOR 2
#define REQ_WINSOCK_MINOR 2
#endif
static void
deinit_sockets (void)
{
WSACleanup();
}
static void
init_sockets (void)
{
static int initialized;
static WSADATA wsdata;
if (initialized)
return;
if ( WSAStartup( MAKEWORD (REQ_WINSOCK_MINOR, REQ_WINSOCK_MAJOR), &wsdata ) )
{
log_error ("error initializing socket library: ec=%d\n",
(int)WSAGetLastError () );
return;
}
if ( LOBYTE(wsdata.wVersion) != REQ_WINSOCK_MAJOR
|| HIBYTE(wsdata.wVersion) != REQ_WINSOCK_MINOR )
{
log_error ("socket library version is %x.%x - but %d.%d needed\n",
LOBYTE(wsdata.wVersion), HIBYTE(wsdata.wVersion),
REQ_WINSOCK_MAJOR, REQ_WINSOCK_MINOR);
WSACleanup();
return;
}
atexit ( deinit_sockets );
initialized = 1;
}
#endif /*HAVE_W32_SYSTEM && !HTTP_NO_WSASTARTUP*/
/* Create a new socket object. Returns NULL and closes FD if not
enough memory is available. */
static my_socket_t
_my_socket_new (int lnr, assuan_fd_t fd)
{
my_socket_t so;
so = xtrymalloc (sizeof *so);
if (!so)
{
int save_errno = errno;
assuan_sock_close (fd);
gpg_err_set_errno (save_errno);
return NULL;
}
so->fd = fd;
so->refcount = 1;
if (opt_debug)
log_debug ("http.c:%d:socket_new: object %p for fd %d created\n",
lnr, so, FD_DBG (so->fd));
return so;
}
#define my_socket_new(a) _my_socket_new (__LINE__, (a))
/* Bump up the reference counter for the socket object SO. */
static my_socket_t
_my_socket_ref (int lnr, my_socket_t so)
{
so->refcount++;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_ref: object %p for fd %d refcount now %d\n",
lnr, so, FD_DBG (so->fd), so->refcount);
return so;
}
#define my_socket_ref(a) _my_socket_ref (__LINE__,(a))
/* Bump down the reference counter for the socket object SO. If SO
has no more references, close the socket and release the
object. */
static void
_my_socket_unref (int lnr, my_socket_t so,
void (*preclose)(void*), void *preclosearg)
{
if (so)
{
so->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:socket_unref: object %p for fd %d ref now %d\n",
lnr, so, FD_DBG (so->fd), so->refcount);
if (!so->refcount)
{
if (preclose)
preclose (preclosearg);
assuan_sock_close (so->fd);
xfree (so);
}
}
}
#define my_socket_unref(a,b,c) _my_socket_unref (__LINE__,(a),(b),(c))
#ifdef HTTP_USE_GNUTLS
static ssize_t
my_gnutls_read (gnutls_transport_ptr_t ptr, void *buffer, size_t size)
{
my_socket_t sock = ptr;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use recv for a socket. */
int nread;
# if USE_NPTH
npth_unprotect ();
# endif
nread = recv (FD2INT (sock->fd), buffer, size, 0);
# if USE_NPTH
npth_protect ();
# endif
return nread;
#else /* !HAVE_W32_SYSTEM */
# if USE_NPTH
return npth_read (sock->fd, buffer, size);
# else
return read (sock->fd, buffer, size);
# endif
#endif
}
static ssize_t
my_gnutls_write (gnutls_transport_ptr_t ptr, const void *buffer, size_t size)
{
my_socket_t sock = ptr;
#ifdef HAVE_W32_SYSTEM
int nwritten;
# if USE_NPTH
npth_unprotect ();
# endif
nwritten = send (FD2INT (sock->fd), buffer, size, 0);
# if USE_NPTH
npth_protect ();
# endif
return nwritten;
#else /*!HAVE_W32_SYSTEM*/
# if USE_NPTH
return npth_write (sock->fd, buffer, size);
# else
return write (sock->fd, buffer, size);
# endif
#endif
}
#endif /*HTTP_USE_GNUTLS*/
#ifdef HTTP_USE_NTBTLS
/* Connect the ntbls callback to our generic callback. */
static gpg_error_t
my_ntbtls_verify_cb (void *opaque, ntbtls_t tls, unsigned int verify_flags)
{
http_t hd = opaque;
(void)verify_flags;
log_assert (hd && hd->session && hd->session->verify_cb);
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
log_assert (hd->session->magic == HTTP_SESSION_MAGIC);
return hd->session->verify_cb (hd->session->verify_cb_value,
hd, hd->session,
(hd->flags | hd->session->flags),
tls);
}
#endif /*HTTP_USE_NTBTLS*/
/* This notification function is called by estream whenever stream is
closed. Its purpose is to mark the closing in the handle so
that a http_close won't accidentally close the estream. The function
http_close removes this notification so that it won't be called if
http_close was used before an es_fclose. */
static void
fp_onclose_notification (estream_t stream, void *opaque)
{
http_t hd = opaque;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
if (hd->fp_read && hd->fp_read == stream)
hd->fp_read = NULL;
else if (hd->fp_write && hd->fp_write == stream)
hd->fp_write = NULL;
}
/*
* Helper function to create an HTTP header with hex encoded data. A
* new buffer is returned. This buffer is the concatenation of the
* string PREFIX, the hex-encoded DATA of length LEN and the string
* SUFFIX. On error NULL is returned and ERRNO set.
*/
static char *
make_header_line (const char *prefix, const char *suffix,
const void *data, size_t len )
{
static unsigned char bintoasc[] =
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
const unsigned char *s = data;
char *buffer, *p;
buffer = xtrymalloc (strlen (prefix) + (len+2)/3*4 + strlen (suffix) + 1);
if (!buffer)
return NULL;
p = stpcpy (buffer, prefix);
for ( ; len >= 3 ; len -= 3, s += 3 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[(((s[1]<<2)&074)|((s[2]>>6)&03))&077];
*p++ = bintoasc[s[2]&077];
*p = 0;
}
if ( len == 2 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(((s[0] <<4)&060)|((s[1] >> 4)&017))&077];
*p++ = bintoasc[((s[1]<<2)&074)];
*p++ = '=';
}
else if ( len == 1 )
{
*p++ = bintoasc[(s[0] >> 2) & 077];
*p++ = bintoasc[(s[0] <<4)&060];
*p++ = '=';
*p++ = '=';
}
*p = 0;
strcpy (p, suffix);
return buffer;
}
/* Set verbosity and debug mode for this module. */
void
http_set_verbose (int verbose, int debug)
{
opt_verbose = verbose;
opt_debug = debug;
}
/* Register a non-standard global TLS callback function. If no
verification is desired a callback needs to be registered which
always returns NULL. Only used for GNUTLS. */
void
http_register_tls_callback (gpg_error_t (*cb)(http_t, http_session_t, int))
{
tls_callback = cb;
}
/* Register a CA certificate for future use. The certificate is
expected to be in FNAME. PEM format is assume if FNAME has a
suffix of ".pem". If FNAME is NULL the list of CA files is
removed. Only used for GNUTLS. */
void
http_register_tls_ca (const char *fname)
{
gpg_err_code_t ec;
strlist_t sl;
if (!fname)
{
free_strlist (tls_ca_certlist);
tls_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if ((ec = gnupg_access (fname, F_OK)))
log_info (_("can't access '%s': %s\n"), fname, gpg_strerror (ec));
sl = add_to_strlist (&tls_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a CA certificate for future use. The certificate is
* expected to be in FNAME. PEM format is assume if FNAME has a
* suffix of ".pem". If FNAME is NULL the list of CA files is
* removed. This is a variant of http_register_tls_ca which puts the
* certificate into a separate list enabled using HTTP_FLAG_TRUST_CFG.
* Only used for GNUTLS. */
void
http_register_cfg_ca (const char *fname)
{
gpg_err_code_t ec;
strlist_t sl;
if (!fname)
{
free_strlist (cfg_ca_certlist);
cfg_ca_certlist = NULL;
}
else
{
/* Warn if we can't access right now, but register it anyway in
case it becomes accessible later */
if ((ec = gnupg_access (fname, F_OK)))
log_info (_("can't access '%s': %s\n"), fname, gpg_strerror (ec));
sl = add_to_strlist (&cfg_ca_certlist, fname);
if (*sl->d && !strcmp (sl->d + strlen (sl->d) - 4, ".pem"))
sl->flags = 1;
}
}
/* Register a callback which is called every time the HTTP mode has
* made a successful connection to some server. */
void
http_register_netactivity_cb (void (*cb)(void))
{
netactivity_cb = cb;
}
/* Call the netactivity callback if any. */
static void
notify_netactivity (void)
{
if (netactivity_cb)
netactivity_cb ();
}
/* Free the TLS session associated with SESS, if any. */
static void
close_tls_session (http_session_t sess)
{
if (sess->tls_session)
{
#if HTTP_USE_NTBTLS
/* FIXME!!
Possibly, ntbtls_get_transport and close those streams.
Somehow get SOCK to call my_socket_unref.
*/
ntbtls_release (sess->tls_session);
#elif HTTP_USE_GNUTLS
my_socket_t sock = gnutls_transport_get_ptr (sess->tls_session);
my_socket_unref (sock, NULL, NULL);
gnutls_deinit (sess->tls_session);
if (sess->certcred)
gnutls_certificate_free_credentials (sess->certcred);
#endif /*HTTP_USE_GNUTLS*/
xfree (sess->servername);
sess->tls_session = NULL;
}
}
/* Release a session. Take care not to release it while it is being
used by a http context object. */
static void
session_unref (int lnr, http_session_t sess)
{
if (!sess)
return;
log_assert (sess->magic == HTTP_SESSION_MAGIC);
sess->refcount--;
if (opt_debug > 1)
log_debug ("http.c:%d:session_unref: sess %p ref now %d\n",
lnr, sess, sess->refcount);
if (sess->refcount)
return;
close_tls_session (sess);
sess->magic = 0xdeadbeef;
xfree (sess);
}
#define http_session_unref(a) session_unref (__LINE__, (a))
void
http_session_release (http_session_t sess)
{
http_session_unref (sess);
}
/* Create a write stream and store it in the fp_write member. Also
* store the tls flag and the session. */
static gpg_error_t
make_fp_write (http_t hd, int use_tls, http_session_t session)
{
cookie_t cookie;
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
return gpg_error_from_syserror ();
cookie->sock = my_socket_ref (hd->sock);
cookie->use_tls = use_tls;
if (session)
cookie->session = http_session_ref (session);
hd->fp_write = es_fopencookie (cookie, "w", cookie_functions);
if (!hd->fp_write)
{
gpg_error_t err = gpg_error_from_syserror ();
my_socket_unref (cookie->sock, NULL, NULL);
if (session)
http_session_unref (cookie->session);
xfree (cookie);
return err;
}
hd->write_cookie = cookie; /* Cookie now owned by FP_WRITE. */
return 0;
}
/* Create a read stream and store it in the fp_read member. Also
* store the tls flag and the session. */
static gpg_error_t
make_fp_read (http_t hd, int use_tls, http_session_t session)
{
cookie_t cookie;
cookie = xtrycalloc (1, sizeof *cookie);
if (!cookie)
return gpg_error_from_syserror ();
cookie->sock = my_socket_ref (hd->sock);
cookie->use_tls = use_tls;
if (session)
cookie->session = http_session_ref (session);
hd->fp_read = es_fopencookie (cookie, "r", cookie_functions);
if (!hd->fp_read)
{
gpg_error_t err = gpg_error_from_syserror ();
my_socket_unref (cookie->sock, NULL, NULL);
if (session)
http_session_unref (cookie->session);
xfree (cookie);
return err;
}
hd->read_cookie = cookie; /* Cookie now owned by FP_READ. */
return 0;
}
/* Create a new session object which is currently used to enable TLS
* support. It may eventually allow reusing existing connections.
* Valid values for FLAGS are:
* HTTP_FLAG_TRUST_DEF - Use the CAs set with http_register_tls_ca
* HTTP_FLAG_TRUST_SYS - Also use the CAs defined by the system
* HTTP_FLAG_TRUST_CFG - Also use CAs set with http_register_cfg_ca
* HTTP_FLAG_NO_CRL - Do not consult CRLs for https.
*/
gpg_error_t
http_session_new (http_session_t *r_session,
const char *intended_hostname, unsigned int flags,
http_verify_cb_t verify_cb, void *verify_cb_value)
{
gpg_error_t err;
http_session_t sess;
*r_session = NULL;
sess = xtrycalloc (1, sizeof *sess);
if (!sess)
return gpg_error_from_syserror ();
sess->magic = HTTP_SESSION_MAGIC;
sess->refcount = 1;
sess->flags = flags;
sess->verify_cb = verify_cb;
sess->verify_cb_value = verify_cb_value;
sess->connect_timeout = 0;
#if HTTP_USE_NTBTLS
{
(void)intended_hostname; /* Not needed because we do not preload
* certificates. */
err = ntbtls_new (&sess->tls_session, NTBTLS_CLIENT);
if (err)
{
log_error ("ntbtls_new failed: %s\n", gpg_strerror (err));
goto leave;
}
}
#elif HTTP_USE_GNUTLS
{
const char *errpos;
int rc;
strlist_t sl;
int add_system_cas = !!(flags & HTTP_FLAG_TRUST_SYS);
int is_hkps_pool;
(void)intended_hostname;
rc = gnutls_certificate_allocate_credentials (&sess->certcred);
if (rc < 0)
{
log_error ("gnutls_certificate_allocate_credentials failed: %s\n",
gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Disabled for 2.3.2 to due problems with the standard hkps pool. */
/* is_hkps_pool = (intended_hostname */
/* && !ascii_strcasecmp (intended_hostname, */
/* get_default_keyserver (1))); */
is_hkps_pool = 0;
/* If we are looking for the hkps pool from sks-keyservers.net,
* then forcefully use its dedicated certificate authority. */
/* Disabled for 2.3.2 because the service had to be shutdown. */
/* if (is_hkps_pool) */
/* { */
/* char *pemname = make_filename_try (gnupg_datadir (), */
/* "sks-keyservers.netCA.pem", NULL); */
/* if (!pemname) */
/* { */
/* err = gpg_error_from_syserror (); */
/* log_error ("setting CA from file '%s' failed: %s\n", */
/* pemname, gpg_strerror (err)); */
/* } */
/* else */
/* { */
/* rc = gnutls_certificate_set_x509_trust_file */
/* (sess->certcred, pemname, GNUTLS_X509_FMT_PEM); */
/* if (rc < 0) */
/* log_info ("setting CA from file '%s' failed: %s\n", */
/* pemname, gnutls_strerror (rc)); */
/* xfree (pemname); */
/* } */
/* */
/* if (is_hkps_pool) */
/* add_system_cas = 0; */
/* } */
/* Add configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_DEF) && !is_hkps_pool)
{
for (sl = tls_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
/* If HKP trust is requested and there are no HKP certificates
* configured, also try the standard system certificates. */
if (!tls_ca_certlist)
add_system_cas = 1;
}
/* Add system certificates to the session. */
if (add_system_cas)
{
static int shown;
rc = gnutls_certificate_set_x509_system_trust (sess->certcred);
if (rc < 0)
log_info ("setting system CAs failed: %s\n", gnutls_strerror (rc));
else if (!shown)
{
shown = 1;
log_info ("number of system provided CAs: %d\n", rc);
}
}
/* Add other configured certificates to the session. */
if ((flags & HTTP_FLAG_TRUST_CFG) && !is_hkps_pool)
{
for (sl = cfg_ca_certlist; sl; sl = sl->next)
{
rc = gnutls_certificate_set_x509_trust_file
(sess->certcred, sl->d,
(sl->flags & 1)? GNUTLS_X509_FMT_PEM : GNUTLS_X509_FMT_DER);
if (rc < 0)
log_info ("setting extra CA from file '%s' failed: %s\n",
sl->d, gnutls_strerror (rc));
}
}
rc = gnutls_init (&sess->tls_session, GNUTLS_CLIENT);
if (rc < 0)
{
log_error ("gnutls_init failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* A new session has the transport ptr set to (void*(-1), we need
it to be NULL. */
gnutls_transport_set_ptr (sess->tls_session, NULL);
rc = gnutls_priority_set_direct (sess->tls_session,
"NORMAL",
&errpos);
if (rc < 0)
{
log_error ("gnutls_priority_set_direct failed at '%s': %s\n",
errpos, gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gnutls_credentials_set (sess->tls_session,
GNUTLS_CRD_CERTIFICATE, sess->certcred);
if (rc < 0)
{
log_error ("gnutls_credentials_set failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
#else /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
{
(void)intended_hostname;
(void)flags;
}
#endif /*!HTTP_USE_GNUTLS && !HTTP_USE_NTBTLS*/
if (opt_debug > 1)
log_debug ("http.c:session_new: sess %p created\n", sess);
err = 0;
leave:
if (err)
http_session_unref (sess);
else
*r_session = sess;
return err;
}
/* Increment the reference count for session SESS. Passing NULL for
SESS is allowed. */
http_session_t
http_session_ref (http_session_t sess)
{
if (sess)
{
sess->refcount++;
if (opt_debug > 1)
log_debug ("http.c:session_ref: sess %p ref now %d\n",
sess, sess->refcount);
}
return sess;
}
void
http_session_set_log_cb (http_session_t sess,
void (*cb)(http_session_t, gpg_error_t,
const char *hostname,
const void **certs, size_t *certlens))
{
sess->cert_log_cb = cb;
}
/* Set the TIMEOUT in milliseconds for the connection's connect
* calls. Using 0 disables the timeout. */
void
http_session_set_timeout (http_session_t sess, unsigned int timeout)
{
sess->connect_timeout = timeout;
}
/* Start a HTTP retrieval and on success store at R_HD a context
pointer for completing the request and to wait for the response.
If HTTPHOST is not NULL it is used for the Host header instead of a
Host header derived from the URL. */
gpg_error_t
http_open (ctrl_t ctrl, http_t *r_hd, http_req_t reqtype, const char *url,
const char *httphost,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session, const char *srvtag, strlist_t headers)
{
gpg_error_t err;
http_t hd;
*r_hd = NULL;
if (!(reqtype == HTTP_REQ_GET || reqtype == HTTP_REQ_POST))
return gpg_err_make (default_errsource, GPG_ERR_INV_ARG);
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = reqtype;
hd->flags = flags;
hd->session = http_session_ref (session);
err = parse_uri (&hd->uri, url, 0, !!(flags & HTTP_FLAG_FORCE_TLS));
if (!err)
err = send_request (ctrl, hd, httphost, auth, proxy, srvtag,
hd->session? hd->session->connect_timeout : 0,
headers);
if (err)
{
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
/* This function is useful to connect to a generic TCP service using
this http abstraction layer. This has the advantage of providing
service tags and an estream interface. TIMEOUT is in milliseconds. */
gpg_error_t
http_raw_connect (ctrl_t ctrl, http_t *r_hd,
const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout)
{
gpg_error_t err = 0;
http_t hd;
*r_hd = NULL;
if ((flags & HTTP_FLAG_FORCE_TOR))
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
{
log_error ("Tor support is not available\n");
return gpg_err_make (default_errsource, GPG_ERR_NOT_IMPLEMENTED);
}
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
/* Create the handle. */
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
return gpg_error_from_syserror ();
hd->magic = HTTP_CONTEXT_MAGIC;
hd->req_type = HTTP_REQ_OPAQUE;
hd->flags = flags;
/* Connect. */
{
assuan_fd_t sock;
err = connect_server (ctrl, server, port,
hd->flags, srvtag, timeout, &sock);
if (err)
{
xfree (hd);
return err;
}
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
xfree (hd);
return err;
}
}
/* Setup estreams for reading and writing. */
err = make_fp_write (hd, 0, NULL);
if (err)
goto leave;
err = make_fp_read (hd, 0, NULL);
if (err)
goto leave;
/* Register close notification to interlock the use of es_fclose in
http_close and in user code. */
err = es_onclose (hd->fp_write, 1, fp_onclose_notification, hd);
if (!err)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
leave:
if (err)
{
if (hd->fp_read)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
my_socket_unref (hd->sock, NULL, NULL);
xfree (hd);
}
else
*r_hd = hd;
return err;
}
void
http_start_data (http_t hd)
{
if (!hd->in_data)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug ("http.c:request-header:start_data:\n");
es_fputs ("\r\n", hd->fp_write);
es_fflush (hd->fp_write);
hd->in_data = 1;
}
else
es_fflush (hd->fp_write);
}
gpg_error_t
http_wait_response (http_t hd)
{
gpg_error_t err;
cookie_t cookie;
int use_tls;
int newfpread;
/* Make sure that we are in the data. */
http_start_data (hd);
/* Close the write stream. Note that the reference counted socket
object keeps the actual system socket open. */
cookie = hd->write_cookie;
if (!cookie)
return gpg_err_make (default_errsource, GPG_ERR_INTERNAL);
use_tls = cookie->use_tls;
if (!hd->keep_alive)
{
es_fclose (hd->fp_write);
hd->fp_write = NULL;
/* The close has released the cookie and thus we better set it
* to NULL. */
hd->write_cookie = NULL;
}
/* Shutdown one end of the socket is desired. As per HTTP/1.0 this
is not required but some very old servers (e.g. the original pksd
keyserver didn't worked without it. */
if (!hd->keep_alive && (hd->flags & HTTP_FLAG_SHUTDOWN))
shutdown (FD2INT (hd->sock->fd), 1);
hd->in_data = 0;
/* Create a new cookie and a stream for reading. */
newfpread = 0;
if (!hd->keep_alive || !hd->fp_read)
{
err = make_fp_read (hd, use_tls, hd->session);
if (err)
return err;
newfpread = 1;
((cookie_t)(hd->read_cookie))->up_to_empty_line = 1;
}
err = parse_response (hd);
if (!err && newfpread)
err = es_onclose (hd->fp_read, 1, fp_onclose_notification, hd);
return err;
}
/* Convenience function to send a request and wait for the response.
Closes the handle on error. If PROXY is not NULL, this value will
be used as an HTTP proxy and any enabled $http_proxy gets
ignored. */
gpg_error_t
http_open_document (ctrl_t ctrl, http_t *r_hd, const char *document,
const char *auth, unsigned int flags, const char *proxy,
http_session_t session,
const char *srvtag, strlist_t headers)
{
gpg_error_t err;
err = http_open (ctrl, r_hd, HTTP_REQ_GET, document, NULL, auth, flags,
proxy, session, srvtag, headers);
if (err)
return err;
err = http_wait_response (*r_hd);
if (err)
http_close (*r_hd, 0);
return err;
}
void
http_close (http_t hd, int keep_read_stream)
{
if (!hd)
return;
log_assert (hd->magic == HTTP_CONTEXT_MAGIC);
/* First remove the close notifications for the streams. */
if (hd->fp_read)
es_onclose (hd->fp_read, 0, fp_onclose_notification, hd);
if (hd->fp_write)
es_onclose (hd->fp_write, 0, fp_onclose_notification, hd);
/* Now we can close the streams. */
my_socket_unref (hd->sock, NULL, NULL);
if (hd->fp_read && !keep_read_stream)
es_fclose (hd->fp_read);
if (hd->fp_write)
es_fclose (hd->fp_write);
http_session_unref (hd->session);
hd->magic = 0xdeadbeef;
http_release_parsed_uri (hd->uri);
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
xfree (hd->buffer);
xfree (hd);
}
estream_t
http_get_read_ptr (http_t hd)
{
return hd?hd->fp_read:NULL;
}
estream_t
http_get_write_ptr (http_t hd)
{
return hd?hd->fp_write:NULL;
}
unsigned int
http_get_status_code (http_t hd)
{
return hd?hd->status_code:0;
}
/* Return information pertaining to TLS. If TLS is not in use for HD,
NULL is returned. WHAT is used ask for specific information:
(NULL) := Only check whether TLS is in use. Returns an
unspecified string if TLS is in use. That string may
even be the empty string.
*/
const char *
http_get_tls_info (http_t hd, const char *what)
{
(void)what;
if (!hd)
return NULL;
return hd->uri->use_tls? "":NULL;
}
static gpg_error_t
parse_uri (parsed_uri_t *ret_uri, const char *uri,
int no_scheme_check, int force_tls)
{
gpg_err_code_t ec;
*ret_uri = xtrycalloc (1, sizeof **ret_uri + 2 * strlen (uri) + 1);
if (!*ret_uri)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
strcpy ((*ret_uri)->buffer, uri);
strcpy ((*ret_uri)->buffer + strlen (uri) + 1, uri);
(*ret_uri)->original = (*ret_uri)->buffer + strlen (uri) + 1;
ec = do_parse_uri (*ret_uri, 0, no_scheme_check, force_tls);
if (ec)
{
http_release_parsed_uri (*ret_uri);
*ret_uri = NULL;
}
return gpg_err_make (default_errsource, ec);
}
/*
* Parse an URI and put the result into the newly allocated RET_URI.
* On success the caller must use http_release_parsed_uri() to
* releases the resources. If the HTTP_PARSE_NO_SCHEME_CHECK flag is
* set, the function tries to parse the URL in the same way it would
* do for an HTTP style URI. */
gpg_error_t
http_parse_uri (parsed_uri_t *ret_uri, const char *uri,
unsigned int flags)
{
return parse_uri (ret_uri, uri, !!(flags & HTTP_PARSE_NO_SCHEME_CHECK), 0);
}
void
http_release_parsed_uri (parsed_uri_t uri)
{
if (uri)
{
uri_tuple_t r, r2;
for (r = uri->params; r; r = r2)
{
r2 = r->next;
xfree (r);
}
for (r = uri->query; r; r = r2)
{
r2 = r->next;
xfree (r);
}
xfree (uri);
}
}
static gpg_err_code_t
do_parse_uri (parsed_uri_t uri, int only_local_part,
int no_scheme_check, int force_tls)
{
uri_tuple_t *tail;
char *p, *p2, *p3, *pp;
int n;
p = uri->buffer;
n = strlen (uri->buffer);
/* Initialize all fields to an empty string or an empty list. */
uri->scheme = uri->host = uri->path = p + n;
uri->port = 0;
uri->params = uri->query = NULL;
uri->use_tls = 0;
uri->is_http = 0;
uri->is_ldap = 0;
uri->opaque = 0;
uri->v6lit = 0;
uri->onion = 0;
uri->explicit_port = 0;
uri->off_host = 0;
uri->off_path = 0;
/* A quick validity check unless we have the opaque scheme. */
if (strspn (p, VALID_URI_CHARS) != n
&& strncmp (p, "opaque:", 7))
return GPG_ERR_BAD_URI; /* Invalid characters found. */
if (!only_local_part)
{
/* Find the scheme. */
if (!(p2 = strchr (p, ':')) || p2 == p)
return GPG_ERR_BAD_URI; /* No scheme. */
*p2++ = 0;
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
uri->scheme = p;
if (!strcmp (uri->scheme, "http") && !force_tls)
{
uri->port = 80;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "hkp") && !force_tls)
{
uri->port = 11371;
uri->is_http = 1;
}
else if (!strcmp (uri->scheme, "https") || !strcmp (uri->scheme,"hkps")
|| (force_tls && (!strcmp (uri->scheme, "http")
|| !strcmp (uri->scheme,"hkp"))))
{
uri->port = 443;
uri->is_http = 1;
uri->use_tls = 1;
}
else if (!strcmp (uri->scheme, "opaque"))
{
uri->opaque = 1;
uri->path = p2;
return 0;
}
else if (!no_scheme_check)
return GPG_ERR_INV_URI; /* Not an http style scheme. */
else if (!strcmp (uri->scheme, "ldap") && !force_tls)
{
uri->port = 389;
uri->is_ldap = 1;
}
else if (!strcmp (uri->scheme, "ldaps")
|| (force_tls && (!strcmp (uri->scheme, "ldap"))))
{
uri->port = 636;
uri->is_ldap = 1;
uri->use_tls = 1;
}
else if (!strcmp (uri->scheme, "ldapi")) /* LDAP via IPC. */
{
uri->port = 0;
uri->is_ldap = 1;
}
p = p2;
if (*p == '/' && p[1] == '/' ) /* There seems to be a hostname. */
{
p += 2;
if ((p2 = strchr (p, '/')))
{
if (p2 - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = p2 - uri->buffer;
*p2++ = 0;
}
else
{
n = (p - uri->buffer) + strlen (p);
if (n > 10000)
return GPG_ERR_BAD_URI;
uri->off_path = n;
}
/* Check for username/password encoding */
if ((p3 = strchr (p, '@')))
{
uri->auth = p;
*p3++ = '\0';
p = p3;
}
for (pp=p; *pp; pp++)
*pp = tolower (*(unsigned char*)pp);
/* Handle an IPv6 literal */
if( *p == '[' && (p3=strchr( p, ']' )) )
{
*p3++ = '\0';
/* worst case, uri->host should have length 0, points to \0 */
uri->host = p + 1;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = (p + 1) - uri->buffer;
uri->v6lit = 1;
p = p3;
}
else
{
uri->host = p;
if (p - uri->buffer > 10000)
return GPG_ERR_BAD_URI;
uri->off_host = p - uri->buffer;
}
if ((p3 = strchr (p, ':')))
{
*p3++ = '\0';
uri->port = atoi (p3);
uri->explicit_port = 1;
}
if ((n = remove_escapes (uri->host)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (uri->host))
return GPG_ERR_BAD_URI; /* Hostname includes a Nul. */
p = p2 ? p2 : NULL;
}
else if (!no_scheme_check && (uri->is_http || uri->is_ldap))
return GPG_ERR_INV_URI; /* HTTP or LDAP w/o leading double slash. */
else
{
uri->opaque = 1;
uri->path = p;
if (is_onion_address (uri->path))
uri->onion = 1;
return 0;
}
} /* End global URI part. */
/* Parse the pathname part if any. */
if (p && *p)
{
/* TODO: Here we have to check params. */
/* Do we have a query part? */
if ((p2 = strchr (p, '?')))
*p2++ = 0;
uri->path = p;
if ((n = remove_escapes (p)) < 0)
return GPG_ERR_BAD_URI;
if (n != strlen (p))
return GPG_ERR_BAD_URI; /* Path includes a Nul. */
p = p2 ? p2 : NULL;
/* Parse a query string if any. */
if (p && *p)
{
tail = &uri->query;
for (;;)
{
uri_tuple_t elem;
if ((p2 = strchr (p, '&')))
*p2++ = 0;
if (!(elem = parse_tuple (p)))
return GPG_ERR_BAD_URI;
*tail = elem;
tail = &elem->next;
if (!p2)
break; /* Ready. */
p = p2;
}
}
}
if (is_onion_address (uri->host))
uri->onion = 1;
return 0;
}
/*
* Remove all %xx escapes; this is done in-place. Returns: New length
* of the string.
*/
static int
remove_escapes (char *string)
{
int n = 0;
unsigned char *p, *s;
for (p = s = (unsigned char*)string; *s; s++)
{
if (*s == '%')
{
if (s[1] && s[2] && isxdigit (s[1]) && isxdigit (s[2]))
{
s++;
*p = *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
*p <<= 4;
s++;
*p |= *s >= '0' && *s <= '9' ? *s - '0' :
*s >= 'A' && *s <= 'F' ? *s - 'A' + 10 : *s - 'a' + 10;
p++;
n++;
}
else
{
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p++ = *s++;
if (*s)
*p = 0;
return -1; /* Bad URI. */
}
}
else
{
*p++ = *s;
n++;
}
}
*p = 0; /* Make sure to keep a string terminator. */
return n;
}
/* If SPECIAL is NULL this function escapes in forms mode. */
static size_t
escape_data (char *buffer, const void *data, size_t datalen,
const char *special)
{
int forms = !special;
const unsigned char *s;
size_t n = 0;
if (forms)
special = "%;?&=";
for (s = data; datalen; s++, datalen--)
{
if (forms && *s == ' ')
{
if (buffer)
*buffer++ = '+';
n++;
}
else if (forms && *s == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
}
else if (forms && *s == '\r' && datalen > 1 && s[1] == '\n')
{
if (buffer)
memcpy (buffer, "%0D%0A", 6);
n += 6;
s++;
datalen--;
}
else if (strchr (VALID_URI_CHARS, *s) && !strchr (special, *s))
{
if (buffer)
*(unsigned char*)buffer++ = *s;
n++;
}
else
{
if (buffer)
{
snprintf (buffer, 4, "%%%02X", *s);
buffer += 3;
}
n += 3;
}
}
return n;
}
static int
insert_escapes (char *buffer, const char *string,
const char *special)
{
return escape_data (buffer, string, strlen (string), special);
}
/* Allocate a new string from STRING using standard HTTP escaping as
well as escaping of characters given in SPECIALS. A common pattern
for SPECIALS is "%;?&=". However it depends on the needs, for
example "+" and "/: often needs to be escaped too. Returns NULL on
failure and sets ERRNO. If SPECIAL is NULL a dedicated forms
encoding mode is used. */
char *
http_escape_string (const char *string, const char *specials)
{
int n;
char *buf;
n = insert_escapes (NULL, string, specials);
buf = xtrymalloc (n+1);
if (buf)
{
insert_escapes (buf, string, specials);
buf[n] = 0;
}
return buf;
}
/* Allocate a new string from {DATA,DATALEN} using standard HTTP
escaping as well as escaping of characters given in SPECIALS. A
common pattern for SPECIALS is "%;?&=". However it depends on the
needs, for example "+" and "/: often needs to be escaped too.
Returns NULL on failure and sets ERRNO. If SPECIAL is NULL a
dedicated forms encoding mode is used. */
char *
http_escape_data (const void *data, size_t datalen, const char *specials)
{
int n;
char *buf;
n = escape_data (NULL, data, datalen, specials);
buf = xtrymalloc (n+1);
if (buf)
{
escape_data (buf, data, datalen, specials);
buf[n] = 0;
}
return buf;
}
static uri_tuple_t
parse_tuple (char *string)
{
char *p = string;
char *p2;
int n;
uri_tuple_t tuple;
if ((p2 = strchr (p, '=')))
*p2++ = 0;
if ((n = remove_escapes (p)) < 0)
return NULL; /* Bad URI. */
if (n != strlen (p))
return NULL; /* Name with a Nul in it. */
tuple = xtrycalloc (1, sizeof *tuple);
if (!tuple)
return NULL; /* Out of core. */
tuple->name = p;
if (!p2) /* We have only the name, so we assume an empty value string. */
{
tuple->value = p + strlen (p);
tuple->valuelen = 0;
tuple->no_value = 1; /* Explicitly mark that we have seen no '='. */
}
else /* Name and value. */
{
if ((n = remove_escapes (p2)) < 0)
{
xfree (tuple);
return NULL; /* Bad URI. */
}
tuple->value = p2;
tuple->valuelen = n;
}
return tuple;
}
/* Return true if STRING is likely "hostname:port" or only "hostname". */
static int
is_hostname_port (const char *string)
{
int colons = 0;
if (!string || !*string)
return 0;
for (; *string; string++)
{
if (*string == ':')
{
if (colons)
return 0;
if (!string[1])
return 0;
colons++;
}
else if (!colons && strchr (" \t\f\n\v_@[]/", *string))
return 0; /* Invalid characters in hostname. */
else if (colons && !digitp (string))
return 0; /* Not a digit in the port. */
}
return 1;
}
/* Free the PROXY object. */
static void
release_proxy_info (proxy_info_t proxy)
{
if (!proxy)
return;
http_release_parsed_uri (proxy->uri);
xfree (proxy->outtoken);
#ifdef HAVE_W32_SYSTEM
if (proxy->ctxt_handle_valid)
DeleteSecurityContext (&proxy->ctxt_handle);
if (proxy->cred_handle_valid)
FreeCredentialsHandle (&proxy->cred_handle);
#endif
xfree (proxy);
}
/* Return an http session object. If clear is set, the object is
* destroyed. On error nULL is returned. */
#ifdef HAVE_W32_SYSTEM
static HINTERNET
w32_get_internet_session (int clear)
{
static HINTERNET session;
if (clear)
{
if (session)
{
WinHttpCloseHandle (session);
session = NULL;
}
return NULL;
}
if (!session)
{
session = WinHttpOpen (L"GnuPG dirmngr",
WINHTTP_ACCESS_TYPE_NO_PROXY,
WINHTTP_NO_PROXY_NAME,
WINHTTP_NO_PROXY_BYPASS,
0);
if (!session)
{
log_error ("WinHttpOpen failed: %s\n", w32_strerror (-1));
return NULL;
}
}
return session;
}
#endif /*HAVE_W32_SYSTEM*/
/* Return a proxy using a Windows API. */
#ifdef HAVE_W32_SYSTEM
static char *
w32_get_proxy (const char *url)
{
WINHTTP_AUTOPROXY_OPTIONS options = {0};
WINHTTP_PROXY_INFO info;
char *result = NULL;
char *p;
wchar_t *wurl;
int defaultcfg = 0;
wurl = utf8_to_wchar (url);
if (!wurl)
{
log_error ("utf8_to_wchar failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return NULL;
}
options.dwFlags = (WINHTTP_AUTOPROXY_ALLOW_AUTOCONFIG
| WINHTTP_AUTOPROXY_ALLOW_CM
| WINHTTP_AUTOPROXY_ALLOW_STATIC
| WINHTTP_AUTOPROXY_AUTO_DETECT
| WINHTTP_AUTOPROXY_SORT_RESULTS);
options.dwAutoDetectFlags = (WINHTTP_AUTO_DETECT_TYPE_DHCP
| WINHTTP_AUTO_DETECT_TYPE_DNS_A);
options.fAutoLogonIfChallenged = TRUE;
if (opt_debug)
log_debug ("calling WinHttpGetProxyForUrl (%s)\n", url);
if (!WinHttpGetProxyForUrl (w32_get_internet_session (0),
wurl, &options, &info))
{
int ec = (int)GetLastError ();
if (ec == ERROR_WINHTTP_AUTODETECTION_FAILED)
{
if (opt_debug)
log_debug ("calling WinHttpGetDefaultProxyConfiguration\n");
if (!WinHttpGetDefaultProxyConfiguration (&info))
{
if (opt_verbose)
log_info ("WinHttpGetDefaultProxyConfiguration failed: "
"%s (%d)\n", w32_strerror (ec), ec);
xfree (wurl);
return NULL;
}
defaultcfg = 1;
}
else
{
if (opt_verbose)
log_info ("WinHttpGetProxyForUrl failed: %s (%d)\n",
w32_strerror (ec), ec);
xfree (wurl);
return NULL;
}
}
xfree (wurl);
if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NAMED_PROXY)
{
result = wchar_to_utf8 (info.lpszProxy);
if (!result)
log_error ("wchar_to_utf8 failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
else
{
if (opt_debug)
log_debug ("proxies to use: '%s'\n", result);
/* The returned proxies are delimited by whitespace or
* semicolons. We return only the first proxy. */
for (p=result; *p; p++)
if (spacep (p) || *p == ';')
{
*p = 0;
break;
}
}
}
else if (info.dwAccessType == WINHTTP_ACCESS_TYPE_NO_PROXY)
{
/* No proxy shall be used. */
}
else
log_error ("%s returned unexpected code %lu\n",
defaultcfg? "WinHttpGetDefaultProxyConfiguration"
:"WinHttpGetProxyForUrl", info.dwAccessType);
if (info.lpszProxy)
GlobalFree (info.lpszProxy);
if (info.lpszProxyBypass)
GlobalFree (info.lpszProxyBypass);
return result;
}
#endif /*HAVE_W32_SYSTEM*/
/* Return the proxy to be used for the URL or host specified in HD.
* If OVERRIDE_PROXY is not NULL and not empty, this proxy will be
* used instead of any configured or dynamically determined proxy. If
* the function runs into an error an error code is returned and NULL
- * is stored at R_PROXY. If the fucntion was successful and a proxy
+ * is stored at R_PROXY. If the function was successful and a proxy
* is to be used, information on the procy is stored at R_PROXY; if no
* proxy shall be used R_PROXY is set to NULL. Caller should always
* use release_proxy_info on the value stored at R_PROXY. */
static gpg_error_t
get_proxy_for_url (http_t hd, const char *override_proxy, proxy_info_t *r_proxy)
{
gpg_error_t err = 0;
const char *proxystr, *s;
proxy_info_t proxy;
#ifdef HAVE_W32_SYSTEM
char *proxystrbuf = NULL;
#endif
*r_proxy = NULL;
if (override_proxy && *override_proxy)
proxystr = override_proxy;
else if (!(hd->flags & HTTP_FLAG_TRY_PROXY))
return 0; /* --honor-http-proxy not active */
else if ((s = getenv (HTTP_PROXY_ENV)) && *s)
proxystr = s;
#ifdef HAVE_W32_SYSTEM
else if (hd->uri && hd->uri->original
&& (proxystrbuf = w32_get_proxy (hd->uri->original)))
proxystr = proxystrbuf;
#endif
else
return 0; /* No proxy known. */
proxy = xtrycalloc (1, sizeof *proxy);
if (!proxy)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory for proxy\n");
goto leave;
}
err = parse_uri (&proxy->uri, proxystr, 0, 0);
if (gpg_err_code (err) == GPG_ERR_INV_URI
&& is_hostname_port (proxystr))
{
/* Retry assuming a "hostname:port" string. */
char *tmpname = strconcat ("http://", proxystr, NULL);
if (!tmpname)
err = gpg_error_from_syserror ();
else if (!parse_uri (&proxy->uri, tmpname, 0, 0))
err = 0;
xfree (tmpname);
}
if (!err)
{
/* Get rid of the escapes in the authstring. */
if (proxy->uri->auth)
remove_escapes (proxy->uri->auth);
if (!strcmp (proxy->uri->scheme, "http"))
proxy->is_http_proxy = 1;
else if (!strcmp (proxy->uri->scheme, "socks4")
|| !strcmp (proxy->uri->scheme, "socks5h"))
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
else
err = gpg_error (GPG_ERR_INV_URI);
if (err)
{
log_error ("invalid HTTP proxy (%s): %s\n",
proxystr, gpg_strerror (err));
err = gpg_err_make (default_errsource, GPG_ERR_CONFIGURATION);
}
else if (opt_verbose)
log_info ("using '%s' to proxy '%s'\n",
proxystr, hd->uri? hd->uri->original : NULL);
}
leave:
#ifdef HAVE_W32_SYSTEM
xfree (proxystrbuf);
#endif
if (err)
xfree (proxy);
else
*r_proxy = proxy;
return err;
}
/* Some checks done by send_request. */
static gpg_error_t
send_request_basic_checks (http_t hd)
{
int mode;
if (hd->uri->use_tls && !hd->session)
{
log_error ("TLS requested but no session object provided\n");
return gpg_error (GPG_ERR_INTERNAL);
}
if (hd->uri->use_tls && !hd->session->tls_session)
{
log_error ("TLS requested but no TLS context available\n");
return gpg_error (GPG_ERR_INTERNAL);
}
if (opt_debug)
log_debug ("Using TLS library: %s %s\n",
#if HTTP_USE_NTBTLS
"NTBTLS", ntbtls_check_version (NULL)
#elif HTTP_USE_GNUTLS
"GNUTLS", gnutls_check_version (NULL)
#endif /*HTTP_USE_GNUTLS*/
);
if ((hd->flags & HTTP_FLAG_FORCE_TOR)
&& (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode))
{
log_error ("Tor support is not available\n");
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
return 0;
}
/* Helper for send_request to set the servername. */
static gpg_error_t
send_request_set_sni (http_t hd, const char *name)
{
gpg_error_t err = 0;
# if HTTP_USE_GNUTLS
int rc;
# endif
/* Try to use SNI. */
if (hd->uri->use_tls)
{
xfree (hd->session->servername);
hd->session->servername = xtrystrdup (name);
if (!hd->session->servername)
{
err = gpg_error_from_syserror ();
goto leave;
}
#if HTTP_USE_NTBTLS
err = ntbtls_set_hostname (hd->session->tls_session,
hd->session->servername);
if (err)
{
log_info ("ntbtls_set_hostname failed: %s\n", gpg_strerror (err));
goto leave;
}
#elif HTTP_USE_GNUTLS
rc = gnutls_server_name_set (hd->session->tls_session,
GNUTLS_NAME_DNS,
hd->session->servername,
strlen (hd->session->servername));
if (rc < 0)
log_info ("gnutls_server_name_set failed: %s\n", gnutls_strerror (rc));
#endif /*HTTP_USE_GNUTLS*/
}
leave:
return err;
}
/* Run the NTBTLS handshake if needed. */
#if HTTP_USE_NTBTLS
static gpg_error_t
run_ntbtls_handshake (http_t hd)
{
gpg_error_t err;
estream_t in, out;
if (hd->uri->use_tls)
{
my_socket_ref (hd->sock);
/* Until we support send/recv in estream under Windows we need
* to use es_fopencookie. */
# ifdef HAVE_W32_SYSTEM
in = es_fopencookie (hd->sock->fd, "rb",
simple_cookie_functions);
# else
in = es_fdopen_nc (hd->sock->fd, "rb");
# endif
if (!in)
{
err = gpg_error_from_syserror ();
goto leave;
}
# ifdef HAVE_W32_SYSTEM
out = es_fopencookie (hd->sock->fd, "wb",
simple_cookie_functions);
# else
out = es_fdopen_nc (hd->sock->fd, "wb");
# endif
if (!out)
{
err = gpg_error_from_syserror ();
es_fclose (in);
goto leave;
}
err = ntbtls_set_transport (hd->session->tls_session, in, out);
if (err)
{
log_info ("TLS set_transport failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
es_fclose (in);
es_fclose (out);
goto leave;
}
if (hd->session->verify_cb)
{
err = ntbtls_set_verify_cb (hd->session->tls_session,
my_ntbtls_verify_cb, hd);
if (err)
{
log_error ("ntbtls_set_verify_cb failed: %s\n",
gpg_strerror (err));
goto leave;
}
}
while ((err = ntbtls_handshake (hd->session->tls_session)))
{
unsigned int tlevel, ttype;
const char *s;
s = ntbtls_get_last_alert (hd->session->tls_session, &tlevel, &ttype);
if (s)
log_info ("TLS alert: %s (%u.%u)\n", s, tlevel, ttype);
switch (err)
{
default:
log_info ("TLS handshake failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
goto leave;
}
}
hd->session->verify.done = 0;
/* Note that in contrast to GNUTLS NTBTLS uses a registered
* callback to run the verification as part of the handshake. */
err = 0;
/* FIXME: We could check that the CB has been called and if not
* error out with this warning:
* if (err)
* {
* log_info ("TLS connection authentication failed: %s <%s>\n",
* gpg_strerror (err), gpg_strsource (err));
* goto leave;
* }
*/
}
else
err = 0;
leave:
return err;
}
#endif /*HTTP_USE_NTBTLS*/
/* Run the GNUTLS handshake if needed. */
#if HTTP_USE_GNUTLS
static gpg_error_t
run_gnutls_handshake (http_t hd, const char *server)
{
gpg_error_t err;
int rc;
if (hd->uri->use_tls)
{
my_socket_ref (hd->sock);
gnutls_transport_set_ptr (hd->session->tls_session, hd->sock);
gnutls_transport_set_pull_function (hd->session->tls_session,
my_gnutls_read);
gnutls_transport_set_push_function (hd->session->tls_session,
my_gnutls_write);
handshake_again:
do
{
rc = gnutls_handshake (hd->session->tls_session);
}
while (rc == GNUTLS_E_INTERRUPTED || rc == GNUTLS_E_AGAIN);
if (rc < 0)
{
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED
|| rc == GNUTLS_E_FATAL_ALERT_RECEIVED)
{
gnutls_alert_description_t alertno;
const char *alertstr;
alertno = gnutls_alert_get (hd->session->tls_session);
alertstr = gnutls_alert_get_name (alertno);
log_info ("TLS handshake %s: %s (alert %d)\n",
rc == GNUTLS_E_WARNING_ALERT_RECEIVED
? "warning" : "failed",
alertstr, (int)alertno);
if (alertno == GNUTLS_A_UNRECOGNIZED_NAME && server)
log_info (" (sent server name '%s')\n", server);
if (rc == GNUTLS_E_WARNING_ALERT_RECEIVED)
goto handshake_again;
}
else
log_info ("TLS handshake failed: %s\n", gnutls_strerror (rc));
err = gpg_error (GPG_ERR_NETWORK);
goto leave;
}
hd->session->verify.done = 0;
if (tls_callback)
err = tls_callback (hd, hd->session, 0);
else
err = http_verify_server_credentials (hd->session);
if (err)
{
log_info ("TLS connection authentication failed: %s\n",
gpg_strerror (err));
goto leave;
}
}
else
err =0;
leave:
return err;
}
#endif /*HTTP_USE_GNUTLS*/
-/* It INPUTSTRING is NULL get the intial token. If INPUTSTRING is not
- * NULL, decode the string and use this as input from teh server. On
+/* It INPUTSTRING is NULL get the initial token. If INPUTSTRING is not
+ * NULL, decode the string and use this as input from the server. On
* success the final output token is stored at PROXY->OUTTOKEN and
* OUTTOKLEN. IF the authentication succeeded OUTTOKLEN is zero. */
static gpg_error_t
proxy_get_token (proxy_info_t proxy, const char *inputstring)
{
#ifdef HAVE_W32_SYSTEM
gpg_error_t err;
int rc;
SecBuffer chlg_buf; /* challenge buffer */
SecBufferDesc chlg_desc; /* challenge descriptor */
SecBuffer resp_buf; /* response buffer */
SecBufferDesc resp_desc; /* response descriptor */
unsigned long attrs;
TimeStamp expiry; /* (value not used) */
void *intoken = NULL;
size_t intoklen;
if (inputstring)
{
- /* The input is expected in the token parameter but the paremter
+ /* The input is expected in the token parameter but the parameter
* name is often forgotten. Thus we simply detect the parameter
* name and skip it, assuming no other parameters are given. */
if (!strncmp (inputstring, "token=", 6))
inputstring += 6;
err = b64decode (inputstring, NULL, &intoken, &intoklen);
/* Just to be safe that we don't overflow an ulong we check the
* actual size against an arbitrary limit. */
if (!err && intoklen > 65535)
err = gpg_error (GPG_ERR_ERANGE);
if (err || !intoklen)
{
log_error ("error decoding received auth token: %s\n",
err? gpg_strerror (err):"empty challenge token received");
if (!err)
err = gpg_error (GPG_ERR_BAD_AUTH);
goto leave;
}
}
if (!proxy->spn)
{
char *buffer = strconcat ("HTTP/", (*proxy->uri->host
?proxy->uri->host:"localhost"), NULL);
if (!buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt_debug)
log_debug ("http.c:proxy_connect: using '%s' as SPN\n", buffer);
proxy->spn = utf8_to_wchar (buffer);
xfree (buffer);
if (!proxy->spn)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
if (!proxy->token_size || !proxy->outtoken) /* Not yet initialized. */
{
PSecPkgInfoW pinfo;
rc = QuerySecurityPackageInfoW (NEGOSSP_NAME_W, &pinfo);
if (rc)
{
log_error ("QSPI(Negotiate) failed: %s (%d)\n",
w32_strerror (rc), rc);
err = gpg_error (GPG_ERR_BAD_AUTH);
goto leave;
}
proxy->token_size = pinfo->cbMaxToken;
FreeContextBuffer (pinfo);
proxy->outtoken = xtrymalloc (proxy->token_size);
if (!proxy->outtoken)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
if (!proxy->cred_handle_valid)
{
rc = AcquireCredentialsHandleW (NULL, NEGOSSP_NAME_W,
SECPKG_CRED_OUTBOUND, NULL,
NULL, /* Current user */
NULL, /* reserved */
NULL, /* reserved */
&proxy->cred_handle,
NULL /* expiry */);
if (rc)
{
log_error ("ACH(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc);
err = gpg_error (GPG_ERR_NO_AUTH);
goto leave;
}
proxy->cred_handle_valid = 1;
}
/* Now generate our challenge-response message. */
if (intoken)
{
chlg_buf.BufferType = SECBUFFER_TOKEN;
chlg_buf.pvBuffer = intoken;
chlg_buf.cbBuffer = intoklen;
chlg_desc.ulVersion = SECBUFFER_VERSION;
chlg_desc.cBuffers = 1;
chlg_desc.pBuffers = &chlg_buf;
}
resp_buf.BufferType = SECBUFFER_TOKEN;
resp_buf.pvBuffer = proxy->outtoken;
resp_buf.cbBuffer = proxy->token_size;
resp_desc.ulVersion = SECBUFFER_VERSION;
resp_desc.cBuffers = 1;
resp_desc.pBuffers = &resp_buf;
rc = InitializeSecurityContextW (&proxy->cred_handle,
(intoken && proxy->ctxt_handle_valid)
? &proxy->ctxt_handle : NULL,
proxy->spn, /* service principal name */
ISC_REQ_CONFIDENTIALITY,
0, /* reserved */
SECURITY_NATIVE_DREP,
intoken? &chlg_desc : NULL,
0, /* reserved */
&proxy->ctxt_handle, /* new context */
&resp_desc, /* the output. */
&attrs, /* attribs of the context. */
&expiry);
switch (rc)
{
case SEC_E_OK: /* All done and no more ISC expected. */
break;
case SEC_I_COMPLETE_AND_CONTINUE: /* Need to call CompleteAuthToken. */
case SEC_I_COMPLETE_NEEDED:
rc = CompleteAuthToken (&proxy->ctxt_handle, &resp_desc);
log_error ("CompleteAuthToken failed: %s (%d)\n", w32_strerror (rc), rc);
err = gpg_error (GPG_ERR_NO_AUTH);
goto leave;
break;
case SEC_I_CONTINUE_NEEDED: /* Send the new token to the client. */
break;
default:
log_error ("ISC(Negotiate) failed: %s (%d)\n", w32_strerror (rc), rc);
err = gpg_error (GPG_ERR_NO_AUTH);
goto leave;
}
proxy->outtoklen = resp_buf.cbBuffer;
proxy->ctxt_handle_valid = 1;
err = 0;
leave:
xfree (intoken);
return err;
#else /*!HAVE_W32_SYSTEM*/
(void)proxy;
(void)inputstring;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif /*!HAVE_W32_SYSTEM*/
}
/* Use the CONNECT method to proxy our TLS stream. */
static gpg_error_t
run_proxy_connect (http_t hd, proxy_info_t proxy,
const char *httphost, const char *server,
unsigned short port)
{
gpg_error_t err;
int saved_flags = hd->flags;
char *authhdr = NULL;
char *request = NULL;
char *tmpstr = NULL;
const char *s, *parms;
unsigned int idx;
int auth_basic = 0;
enum auth_negotiate_states authstate = 0;
unsigned int authpasses = 0;
/* Authentication methods implemented here:
* RFC-2617 - HTTP Authentication: Basic and Digest Access Authentication
* RFC-4559 - SPNEGO-based Kerberos and NTLM HTTP Authentication
*/
auth_basic = !!proxy->uri->auth;
hd->keep_alive = !auth_basic; /* We may need to send more requests. */
/* For basic authentication we need to send just one request. */
if (auth_basic
&& !(authhdr = make_header_line ("Proxy-Authorization: Basic ",
"\r\n",
proxy->uri->auth,
strlen (proxy->uri->auth))))
{
err = gpg_error_from_syserror ();
goto leave;
}
again:
xfree (request);
request = es_bsprintf ("CONNECT %s:%hu HTTP/1.%c\r\nHost: %s:%hu\r\n%s%s",
httphost ? httphost : server,
port,
auth_basic? '0' : '1',
httphost ? httphost : server,
port,
authhdr ? authhdr : "",
hd->keep_alive? "Connection: keep-alive\r\n" : "");
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:proxy:request:");
if (!hd->fp_write)
{
err = make_fp_write (hd, 0, NULL);
if (err)
goto leave;
}
if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Make sure http_wait_response doesn't close the stream. */
saved_flags = hd->flags;
hd->flags &= ~HTTP_FLAG_SHUTDOWN;
/* Get the response and set hd->fp_read */
err = http_wait_response (hd);
if (err)
goto leave;
/* Reset state. */
es_clearerr (hd->fp_read);
((cookie_t)(hd->read_cookie))->up_to_empty_line = 1;
hd->in_data = 0;
if (hd->status_code >= 200 && hd->status_code < 300 )
err = 0; /* Success. */
else if (hd->status_code == 407)
{
if (opt_debug)
log_debug ("http.c:proxy_connect: 407 seen\n");
parms = NULL;
for (idx=0; (s = http_get_header (hd, "Proxy-Authenticate", idx)); idx++)
{
if (opt_debug)
log_debug ("http.c:proxy_connect: method=%s\n", s);
if (!parms)
parms = has_leading_keyword (s, "Negotiate");
}
if (!parms)
authstate = AUTH_NGT_NONE;
else if (authstate == AUTH_NGT_NONE)
authstate = AUTH_NGT_RCVD;
switch (authstate)
{
case AUTH_NGT_NONE:
if (opt_debug)
log_debug ("http.c:proxy_connect: no supported auth method\n");
err = gpg_error (GPG_ERR_NO_AUTH);
break;
case AUTH_NGT_RCVD:
if (opt_debug)
log_debug ("http.c:proxy_connect: using negotiate - init\n");
err = proxy_get_token (proxy, NULL);
if (err)
goto leave;
if (proxy->outtoklen) /* Authentication needs to continue. */
{
xfree (authhdr);
authhdr = make_header_line ("Proxy-Authorization: Negotiate ",
"\r\n",
proxy->outtoken, proxy->outtoklen);
if (!authhdr)
{
err = gpg_error_from_syserror ();
goto leave;
}
authstate = AUTH_NGT_SENT;
authpasses++;
goto again;
}
break;
case AUTH_NGT_SENT:
if (opt_debug)
log_debug ("http.c:proxy_connect: using negotiate - next\n");
if (!*parms)
{
log_debug ("proxy authentication failed"
" due to server not accepting our challenge\n");
err = gpg_error (GPG_ERR_BAD_AUTH);
goto leave;
}
if (authpasses > 5)
{
log_error ("proxy authentication failed"
" due to too many passes\n");
err = gpg_error (GPG_ERR_BAD_AUTH);
goto leave;
}
err = proxy_get_token (proxy, parms);
if (err)
goto leave;
if (proxy->outtoklen) /* Authentication needs to continue. */
{
xfree (authhdr);
authhdr = make_header_line ("Proxy-Authorization: Negotiate ",
"\r\n",
proxy->outtoken, proxy->outtoklen);
if (!authhdr)
{
err = gpg_error_from_syserror ();
goto leave;
}
authpasses++;
goto again;
}
break;
default:
BUG();
}
}
else
err = gpg_error (GPG_ERR_NO_DATA);
if (err)
{
xfree (tmpstr);
tmpstr = es_bsprintf ("%s:%hu", httphost ? httphost : server, port);
log_error (_("error accessing '%s': http status %u\n"),
tmpstr ? tmpstr : "out of core",
http_get_status_code (hd));
goto leave;
}
leave:
if (hd->keep_alive)
{
es_fclose (hd->fp_write);
hd->fp_write = NULL;
/* The close has released the cookie and thus we better set it
* to NULL. */
hd->write_cookie = NULL;
}
/* Restore flags, destroy stream, reset state. */
hd->flags = saved_flags;
es_fclose (hd->fp_read);
hd->fp_read = NULL;
hd->read_cookie = NULL;
hd->keep_alive = 0;
hd->in_data = 0;
xfree (request);
xfree (authhdr);
xfree (tmpstr);
return err;
}
/* Make a request string using a standard proxy. On success the
* request is stored at R_REQUEST (and will never be NULL). */
static gpg_error_t
mk_proxy_request (http_t hd, proxy_info_t proxy,
const char *httphost, const char *server,
unsigned short port, const char *relpath,
const char *authstr,
char **r_request)
{
gpg_error_t err = 0;
char *authhdr = NULL;
char *request = NULL;
*r_request = NULL;
if (proxy->uri->auth
&& !(authhdr = make_header_line ("Proxy-Authorization: Basic ",
"\r\n",
proxy->uri->auth,
strlen (proxy->uri->auth))))
{
err = gpg_error_from_syserror ();
goto leave;
}
request = es_bsprintf ("%s %s://%s:%hu%s%s HTTP/1.0\r\n%s%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
hd->uri->use_tls? "https" : "http",
httphost? httphost : server,
port, *relpath == '/' ? "" : "/", relpath,
authstr ? authstr : "",
authhdr ? authhdr : "");
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
*r_request = request;
request = NULL;
leave:
xfree (request);
xfree (authhdr);
return err;
}
/* Make a request string using. On success the request is stored at
* R_REQUEST (and will never be NULL). */
static gpg_error_t
mk_std_request (http_t hd,
const char *httphost, const char *server,
unsigned short port, const char *relpath,
const char *authstr,
char **r_request)
{
gpg_error_t err = 0;
char portstr[35];
char *request = NULL;
*r_request = NULL;
if (port == (hd->uri->use_tls? 443 : 80))
*portstr = 0;
else
snprintf (portstr, sizeof portstr, ":%u", port);
request = es_bsprintf ("%s %s%s HTTP/1.0\r\nHost: %s%s\r\n%s",
hd->req_type == HTTP_REQ_GET ? "GET" :
hd->req_type == HTTP_REQ_HEAD ? "HEAD" :
hd->req_type == HTTP_REQ_POST ? "POST" : "OOPS",
*relpath == '/' ? "" : "/", relpath,
httphost? httphost : server,
portstr,
authstr? authstr:"");
if (!request)
{
err = gpg_error_from_syserror ();
goto leave;
}
*r_request = request;
request = NULL;
leave:
xfree (request);
return err;
}
/*
* Send a HTTP request to the server
* Returns 0 if the request was successful
*/
static gpg_error_t
send_request (ctrl_t ctrl,
http_t hd, const char *httphost, const char *auth,
const char *override_proxy,
const char *srvtag, unsigned int timeout,
strlist_t headers)
{
gpg_error_t err;
const char *server;
char *request = NULL;
char *relpath = NULL;
unsigned short port;
int use_http_proxy = 0;
char *proxy_authstr = NULL;
char *authstr = NULL;
assuan_fd_t sock;
proxy_info_t proxy = NULL;
err = send_request_basic_checks (hd);
if (err)
goto leave;
if ((hd->flags & HTTP_FLAG_FORCE_TOR))
{
/* Non-blocking connects do not work with our Tor proxy because
* we can't continue the Socks protocol after the EINPROGRESS.
* Disable the timeout to use a blocking connect. */
timeout = 0;
}
server = *hd->uri->host ? hd->uri->host : "localhost";
port = hd->uri->port ? hd->uri->port : 80;
if ((err = send_request_set_sni (hd, httphost? httphost : server)))
goto leave;
if ((err = get_proxy_for_url (hd, override_proxy, &proxy)))
goto leave;
if (proxy && proxy->is_http_proxy)
{
use_http_proxy = 1; /* We want to use a proxy for the connection. */
err = connect_server (ctrl,
*proxy->uri->host ? proxy->uri->host : "localhost",
proxy->uri->port ? proxy->uri->port : 80,
hd->flags, NULL, timeout, &sock);
}
else
{
err = connect_server (ctrl,
server, port, hd->flags, srvtag, timeout, &sock);
}
if (err)
goto leave;
hd->sock = my_socket_new (sock);
if (!hd->sock)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (use_http_proxy && hd->uri->use_tls)
{
err = run_proxy_connect (hd, proxy, httphost, server, port);
if (err)
goto leave;
/* We are done with the proxy, the code below will establish a
* TLS session and talk directly to the target server. Thus we
* clear the flag to indicate this. */
use_http_proxy = 0;
}
#if HTTP_USE_NTBTLS
err = run_ntbtls_handshake (hd);
#elif HTTP_USE_GNUTLS
err = run_gnutls_handshake (hd, server);
#else
err = 0;
#endif
if (err)
goto leave;
if (auth || hd->uri->auth)
{
char *myauth;
if (auth)
{
myauth = xtrystrdup (auth);
if (!myauth)
{
err = gpg_error_from_syserror ();
goto leave;
}
remove_escapes (myauth);
}
else
{
remove_escapes (hd->uri->auth);
myauth = hd->uri->auth;
}
authstr = make_header_line ("Authorization: Basic ", "\r\n",
myauth, strlen (myauth));
if (auth) /* (Was allocated.) */
xfree (myauth);
if (!authstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
relpath = build_rel_path (hd->uri);
if (!relpath)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (use_http_proxy)
err = mk_proxy_request (hd, proxy, httphost, server, port,
relpath, authstr, &request);
else
err = mk_std_request (hd, httphost, server, port,
relpath, authstr, &request);
if (err)
goto leave;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (request, "http.c:request:");
/* First setup estream so that we can write even the first line
using estream. This is also required for the sake of gnutls. */
err = make_fp_write (hd, hd->uri->use_tls, hd->session);
if (err)
goto leave;
if (es_fputs (request, hd->fp_write) || es_fflush (hd->fp_write))
{
err = gpg_error_from_syserror ();
goto leave;
}
for (;headers; headers=headers->next)
{
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (headers->d, "http.c:request-header:");
if ((es_fputs (headers->d, hd->fp_write) || es_fflush (hd->fp_write))
|| (es_fputs("\r\n",hd->fp_write) || es_fflush(hd->fp_write)))
{
err = gpg_error_from_syserror ();
goto leave;
}
}
leave:
es_free (request);
xfree (authstr);
xfree (proxy_authstr);
xfree (relpath);
release_proxy_info (proxy);
return err;
}
/*
* Build the relative path from the parsed URI. Minimal
* implementation. May return NULL in case of memory failure; errno
* is then set accordingly.
*/
static char *
build_rel_path (parsed_uri_t uri)
{
uri_tuple_t r;
char *rel_path, *p;
int n;
/* Count the needed space. */
n = insert_escapes (NULL, uri->path, "%;?&");
/* TODO: build params. */
for (r = uri->query; r; r = r->next)
{
n++; /* '?'/'&' */
n += insert_escapes (NULL, r->name, "%;?&=");
if (!r->no_value)
{
n++; /* '=' */
n += insert_escapes (NULL, r->value, "%;?&=");
}
}
n++;
/* Now allocate and copy. */
p = rel_path = xtrymalloc (n);
if (!p)
return NULL;
n = insert_escapes (p, uri->path, "%;?&");
p += n;
/* TODO: add params. */
for (r = uri->query; r; r = r->next)
{
*p++ = r == uri->query ? '?' : '&';
n = insert_escapes (p, r->name, "%;?&=");
p += n;
if (!r->no_value)
{
*p++ = '=';
/* TODO: Use valuelen. */
n = insert_escapes (p, r->value, "%;?&=");
p += n;
}
}
*p = 0;
return rel_path;
}
/* Transform a header name into a standard capitalized format; e.g.
"Content-Type". Conversion stops at the colon. As usual we don't
use the localized versions of ctype.h. */
static void
capitalize_header_name (char *name)
{
int first = 1;
for (; *name && *name != ':'; name++)
{
if (*name == '-')
first = 1;
else if (first)
{
if (*name >= 'a' && *name <= 'z')
*name = *name - 'a' + 'A';
first = 0;
}
else if (*name >= 'A' && *name <= 'Z')
*name = *name - 'A' + 'a';
}
}
/* Store an HTTP header line in LINE away. Line continuation is
supported as well as merging of headers with the same name. This
function may modify LINE. */
static gpg_err_code_t
store_header (http_t hd, char *line)
{
size_t n;
char *p, *value;
header_t h;
n = strlen (line);
if (n && line[n-1] == '\n')
{
line[--n] = 0;
if (n && line[n-1] == '\r')
line[--n] = 0;
}
if (!n) /* we are never called to hit this. */
return GPG_ERR_BUG;
if (*line == ' ' || *line == '\t')
{
/* Continuation. This won't happen too often as it is not
recommended. We use a straightforward implementation. */
if (!hd->headers)
return GPG_ERR_PROTOCOL_VIOLATION;
n += strlen (hd->headers->value);
p = xtrymalloc (n+1);
if (!p)
return gpg_err_code_from_syserror ();
strcpy (stpcpy (p, hd->headers->value), line);
xfree (hd->headers->value);
hd->headers->value = p;
return 0;
}
capitalize_header_name (line);
p = strchr (line, ':');
if (!p)
return GPG_ERR_PROTOCOL_VIOLATION;
*p++ = 0;
while (*p == ' ' || *p == '\t')
p++;
value = p;
/* Check whether we have already seen a line with that name. In
* that case we assume it is a comma separated list and merge
* them. Of course there are a few exceptions. */
if (!strcmp (line, "Proxy-Authenticate")
|| !strcmp (line, "Www-Authenticate"))
; /* Better to have them separate. */
else
{
for (h=hd->headers; h; h = h->next)
if ( !strcmp (h->name, line) )
break;
if (h)
{
p = strconcat (h->value, ",", value, NULL);
if (!p)
return gpg_err_code_from_syserror ();
xfree (h->value);
h->value = p;
return 0;
}
}
/* Append a new header. */
h = xtrymalloc (sizeof *h + strlen (line));
if (!h)
return gpg_err_code_from_syserror ();
strcpy (h->name, line);
h->value = xtrymalloc (strlen (value)+1);
if (!h->value)
{
xfree (h);
return gpg_err_code_from_syserror ();
}
strcpy (h->value, value);
h->next = hd->headers;
hd->headers = h;
return 0;
}
/* Return the header NAME from the last response. The returned value
* is valid as along as HD has not been closed and no other request
* has been send. If the header was not found, NULL is returned. NAME
* must be canonicalized, that is the first letter of each dash
* delimited part must be uppercase and all other letters lowercase.
* SKIP gives the number of entries of the requested NAME to skip
* before returning; this can be used to enumerate headers with the
* same name (see store_header).
*/
const char *
http_get_header (http_t hd, const char *name, unsigned int skip)
{
header_t h;
for (h=hd->headers; h; h = h->next)
if (!strcmp (h->name, name))
{
if (skip)
skip--;
else
return h->value;
}
return NULL;
}
/* Return a newly allocated and NULL terminated array with pointers to
header names. The array must be released with xfree() and its
content is only values as long as no other request has been
send. */
const char **
http_get_header_names (http_t hd)
{
const char **array;
size_t n;
header_t h;
for (n=0, h = hd->headers; h; h = h->next)
n++;
array = xtrycalloc (n+1, sizeof *array);
if (array)
{
for (n=0, h = hd->headers; h; h = h->next)
array[n++] = h->name;
}
return array;
}
/*
* Parse the response from a server.
* Returns: Errorcode and sets some files in the handle
*/
static gpg_error_t
parse_response (http_t hd)
{
char *line, *p, *p2;
size_t maxlen, len;
cookie_t cookie = hd->read_cookie;
const char *s;
/* Delete old header lines. */
while (hd->headers)
{
header_t tmp = hd->headers->next;
xfree (hd->headers->value);
xfree (hd->headers);
hd->headers = tmp;
}
/* Wait for the status line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_error_from_syserror (); /* Out of core. */
if (!maxlen)
return gpg_error (GPG_ERR_TRUNCATED); /* Line has been truncated. */
if (!len)
return gpg_error (GPG_ERR_EOF);
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_debug_string (line, "http.c:response:\n");
}
while (!*line);
if ((p = strchr (line, '/')))
*p++ = 0;
if (!p || strcmp (line, "HTTP"))
return 0; /* Assume http 0.9. */
if ((p2 = strpbrk (p, " \t")))
{
*p2++ = 0;
p2 += strspn (p2, " \t");
}
if (!p2)
return 0; /* Also assume http 0.9. */
p = p2;
/* TODO: Add HTTP version number check. */
if ((p2 = strpbrk (p, " \t")))
*p2++ = 0;
if (!isdigit ((unsigned int)p[0]) || !isdigit ((unsigned int)p[1])
|| !isdigit ((unsigned int)p[2]) || p[3])
{
/* Malformed HTTP status code - assume http 0.9. */
hd->is_http_0_9 = 1;
hd->status_code = 200;
return 0;
}
hd->status_code = atoi (p);
/* Skip all the header lines and wait for the empty line. */
do
{
maxlen = MAX_LINELEN;
len = es_read_line (hd->fp_read, &hd->buffer, &hd->buffer_size, &maxlen);
line = hd->buffer;
if (!line)
return gpg_error_from_syserror (); /* Out of core. */
/* Note, that we can silently ignore truncated lines. */
if (!len)
return gpg_error (GPG_ERR_EOF);
/* Trim line endings of empty lines. */
if ((*line == '\r' && line[1] == '\n') || *line == '\n')
*line = 0;
if (opt_debug || (hd->flags & HTTP_FLAG_LOG_RESP))
log_info ("http.c:RESP: '%.*s'\n",
(int)strlen(line)-(*line&&line[1]?2:0),line);
if (*line)
{
gpg_err_code_t ec = store_header (hd, line);
if (ec)
return gpg_error (ec);
}
}
while (len && *line);
cookie->content_length_valid = 0;
if (!(hd->flags & HTTP_FLAG_IGNORE_CL))
{
s = http_get_header (hd, "Content-Length", 0);
if (s)
{
cookie->content_length_valid = 1;
cookie->content_length = string_to_u64 (s);
}
}
return 0;
}
#if 0
static int
start_server ()
{
struct sockaddr_in mya;
struct sockaddr_in peer;
int fd, client;
fd_set rfds;
int addrlen;
int i;
if ((fd = socket (AF_INET, SOCK_STREAM, 0)) == -1)
{
log_error ("socket() failed: %s\n", strerror (errno));
return -1;
}
i = 1;
if (setsockopt (fd, SOL_SOCKET, SO_REUSEADDR, (byte *) & i, sizeof (i)))
log_info ("setsockopt(SO_REUSEADDR) failed: %s\n", strerror (errno));
mya.sin_family = AF_INET;
memset (&mya.sin_addr, 0, sizeof (mya.sin_addr));
mya.sin_port = htons (11371);
if (bind (fd, (struct sockaddr *) &mya, sizeof (mya)))
{
log_error ("bind to port 11371 failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
if (listen (fd, 5))
{
log_error ("listen failed: %s\n", strerror (errno));
sock_close (fd);
return -1;
}
for (;;)
{
FD_ZERO (&rfds);
FD_SET (fd, &rfds);
if (my_select (fd + 1, &rfds, NULL, NULL, NULL) <= 0)
continue; /* ignore any errors */
if (!FD_ISSET (fd, &rfds))
continue;
addrlen = sizeof peer;
client = my_accept (fd, (struct sockaddr *) &peer, &addrlen);
if (client == -1)
continue; /* oops */
log_info ("connect from %s\n", inet_ntoa (peer.sin_addr));
fflush (stdout);
fflush (stderr);
if (!fork ())
{
int c;
FILE *fp;
fp = fdopen (client, "r");
while ((c = getc (fp)) != EOF)
putchar (c);
fclose (fp);
exit (0);
}
sock_close (client);
}
return 0;
}
#endif
/* Return true if SOCKS shall be used. This is the case if tor_mode
* is enabled and the desired address is not the loopback address.
* This function is basically a copy of the same internal function in
* Libassuan. */
static int
use_socks (struct sockaddr_storage *addr)
{
int mode;
if (assuan_sock_get_flag (ASSUAN_INVALID_FD, "tor-mode", &mode) || !mode)
return 0; /* Not in Tor mode. */
else if (addr->ss_family == AF_INET6)
{
struct sockaddr_in6 *addr_in6 = (struct sockaddr_in6 *)addr;
const unsigned char *s;
int i;
s = (unsigned char *)&addr_in6->sin6_addr.s6_addr;
if (s[15] != 1)
return 1; /* Last octet is not 1 - not the loopback address. */
for (i=0; i < 15; i++, s++)
if (*s)
return 1; /* Non-zero octet found - not the loopback address. */
return 0; /* This is the loopback address. */
}
else if (addr->ss_family == AF_INET)
{
struct sockaddr_in *addr_in = (struct sockaddr_in *)addr;
if (*(unsigned char*)&addr_in->sin_addr.s_addr == 127)
return 0; /* Loopback (127.0.0.0/8) */
return 1;
}
else
return 0;
}
/* Wrapper around assuan_sock_new which takes the domain from an
* address parameter. */
static assuan_fd_t
my_sock_new_for_addr (struct sockaddr_storage *addr, int type, int proto)
{
int domain;
if (use_socks (addr))
{
/* Libassaun always uses 127.0.0.1 to connect to the socks
* server (i.e. the Tor daemon). */
domain = AF_INET;
}
else
domain = addr->ss_family;
return assuan_sock_new (domain, type, proto);
}
/* Call WSAGetLastError and map it to a libgpg-error. */
#ifdef HAVE_W32_SYSTEM
static gpg_error_t
my_wsagetlasterror (void)
{
int wsaerr;
gpg_err_code_t ec;
wsaerr = WSAGetLastError ();
switch (wsaerr)
{
case WSAENOTSOCK: ec = GPG_ERR_EINVAL; break;
case WSAEWOULDBLOCK: ec = GPG_ERR_EAGAIN; break;
case ERROR_BROKEN_PIPE: ec = GPG_ERR_EPIPE; break;
case WSANOTINITIALISED: ec = GPG_ERR_ENOSYS; break;
case WSAENOBUFS: ec = GPG_ERR_ENOBUFS; break;
case WSAEMSGSIZE: ec = GPG_ERR_EMSGSIZE; break;
case WSAECONNREFUSED: ec = GPG_ERR_ECONNREFUSED; break;
case WSAEISCONN: ec = GPG_ERR_EISCONN; break;
case WSAEALREADY: ec = GPG_ERR_EALREADY; break;
case WSAETIMEDOUT: ec = GPG_ERR_ETIMEDOUT; break;
default: ec = GPG_ERR_EIO; break;
}
return gpg_err_make (default_errsource, ec);
}
#endif /*HAVE_W32_SYSTEM*/
/* Connect SOCK and return GPG_ERR_ETIMEOUT if a connection could not
* be established within TIMEOUT milliseconds. 0 indicates the
* system's default timeout. The other args are the usual connect
* args. On success 0 is returned, on timeout GPG_ERR_ETIMEDOUT, and
* another error code for other errors. On timeout the caller needs
* to close the socket as soon as possible to stop an ongoing
* handshake.
*
* This implementation is for well-behaving systems; see Stevens,
* Network Programming, 2nd edition, Vol 1, 15.4. */
static gpg_error_t
connect_with_timeout (assuan_fd_t sock,
struct sockaddr *addr, int addrlen,
unsigned int timeout)
{
gpg_error_t err;
int syserr;
socklen_t slen;
fd_set rset, wset;
struct timeval tval;
int n;
#ifndef HAVE_W32_SYSTEM
int oflags;
# define RESTORE_BLOCKING() do { \
fcntl (sock, F_SETFL, oflags); \
} while (0)
#else /*HAVE_W32_SYSTEM*/
# define RESTORE_BLOCKING() do { \
unsigned long along = 0; \
ioctlsocket (FD2INT (sock), FIONBIO, &along); \
} while (0)
#endif /*HAVE_W32_SYSTEM*/
if (!timeout)
{
/* Shortcut. */
if (assuan_sock_connect (sock, addr, addrlen))
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
else
err = 0;
return err;
}
/* Switch the socket into non-blocking mode. */
#ifdef HAVE_W32_SYSTEM
{
unsigned long along = 1;
if (ioctlsocket (FD2INT (sock), FIONBIO, &along))
return my_wsagetlasterror ();
}
#else
oflags = fcntl (sock, F_GETFL, 0);
if (fcntl (sock, F_SETFL, oflags | O_NONBLOCK))
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
#endif
/* Do the connect. */
if (!assuan_sock_connect (sock, addr, addrlen))
{
/* Immediate connect. Restore flags. */
RESTORE_BLOCKING ();
return 0; /* Success. */
}
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
if (gpg_err_code (err) != GPG_ERR_EINPROGRESS
#ifdef HAVE_W32_SYSTEM
&& gpg_err_code (err) != GPG_ERR_EAGAIN
#endif
)
{
RESTORE_BLOCKING ();
return err;
}
FD_ZERO (&rset);
FD_SET (FD2INT (sock), &rset);
wset = rset;
tval.tv_sec = timeout / 1000;
tval.tv_usec = (timeout % 1000) * 1000;
n = my_select (FD2NUM(sock)+1, &rset, &wset, NULL, &tval);
if (n < 0)
{
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
RESTORE_BLOCKING ();
return err;
}
if (!n)
{
/* Timeout: We do not restore the socket flags on timeout
* because the caller is expected to close the socket. */
return gpg_err_make (default_errsource, GPG_ERR_ETIMEDOUT);
}
if (!FD_ISSET (FD2INT (sock), &rset) && !FD_ISSET (FD2INT (sock), &wset))
{
/* select misbehaved. */
return gpg_err_make (default_errsource, GPG_ERR_SYSTEM_BUG);
}
slen = sizeof (syserr);
if (getsockopt (FD2INT(sock), SOL_SOCKET, SO_ERROR,
(void*)&syserr, &slen) < 0)
{
/* Assume that this is Solaris which returns the error in ERRNO. */
err = gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
}
else if (syserr)
err = gpg_err_make (default_errsource, gpg_err_code_from_errno (syserr));
else
err = 0; /* Connected. */
RESTORE_BLOCKING ();
return err;
#undef RESTORE_BLOCKING
}
/* Actually connect to a server. On success 0 is returned and the
* file descriptor for the socket is stored at R_SOCK; on error an
* error code is returned and ASSUAN_INVALID_FD is stored at R_SOCK.
* TIMEOUT is the connect timeout in milliseconds. Note that the
* function tries to connect to all known addresses and the timeout is
* for each one. */
static gpg_error_t
connect_server (ctrl_t ctrl, const char *server, unsigned short port,
unsigned int flags, const char *srvtag, unsigned int timeout,
assuan_fd_t *r_sock)
{
gpg_error_t err;
assuan_fd_t sock = ASSUAN_INVALID_FD;
unsigned int srvcount = 0;
int hostfound = 0;
int anyhostaddr = 0;
int srv, connected, v4_valid, v6_valid;
gpg_error_t last_err = 0;
struct srventry *serverlist = NULL;
*r_sock = ASSUAN_INVALID_FD;
#if defined(HAVE_W32_SYSTEM) && !defined(HTTP_NO_WSASTARTUP)
init_sockets ();
#endif /*Windows*/
check_inet_support (&v4_valid, &v6_valid);
/* Onion addresses require special treatment. */
if (is_onion_address (server))
{
#ifdef ASSUAN_SOCK_TOR
if (opt_debug)
log_debug ("http.c:connect_server:onion: name='%s' port=%hu\n",
server, port);
sock = assuan_sock_connect_byname (server, port, 0, NULL,
ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
{
err = gpg_err_make (default_errsource,
(errno == EHOSTUNREACH)? GPG_ERR_UNKNOWN_HOST
: gpg_err_code_from_syserror ());
log_error ("can't connect to '%s': %s\n", server, gpg_strerror (err));
return err;
}
notify_netactivity ();
*r_sock = sock;
return 0;
#else /*!ASSUAN_SOCK_TOR*/
err = gpg_err_make (default_errsource, GPG_ERR_ENETUNREACH);
return ASSUAN_INVALID_FD;
#endif /*!HASSUAN_SOCK_TOR*/
}
/* Do the SRV thing */
if (srvtag)
{
err = get_dns_srv (ctrl, server, srvtag, NULL, &serverlist, &srvcount);
if (err)
log_info ("getting '%s' SRV for '%s' failed: %s\n",
srvtag, server, gpg_strerror (err));
/* Note that on error SRVCOUNT is zero. */
err = 0;
}
if (!serverlist)
{
/* Either we're not using SRV, or the SRV lookup failed. Make
up a fake SRV record. */
serverlist = xtrycalloc (1, sizeof *serverlist);
if (!serverlist)
return gpg_err_make (default_errsource, gpg_err_code_from_syserror ());
serverlist->port = port;
strncpy (serverlist->target, server, DIMof (struct srventry, target));
serverlist->target[DIMof (struct srventry, target)-1] = '\0';
srvcount = 1;
}
connected = 0;
for (srv=0; srv < srvcount && !connected; srv++)
{
dns_addrinfo_t aibuf, ai;
if (opt_debug)
log_debug ("http.c:connect_server: trying name='%s' port=%hu\n",
serverlist[srv].target, port);
err = resolve_dns_name (ctrl,
serverlist[srv].target, port, 0, SOCK_STREAM,
&aibuf, NULL);
if (err)
{
log_info ("resolving '%s' failed: %s\n",
serverlist[srv].target, gpg_strerror (err));
last_err = err;
continue; /* Not found - try next one. */
}
hostfound = 1;
for (ai = aibuf; ai && !connected; ai = ai->next)
{
if (ai->family == AF_INET
&& ((flags & HTTP_FLAG_IGNORE_IPv4) || !v4_valid))
continue;
if (ai->family == AF_INET6
&& ((flags & HTTP_FLAG_IGNORE_IPv6) || !v6_valid))
continue;
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
sock = my_sock_new_for_addr (ai->addr, ai->socktype, ai->protocol);
if (sock == ASSUAN_INVALID_FD)
{
if (errno == EAFNOSUPPORT)
{
if (ai->family == AF_INET)
v4_valid = 0;
if (ai->family == AF_INET6)
v6_valid = 0;
continue;
}
err = gpg_err_make (default_errsource,
gpg_err_code_from_syserror ());
log_error ("error creating socket: %s\n", gpg_strerror (err));
free_dns_addrinfo (aibuf);
xfree (serverlist);
return err;
}
anyhostaddr = 1;
err = connect_with_timeout (sock, (struct sockaddr *)ai->addr,
ai->addrlen, timeout);
if (err)
{
last_err = err;
}
else
{
connected = 1;
notify_netactivity ();
}
}
free_dns_addrinfo (aibuf);
}
xfree (serverlist);
if (!connected)
{
if (!hostfound)
{
log_error ("can't connect to '%s': %s\n",
server, "host not found");
/* If the resolver told us "no name" translate this in this
* case to "unknown host". */
if (gpg_err_code (last_err) == GPG_ERR_NO_NAME)
last_err = 0;
}
else if (!anyhostaddr)
log_error ("can't connect to '%s': %s\n",
server, "no IP address for host");
else
{
#ifdef HAVE_W32_SYSTEM
log_error ("can't connect to '%s': ec=%d\n",
server, (int)WSAGetLastError());
#else
log_error ("can't connect to '%s': %s\n",
server, gpg_strerror (last_err));
#endif
}
err = last_err? last_err : gpg_err_make (default_errsource,
GPG_ERR_UNKNOWN_HOST);
if (sock != ASSUAN_INVALID_FD)
assuan_sock_close (sock);
return err;
}
*r_sock = sock;
return 0;
}
/* Helper to read from a socket. This handles npth things and
* EINTR. */
static gpgrt_ssize_t
read_server (assuan_fd_t sock, void *buffer, size_t size)
{
int nread;
do
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to use recv for a socket. */
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nread = recv (FD2INT (sock), buffer, size, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nread = npth_read (sock, buffer, size);
# else
nread = read (sock, buffer, size);
# endif
#endif /*!HAVE_W32_SYSTEM*/
}
while (nread == -1 && errno == EINTR);
return nread;
}
static gpg_error_t
write_server (assuan_fd_t sock, const char *data, size_t length)
{
int nleft;
int nwritten;
nleft = length;
while (nleft > 0)
{
#if defined(HAVE_W32_SYSTEM)
# if defined(USE_NPTH)
npth_unprotect ();
# endif
nwritten = send (FD2INT (sock), data, nleft, 0);
# if defined(USE_NPTH)
npth_protect ();
# endif
if ( nwritten == SOCKET_ERROR )
{
log_info ("network write failed: ec=%d\n", (int)WSAGetLastError ());
return gpg_error (GPG_ERR_NETWORK);
}
#else /*!HAVE_W32_SYSTEM*/
# ifdef USE_NPTH
nwritten = npth_write (sock, data, nleft);
# else
nwritten = write (sock, data, nleft);
# endif
if (nwritten == -1)
{
if (errno == EINTR)
continue;
if (errno == EAGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("network write failed: %s\n", strerror (errno));
return gpg_error_from_syserror ();
}
#endif /*!HAVE_W32_SYSTEM*/
nleft -= nwritten;
data += nwritten;
}
return 0;
}
/* Read handler for estream. */
static gpgrt_ssize_t
cookie_read (void *cookie, void *buffer, size_t size)
{
cookie_t c = cookie;
int nread;
size_t offset = 0;
if (c->content_length_valid)
{
if (!c->content_length)
{
c->content_length_valid = 0;
return 0; /* EOF */
}
if (c->content_length < size)
size = c->content_length;
}
if (c->pending.len)
{
offset = c->pending.len > size? size : c->pending.len;
memcpy (buffer, c->pending.data, offset);
c->pending.len -= offset;
}
if (offset >= size)
nread = offset;
else
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
nread = es_fread ((char*)buffer+offset, 1, size-offset, in);
if (opt_debug)
log_debug ("TLS network read: %d/%zu\n", nread, size-offset);
if (nread >= 0)
nread += offset;
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
again:
nread = gnutls_record_recv (c->session->tls_session,
(char*)buffer+offset, size-offset);
if (nread < 0)
{
if (nread == GNUTLS_E_INTERRUPTED)
goto again;
if (nread == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
if (nread == GNUTLS_E_REHANDSHAKE)
goto again; /* A client is allowed to just ignore this request. */
if (nread == GNUTLS_E_PREMATURE_TERMINATION)
{
/* The server terminated the connection. Close the TLS
session, and indicate EOF using a short read. */
close_tls_session (c->session);
return 0;
}
log_info ("TLS network read failed: %s\n", gnutls_strerror (nread));
gpg_err_set_errno (EIO);
return -1;
}
if (nread >= 0)
nread += offset;
}
else
#endif /*HTTP_USE_GNUTLS*/
{
nread = read_server (c->sock->fd, (char*)buffer+offset, size-offset);
if (opt_debug)
log_debug ("network read: %d/%zu\n", nread, size);
if (nread >= 0)
nread += offset;
}
if (nread > 0 && c->up_to_empty_line)
{
gpg_error_t err;
const char *s;
size_t n;
int extra;
int lfcr_pending = 0;
char *bufp = buffer;
if (c->last_was_lf && nread > 1 && bufp[0] == '\r' && bufp[1] == '\n')
{
s = buffer;
extra = 2;
}
else if (c->last_was_lf && bufp[0] == '\r')
{
lfcr_pending = 1;
s = buffer; /* Only to avoid the call to gnupg_memstr. */
}
else if (c->last_was_lfcr && bufp[0] == '\n')
{
s = buffer;
extra = 1;
}
else
s = NULL;
c->last_was_lfcr = c->last_was_lf = 0;
if (!s)
{
s = gnupg_memstr (buffer, nread, "\n\r\n");
extra = 3;
}
if (lfcr_pending)
c->last_was_lfcr = 1;
else if (s)
{
/* Save away the rest and return up to the LF. */
log_assert (!c->pending.len);
n = (s+extra) - bufp;
log_assert (n <= nread);
c->pending.len = nread - n;
if (!c->pending.data || c->pending.len >= c->pending.size)
{
xfree (c->pending.data);
c->pending.size = c->pending.len + 256; /* Some extra space. */
c->pending.data = xtrymalloc (c->pending.size);
if (!c->pending.data)
{
err = gpg_error_from_syserror ();
log_error ("error allocating network read buffer: %s\n",
gpg_strerror (err));
return -1;
}
memcpy (c->pending.data, bufp + n, c->pending.len);
}
else
memcpy (c->pending.data, bufp + n, c->pending.len);
nread = n; /* Return everything up to the empty line. */
c->up_to_empty_line = 0;
}
else if (bufp[nread-1] == '\n')
c->last_was_lf = 1;
else if (nread > 1 && bufp[nread-2] == '\n' && bufp[nread-1] == '\r')
c->last_was_lfcr = 1;
}
if (c->content_length_valid && nread > 0)
{
if (nread < c->content_length)
c->content_length -= nread;
else
c->content_length = 0;
}
return (gpgrt_ssize_t)nread;
}
/* Write handler for estream. */
static gpgrt_ssize_t
cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
cookie_t c = cookie;
int nwritten = 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
estream_t in, out;
ntbtls_get_stream (c->session->tls_session, &in, &out);
if (size == 0)
es_fflush (out);
else
nwritten = es_fwrite (buffer, 1, size, out);
if (opt_debug)
log_debug ("TLS network write: %d/%zu\n", nwritten, size);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
{
int nleft = size;
while (nleft > 0)
{
nwritten = gnutls_record_send (c->session->tls_session,
buffer, nleft);
if (nwritten <= 0)
{
if (nwritten == GNUTLS_E_INTERRUPTED)
continue;
if (nwritten == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
continue;
}
log_info ("TLS network write failed: %s\n",
gnutls_strerror (nwritten));
gpg_err_set_errno (EIO);
return -1;
}
nleft -= nwritten;
buffer += nwritten;
}
}
else
#endif /*HTTP_USE_GNUTLS*/
{
if ( write_server (c->sock->fd, buffer, size) )
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
}
return (gpgrt_ssize_t)nwritten;
}
#if defined(HAVE_W32_SYSTEM) && defined(HTTP_USE_NTBTLS)
static gpgrt_ssize_t
simple_cookie_read (void *cookie, void *buffer, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
return read_server (sock, buffer, size);
}
static gpgrt_ssize_t
simple_cookie_write (void *cookie, const void *buffer_arg, size_t size)
{
assuan_fd_t sock = (assuan_fd_t)cookie;
const char *buffer = buffer_arg;
int nwritten;
if (write_server (sock, buffer, size))
{
gpg_err_set_errno (EIO);
nwritten = -1;
}
else
nwritten = size;
return (gpgrt_ssize_t)nwritten;
}
#endif /*HAVE_W32_SYSTEM*/
#ifdef HTTP_USE_GNUTLS
/* Wrapper for gnutls_bye used by my_socket_unref. */
static void
send_gnutls_bye (void *opaque)
{
tls_session_t tls_session = opaque;
int ret;
again:
do
ret = gnutls_bye (tls_session, GNUTLS_SHUT_RDWR);
while (ret == GNUTLS_E_INTERRUPTED);
if (ret == GNUTLS_E_AGAIN)
{
struct timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 50000;
my_select (0, NULL, NULL, NULL, &tv);
goto again;
}
}
#endif /*HTTP_USE_GNUTLS*/
/* Close handler for estream. */
static int
cookie_close (void *cookie)
{
cookie_t c = cookie;
if (!c)
return 0;
#if HTTP_USE_NTBTLS
if (c->use_tls && c->session && c->session->tls_session)
{
/* FIXME!! Possibly call ntbtls_close_notify for close
of write stream. */
my_socket_unref (c->sock, NULL, NULL);
}
else
#elif HTTP_USE_GNUTLS
if (c->use_tls && c->session && c->session->tls_session)
my_socket_unref (c->sock, send_gnutls_bye, c->session->tls_session);
else
#endif /*HTTP_USE_GNUTLS*/
if (c->sock)
my_socket_unref (c->sock, NULL, NULL);
if (c->session)
http_session_unref (c->session);
xfree (c->pending.data);
xfree (c);
return 0;
}
/* Verify the credentials of the server. Returns 0 on success and
store the result in the session object. Only used by GNUTLS. */
gpg_error_t
http_verify_server_credentials (http_session_t sess)
{
#if HTTP_USE_GNUTLS
static const char errprefix[] = "TLS verification of peer failed";
int rc;
unsigned int status;
const char *hostname;
const gnutls_datum_t *certlist;
unsigned int certlistlen;
gnutls_x509_crt_t cert;
gpg_error_t err = 0;
sess->verify.done = 1;
sess->verify.status = 0;
sess->verify.rc = GNUTLS_E_CERTIFICATE_ERROR;
if (gnutls_certificate_type_get (sess->tls_session) != GNUTLS_CRT_X509)
{
log_error ("%s: %s\n", errprefix, "not an X.509 certificate");
sess->verify.rc = GNUTLS_E_UNSUPPORTED_CERTIFICATE_TYPE;
return gpg_error (GPG_ERR_GENERAL);
}
rc = gnutls_certificate_verify_peers2 (sess->tls_session, &status);
if (rc)
{
log_error ("%s: %s\n", errprefix, gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
else if (status)
{
gnutls_datum_t statusdat;
log_error ("%s: status=0x%04x\n", errprefix, status);
if (!gnutls_certificate_verification_status_print
(status, GNUTLS_CRT_X509, &statusdat, 0))
{
log_info ("%s: %s\n", errprefix, statusdat.data);
gnutls_free (statusdat.data);
}
sess->verify.status = status;
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
hostname = sess->servername;
if (!hostname || !strchr (hostname, '.'))
{
log_error ("%s: %s\n", errprefix, "hostname missing");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
certlist = gnutls_certificate_get_peers (sess->tls_session, &certlistlen);
if (!certlistlen)
{
log_error ("%s: %s\n", errprefix, "server did not send a certificate");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
/* Need to stop here. */
if (err)
return err;
}
rc = gnutls_x509_crt_init (&cert);
if (rc < 0)
{
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
if (err)
return err;
}
rc = gnutls_x509_crt_import (cert, &certlist[0], GNUTLS_X509_FMT_DER);
if (rc < 0)
{
log_error ("%s: %s: %s\n", errprefix, "error importing certificate",
gnutls_strerror (rc));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
if (!gnutls_x509_crt_check_hostname (cert, hostname))
{
log_error ("%s: %s\n", errprefix, "hostname does not match");
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
}
gnutls_x509_crt_deinit (cert);
if (!err)
sess->verify.rc = 0;
if (sess->cert_log_cb)
{
const void *bufarr[10];
size_t buflenarr[10];
size_t n;
for (n = 0; n < certlistlen && n < DIM (bufarr)-1; n++)
{
bufarr[n] = certlist[n].data;
buflenarr[n] = certlist[n].size;
}
bufarr[n] = NULL;
buflenarr[n] = 0;
sess->cert_log_cb (sess, err, hostname, bufarr, buflenarr);
}
return err;
#else /*!HTTP_USE_GNUTLS*/
(void)sess;
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
#endif
}
/* Return the first query variable with the specified key. If there
is no such variable, return NULL. */
struct uri_tuple_s *
uri_query_lookup (parsed_uri_t uri, const char *key)
{
struct uri_tuple_s *t;
for (t = uri->query; t; t = t->next)
if (strcmp (t->name, key) == 0)
return t;
return NULL;
}
const char *
uri_query_value (parsed_uri_t url, const char *key)
{
struct uri_tuple_s *t;
t = uri_query_lookup (url, key);
return t? t->value : NULL;
}
/* Return true if both URI point to the same host for the purpose of
* redirection check. A is the original host and B the host given in
* the Location header. As a temporary workaround a fixed list of
* exceptions is also consulted. */
static int
same_host_p (parsed_uri_t a, parsed_uri_t b)
{
static struct
{
const char *from; /* NULL uses the last entry from the table. */
const char *to;
} allow[] =
{
{ "protonmail.com", "api.protonmail.com" },
{ NULL, "api.protonmail.ch" },
{ "protonmail.ch", "api.protonmail.com" },
{ NULL, "api.protonmail.ch" },
{ "pm.me", "api.protonmail.ch" }
};
static const char *subdomains[] =
{
"openpgpkey."
};
int i;
const char *from;
if (!a->host || !b->host)
return 0;
if (!ascii_strcasecmp (a->host, b->host))
return 1;
from = NULL;
for (i=0; i < DIM (allow); i++)
{
if (allow[i].from)
from = allow[i].from;
if (!from)
continue;
if (!ascii_strcasecmp (from, a->host)
&& !ascii_strcasecmp (allow[i].to, b->host))
return 1;
}
/* Also consider hosts the same if they differ only in a subdomain;
* in both direction. This allows one to have redirection between the
* WKD advanced and direct lookup methods. */
for (i=0; i < DIM (subdomains); i++)
{
const char *subdom = subdomains[i];
size_t subdomlen = strlen (subdom);
if (!ascii_strncasecmp (a->host, subdom, subdomlen)
&& !ascii_strcasecmp (a->host + subdomlen, b->host))
return 1;
if (!ascii_strncasecmp (b->host, subdom, subdomlen)
&& !ascii_strcasecmp (b->host + subdomlen, a->host))
return 1;
}
return 0;
}
/* Prepare a new URL for a HTTP redirect. INFO has flags controlling
* the operation, STATUS_CODE is used for diagnostics, LOCATION is the
- * value of the "Location" header, and R_URL reveives the new URL on
- * success or NULL or error. Note that INFO->ORIG_URL is
+ * value of the "Location" header, and R_URL receives the new URL on
+ * success or NULL on error. Note that INFO->ORIG_URL is
* required. */
gpg_error_t
http_prepare_redirect (http_redir_info_t *info, unsigned int status_code,
const char *location, char **r_url)
{
gpg_error_t err;
parsed_uri_t locuri;
parsed_uri_t origuri;
char *newurl;
char *p;
*r_url = NULL;
if (!info || !info->orig_url)
return gpg_error (GPG_ERR_INV_ARG);
if (!info->silent)
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
info->orig_url, location? location:"[none]", status_code);
if (!info->redirects_left)
{
if (!info->silent)
log_error (_("too many redirections\n"));
return gpg_error (GPG_ERR_NO_DATA);
}
info->redirects_left--;
if (!location || !*location)
return gpg_error (GPG_ERR_NO_DATA);
err = http_parse_uri (&locuri, location, 0);
if (err)
return err;
/* Make sure that an onion address only redirects to another
* onion address, or that a https address only redirects to a
* https address. */
if (info->orig_onion && !locuri->onion)
{
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect %u"
" redirect from onion to non-onion address"
" rejected",
err);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_FORBIDDEN);
}
if (!info->allow_downgrade && info->orig_https && !locuri->use_tls)
{
err = gpg_error (GPG_ERR_FORBIDDEN);
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect %u"
" redirect '%s' to '%s' rejected",
err, info->orig_url, location);
http_release_parsed_uri (locuri);
return err;
}
if (info->trust_location)
{
/* We trust the Location - return it verbatim. */
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
return err;
}
}
else if ((err = http_parse_uri (&origuri, info->orig_url, 0)))
{
http_release_parsed_uri (locuri);
return err;
}
else if (!info->restrict_redir || same_host_p (origuri, locuri))
{
/* Take the syntactically correct location or if restrict_redir
* is set the host is the same or on an exception list and thus
* we can take the location verbatim. */
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
newurl = xtrystrdup (location);
if (!newurl)
{
err = gpg_error_from_syserror ();
return err;
}
}
else /* Strictly rectricted redirection which we used in the past. */
{
/* We take only the host and port from the URL given in the
* Location. This limits the effects of redirection attacks by
* rogue hosts returning an URL to servers in the client's own
* network. We don't even include the userinfo because they
* should be considered similar to the path and query parts.
*/
if (!(locuri->off_path - locuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
if (!(origuri->off_path - origuri->off_host))
{
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return gpg_error (GPG_ERR_BAD_URI);
}
newurl = xtrymalloc (strlen (origuri->original)
+ (locuri->off_path - locuri->off_host) + 1);
if (!newurl)
{
err = gpg_error_from_syserror ();
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
return err;
}
/* Build new URL from
* uriguri: scheme userinfo ---- ---- path rest
* locuri: ------ -------- host port ---- ----
*/
p = newurl;
memcpy (p, origuri->original, origuri->off_host);
p += origuri->off_host;
memcpy (p, locuri->original + locuri->off_host,
(locuri->off_path - locuri->off_host));
p += locuri->off_path - locuri->off_host;
strcpy (p, origuri->original + origuri->off_path);
http_release_parsed_uri (origuri);
http_release_parsed_uri (locuri);
if (!info->silent)
log_info (_("redirection changed to '%s'\n"), newurl);
dirmngr_status_printf (info->ctrl, "WARNING",
"http_redirect_cleanup %u"
" changed from '%s' to '%s'",
0, info->orig_url, newurl);
}
*r_url = newurl;
return 0;
}
/* Return string describing the http STATUS. Returns an empty string
* for an unknown status. */
const char *
http_status2string (unsigned int status)
{
switch (status)
{
case 500: return "Internal Server Error";
case 501: return "Not Implemented";
case 502: return "Bad Gateway";
case 503: return "Service Unavailable";
case 504: return "Gateway Timeout";
case 505: return "HTTP version Not Supported";
case 506: return "Variant Also Negation";
case 507: return "Insufficient Storage";
case 508: return "Loop Detected";
case 510: return "Not Extended";
case 511: return "Network Authentication Required";
}
return "";
}
-/* Fucntion called on SIGHUP to flush internal variables. */
+/* Function called on SIGHUP to flush internal variables. */
void
http_reinitialize (void)
{
#ifdef HAVE_W32_SYSTEM
w32_get_internet_session (1); /* Clear our session. */
#endif /*HAVE_W32_SYSTEM*/
}
diff --git a/dirmngr/ks-engine-ldap.c b/dirmngr/ks-engine-ldap.c
index 688972a89..c2c57d819 100644
--- a/dirmngr/ks-engine-ldap.c
+++ b/dirmngr/ks-engine-ldap.c
@@ -1,3246 +1,3246 @@
/* ks-engine-ldap.c - talk to a LDAP keyserver
* Copyright (C) 2001, 2002, 2004, 2005, 2006
* 2007 Free Software Foundation, Inc.
* Copyright (C) 2015, 2020, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <time.h>
#include <unistd.h>
#include <stdlib.h>
#include <npth.h>
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# include <winsock2.h>
# include <winldap.h>
# include <winber.h>
# include <sddl.h>
#endif
#include "dirmngr.h"
#include "misc.h"
#include "../common/userids.h"
#include "../common/mbox-util.h"
#include "ks-action.h"
#include "ks-engine.h"
#include "ldap-misc.h"
#include "ldap-parse-uri.h"
#include "ldapserver.h"
/* Flags with infos from the connected server. */
#define SERVERINFO_REALLDAP 1 /* This is not the PGP keyserver. */
#define SERVERINFO_PGPKEYV2 2 /* Needs "pgpKeyV2" instead of "pgpKey"*/
#define SERVERINFO_SCHEMAV2 4 /* Version 2 of the Schema. */
#define SERVERINFO_NTDS 8 /* Server is an Active Directory. */
-#define SERVERINFO_GENERIC 16 /* Connected in genric mode. */
+#define SERVERINFO_GENERIC 16 /* Connected in generic mode. */
/* The page size requested from the server. */
#define PAGE_SIZE 100
#ifndef HAVE_TIMEGM
time_t timegm(struct tm *tm);
#endif
/* Object to keep state pertaining to this module. */
struct ks_engine_ldap_local_s
{
LDAP *ldap_conn;
LDAPMessage *message;
LDAPMessage *msg_iter; /* Iterator for message. */
unsigned int serverinfo;
int scope;
char *basedn;
char *keyspec;
char *filter;
struct berval *pagecookie;
unsigned int pageno; /* Current page number (starting at 1). */
unsigned int total; /* Total number of attributes read. */
int more_pages; /* More pages announced by server. */
};
/*-- prototypes --*/
static char *map_rid_to_dn (ctrl_t ctrl, const char *rid);
static char *basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri);
static time_t
ldap2epochtime (const char *timestr)
{
struct tm pgptime;
time_t answer;
memset (&pgptime, 0, sizeof(pgptime));
/* YYYYMMDDHHmmssZ */
sscanf (timestr, "%4d%2d%2d%2d%2d%2d",
&pgptime.tm_year,
&pgptime.tm_mon,
&pgptime.tm_mday,
&pgptime.tm_hour,
&pgptime.tm_min,
&pgptime.tm_sec);
pgptime.tm_year -= 1900;
pgptime.tm_isdst = -1;
pgptime.tm_mon--;
/* mktime() takes the timezone into account, so we use timegm() */
answer = timegm (&pgptime);
return answer;
}
/* Caller must free the result. */
static char *
tm2ldaptime (struct tm *tm)
{
struct tm tmp = *tm;
char buf[16];
/* YYYYMMDDHHmmssZ */
tmp.tm_year += 1900;
tmp.tm_mon ++;
snprintf (buf, sizeof buf, "%04d%02d%02d%02d%02d%02dZ",
tmp.tm_year,
tmp.tm_mon,
tmp.tm_mday,
tmp.tm_hour,
tmp.tm_min,
tmp.tm_sec);
return xstrdup (buf);
}
#if 0
/* Caller must free */
static char *
epoch2ldaptime (time_t stamp)
{
struct tm tm;
if (gmtime_r (&stamp, &tm))
return tm2ldaptime (&tm);
else
return xstrdup ("INVALID TIME");
}
#endif
static void
my_ldap_value_free (char **vals)
{
if (vals)
ldap_value_free (vals);
}
/* Print a description of supported variables. */
void
ks_ldap_help_variables (ctrl_t ctrl)
{
const char data[] =
"Supported variables in LDAP filter expressions:\n"
"\n"
"domain - The defaultNamingContext.\n"
"domain_admins - Group of domain admins.\n"
"domain_users - Group with all user accounts.\n"
"domain_guests - Group with the builtin gues account.\n"
"domain_computers - Group with all clients and servers.\n"
"cert_publishers - Group with all cert issuing computers.\n"
"protected_users - Group of users with extra protection.\n"
"key_admins - Group for delegated access to msdsKeyCredentialLink.\n"
"enterprise_key_admins - Similar to key_admins.\n"
"domain_domain_controllers - Group with all domain controllers.\n"
"sid_domain - SubAuthority numbers.\n";
ks_print_help (ctrl, data);
}
/* Helper function for substitute_vars. */
static const char *
getval_for_filter (void *cookie, const char *name)
{
ctrl_t ctrl = cookie;
const char *result = NULL;
if (!strcmp (name, "sid_domain"))
{
#ifdef HAVE_W32_SYSTEM
PSID mysid;
static char *sidstr;
char *s, *s0;
int i;
if (!sidstr)
{
mysid = w32_get_user_sid ();
if (!mysid)
{
gpg_err_set_errno (ENOENT);
goto leave;
}
if (!ConvertSidToStringSid (mysid, &sidstr))
{
gpg_err_set_errno (EINVAL);
goto leave;
}
/* Example for SIDSTR:
* S-1-5-21-3636969917-2569447256-918939550-1127 */
for (s0=NULL,s=sidstr,i=0; (s=strchr (s, '-')); i++)
{
s++;
if (i == 3)
s0 = s;
else if (i==6)
{
s[-1] = 0;
break;
}
}
if (!s0)
{
log_error ("oops: invalid SID received from OS");
gpg_err_set_errno (EINVAL);
LocalFree (sidstr);
goto leave;
}
sidstr = s0; /* (We never release SIDSTR thus no memmove.) */
}
result = sidstr;
#else
gpg_err_set_errno (ENOSYS);
goto leave;
#endif
}
else if (!strcmp (name, "domain"))
result = basedn_from_rootdse (ctrl, NULL);
else if (!strcmp (name, "domain_admins"))
result = map_rid_to_dn (ctrl, "512");
else if (!strcmp (name, "domain_users"))
result = map_rid_to_dn (ctrl, "513");
else if (!strcmp (name, "domain_guests"))
result = map_rid_to_dn (ctrl, "514");
else if (!strcmp (name, "domain_computers"))
result = map_rid_to_dn (ctrl, "515");
else if (!strcmp (name, "domain_domain_controllers"))
result = map_rid_to_dn (ctrl, "516");
else if (!strcmp (name, "cert_publishers"))
result = map_rid_to_dn (ctrl, "517");
else if (!strcmp (name, "protected_users"))
result = map_rid_to_dn (ctrl, "525");
else if (!strcmp (name, "key_admins"))
result = map_rid_to_dn (ctrl, "526");
else if (!strcmp (name, "enterprise_key_admins"))
result = map_rid_to_dn (ctrl, "527");
else
result = ""; /* Unknown variables are empty. */
leave:
return result;
}
/* Print a help output for the schemata supported by this module. */
gpg_error_t
ks_ldap_help (ctrl_t ctrl, parsed_uri_t uri)
{
const char data[] =
"Handler for LDAP URLs:\n"
" ldap://HOST:PORT/[BASEDN]????[bindname=BINDNAME,password=PASSWORD]\n"
"\n"
"Note: basedn, bindname and password need to be percent escaped. In\n"
"particular, spaces need to be replaced with %20 and commas with %2c.\n"
"Thus bindname will typically be of the form:\n"
"\n"
" uid=user%2cou=PGP%20Users%2cdc=EXAMPLE%2cdc=ORG\n"
"\n"
"The ldaps:// and ldapi:// schemes are also supported. If ldaps is used\n"
"then the server's certificate will be checked. If it is not valid, any\n"
"operation will be aborted. Note that ldaps means LDAP with STARTTLS\n"
"\n"
"As an alternative to an URL a string in this form may be used:\n"
"\n"
" HOST:PORT:BINDNAME:PASSWORD:BASEDN:FLAGS:\n"
"\n"
"The use of the percent sign or a colon in one of the string values is\n"
"currently not supported.\n"
"\n"
"Supported methods: search, get, put\n";
gpg_error_t err;
if(!uri)
err = ks_print_help (ctrl, " ldap");
else if (uri->is_ldap || uri->opaque)
err = ks_print_help (ctrl, data);
else
err = 0;
return err;
}
/* Create a new empty state object. Returns NULL on error */
static struct ks_engine_ldap_local_s *
ks_ldap_new_state (void)
{
struct ks_engine_ldap_local_s *state;
state = xtrycalloc (1, sizeof(struct ks_engine_ldap_local_s));
if (state)
state->scope = LDAP_SCOPE_SUBTREE;
return state;
}
/* Clear the state object STATE. Returns the STATE object. */
static struct ks_engine_ldap_local_s *
ks_ldap_clear_state (struct ks_engine_ldap_local_s *state)
{
if (state->ldap_conn)
{
ldap_unbind (state->ldap_conn);
state->ldap_conn = NULL;
}
if (state->message)
{
ldap_msgfree (state->message);
state->message = NULL;
}
if (state->pagecookie)
{
ber_bvfree (state->pagecookie);
state->pagecookie = NULL;
}
state->serverinfo = 0;
xfree (state->basedn);
state->scope = LDAP_SCOPE_SUBTREE;
state->basedn = NULL;
xfree (state->keyspec);
state->keyspec = NULL;
xfree (state->filter);
state->filter = NULL;
state->pageno = 0;
state->total = 0;
state->more_pages = 0;
return state;
}
/* Release a state object. */
void
ks_ldap_free_state (struct ks_engine_ldap_local_s *state)
{
if (!state)
return;
ks_ldap_clear_state (state);
xfree (state);
}
/* Helper for ks_ldap_get and ks_ldap_query. On return first_mode and
* next_mode are set accordingly. */
static gpg_error_t
ks_ldap_prepare_my_state (ctrl_t ctrl, unsigned int ks_get_flags,
int *first_mode, int *next_mode)
{
*first_mode = *next_mode = 0;
if ((ks_get_flags & KS_GET_FLAG_FIRST))
{
if (ctrl->ks_get_state)
ks_ldap_clear_state (ctrl->ks_get_state);
else if (!(ctrl->ks_get_state = ks_ldap_new_state ()))
return gpg_error_from_syserror ();
*first_mode = 1;
}
if ((ks_get_flags & KS_GET_FLAG_NEXT))
{
if (!ctrl->ks_get_state || !ctrl->ks_get_state->ldap_conn
|| !ctrl->ks_get_state->message)
{
log_error ("ks-ldap: --next requested but no state\n");
return gpg_error (GPG_ERR_INV_STATE);
}
*next_mode = 1;
}
/* Do not keep an old state around if not needed. */
if (!(*first_mode || *next_mode))
{
ks_ldap_free_state (ctrl->ks_get_state);
ctrl->ks_get_state = NULL;
}
return 0;
}
/* Convert a keyspec to a filter. Return an error if the keyspec is
bad or is not supported. The filter is escaped and returned in
*filter. It is the caller's responsibility to free *filter.
*filter is only set if this function returns success (i.e., 0). */
static gpg_error_t
keyspec_to_ldap_filter (const char *keyspec, char **filter, int only_exact,
unsigned int serverinfo)
{
/* Remove search type indicator and adjust PATTERN accordingly.
Note: don't include a preceding 0x when searching by keyid. */
/* XXX: Should we include disabled / revoke options? */
KEYDB_SEARCH_DESC desc;
char *f = NULL;
char *freeme = NULL;
char *p;
gpg_error_t err = classify_user_id (keyspec, &desc, 1);
if (err)
return err;
switch (desc.mode)
{
case KEYDB_SEARCH_MODE_EXACT:
f = xasprintf ("(pgpUserID=%s)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_SUBSTR:
if (! only_exact)
f = xasprintf ("(pgpUserID=*%s*)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_MAIL:
freeme = ldap_escape_filter (desc.u.name);
if (!freeme)
break;
if (*freeme == '<' && freeme[1] && freeme[2])
{
/* Strip angle brackets. Note that it is does not
* matter whether we work on the plan or LDAP escaped
* version of the mailbox. */
p = freeme + 1;
if (p[strlen(p)-1] == '>')
p[strlen(p)-1] = 0;
}
else
p = freeme;
if ((serverinfo & SERVERINFO_SCHEMAV2))
f = xasprintf ("(&(gpgMailbox=%s)(!(|(pgpRevoked=1)(pgpDisabled=1))))",
p);
else if (!only_exact)
f = xasprintf ("(pgpUserID=*<%s>*)", p);
break;
case KEYDB_SEARCH_MODE_MAILSUB:
if ((serverinfo & SERVERINFO_SCHEMAV2))
f = xasprintf("(&(gpgMailbox=*%s*)(!(|(pgpRevoked=1)(pgpDisabled=1))))",
(freeme = ldap_escape_filter (desc.u.name)));
else if (!only_exact)
f = xasprintf ("(pgpUserID=*<*%s*>*)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_MAILEND:
if ((serverinfo & SERVERINFO_SCHEMAV2))
f = xasprintf("(&(gpgMailbox=*%s)(!(|(pgpRevoked=1)(pgpDisabled=1))))",
(freeme = ldap_escape_filter (desc.u.name)));
else if (!only_exact)
f = xasprintf ("(pgpUserID=*<*%s>*)",
(freeme = ldap_escape_filter (desc.u.name)));
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
f = xasprintf ("(pgpKeyID=%08lX)", (ulong) desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
f = xasprintf ("(pgpCertID=%08lX%08lX)",
(ulong) desc.u.kid[0], (ulong) desc.u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
if ((serverinfo & SERVERINFO_SCHEMAV2))
{
freeme = bin2hex (desc.u.fpr, desc.fprlen, NULL);
if (!freeme)
return gpg_error_from_syserror ();
f = xasprintf ("(|(gpgFingerprint=%s)(gpgSubFingerprint=%s))",
freeme, freeme);
/* FIXME: For an exact search and in case of a match on
* gpgSubFingerprint we need to check that there is only one
* matching value. */
}
break;
case KEYDB_SEARCH_MODE_ISSUER:
case KEYDB_SEARCH_MODE_ISSUER_SN:
case KEYDB_SEARCH_MODE_SN:
case KEYDB_SEARCH_MODE_SUBJECT:
case KEYDB_SEARCH_MODE_KEYGRIP:
case KEYDB_SEARCH_MODE_WORDS:
case KEYDB_SEARCH_MODE_FIRST:
case KEYDB_SEARCH_MODE_NEXT:
default:
break;
}
xfree (freeme);
if (! f)
{
log_error ("Unsupported search mode.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
*filter = f;
return 0;
}
/* Helper for my_ldap_connect. */
static char *
interrogate_ldap_dn (LDAP *ldap_conn, const char *basedn_search,
unsigned int *r_serverinfo)
{
int lerr;
char **vals;
LDAPMessage *si_res;
int is_gnupg = 0;
char *basedn = NULL;
char *attr2[] = { "pgpBaseKeySpaceDN", "pgpVersion", "pgpSoftware", NULL };
char *object;
object = xasprintf ("cn=pgpServerInfo,%s", basedn_search);
npth_unprotect ();
lerr = ldap_search_s (ldap_conn, object, LDAP_SCOPE_BASE,
"(objectClass=*)", attr2, 0, &si_res);
npth_protect ();
xfree (object);
if (lerr == LDAP_SUCCESS)
{
vals = ldap_get_values (ldap_conn, si_res, "pgpBaseKeySpaceDN");
if (vals && vals[0])
basedn = xtrystrdup (vals[0]);
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, si_res, "pgpSoftware");
if (vals && vals[0])
{
if (opt.debug)
log_debug ("Server: \t%s\n", vals[0]);
if (!ascii_strcasecmp (vals[0], "GnuPG"))
is_gnupg = 1;
}
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, si_res, "pgpVersion");
if (vals && vals[0])
{
if (opt.debug)
log_debug ("Version:\t%s\n", vals[0]);
if (is_gnupg)
{
const char *fields[2];
int nfields;
nfields = split_fields (vals[0], fields, DIM(fields));
if (nfields > 0 && atoi(fields[0]) > 1)
*r_serverinfo |= SERVERINFO_SCHEMAV2;
if (nfields > 1
&& !ascii_strcasecmp (fields[1], "ntds"))
*r_serverinfo |= SERVERINFO_NTDS;
}
}
my_ldap_value_free (vals);
}
/* From man ldap_search_s: "res parameter of
ldap_search_ext_s() and ldap_search_s() should be
freed with ldap_msgfree() regardless of return
value of these functions. */
ldap_msgfree (si_res);
return basedn;
}
/* Connect to an LDAP server and interrogate it.
*
* URI describes the server to connect to and various options
* including whether to use TLS and the username and password (see
* ldap_parse_uri for a description of the various fields). Be
* default a PGP keyserver is assumed; if GENERIC is true a generic
* ldap connection is instead established.
*
* Returns: The ldap connection handle in *LDAP_CONNP, R_BASEDN is set
* to the base DN for the PGP key space, several flags will be stored
* at SERVERINFO, If you pass NULL, then the value won't be returned.
* It is the caller's responsibility to release *LDAP_CONNP with
* ldap_unbind and to xfree *BASEDNP. On error these variables are
* cleared.
*
* Note: On success, you still need to check that *BASEDNP is valid.
* If it is NULL, then the server does not appear to be an OpenPGP
* keyserver. */
static gpg_error_t
my_ldap_connect (parsed_uri_t uri, unsigned int generic, LDAP **ldap_connp,
char **r_basedn, char **r_host, int *r_use_tls,
unsigned int *r_serverinfo)
{
gpg_error_t err = 0;
int lerr;
ldap_server_t server = NULL;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
char *host = NULL; /* Host to use. */
int port; /* Port to use. */
int use_tls; /* 1 = starttls, 2 = ldap-over-tls */
int use_ntds; /* Use Active Directory authentication. */
int use_areconly; /* Lookup only via A record (Windows). */
const char *bindname;
const char *password;
const char *basedn_arg;
#ifndef HAVE_W32_SYSTEM
char *tmpstr;
#endif
if (r_basedn)
*r_basedn = NULL;
if (r_host)
*r_host = NULL;
if (r_use_tls)
*r_use_tls = 0;
*r_serverinfo = 0;
if (uri->opaque)
{
server = ldapserver_parse_one (uri->path, NULL, 0);
if (!server)
return gpg_error (GPG_ERR_LDAP_OTHER);
host = server->host;
port = server->port;
bindname = server->user;
password = bindname? server->pass : NULL;
basedn_arg = server->base;
use_tls = server->starttls? 1 : server->ldap_over_tls? 2 : 0;
use_ntds = server->ntds;
use_areconly = server->areconly;
}
else
{
host = uri->host;
port = uri->port;
bindname = uri->auth;
password = bindname? uri_query_value (uri, "password") : NULL;
basedn_arg = uri->path;
use_tls = uri->use_tls ? 1 : 0;
use_ntds = uri->ad_current;
use_areconly = 0;
}
if (!port)
port = use_tls == 2? 636 : 389;
if (host)
{
host = xtrystrdup (host);
if (!host)
{
err = gpg_error_from_syserror ();
goto out;
}
}
if (opt.verbose)
log_info ("ldap connect to '%s:%d:%s:%s:%s:%s%s%s'%s\n",
host, port,
basedn_arg ? basedn_arg : "",
bindname ? bindname : "",
password ? "*****" : "",
use_tls == 1? "starttls" : use_tls == 2? "ldaptls" : "plain",
use_ntds ? ",ntds":"",
use_areconly? ",areconly":"",
generic? " (generic)":"");
/* If the uri specifies a secure connection and we don't support
TLS, then fail; don't silently revert to an insecure
connection. */
if (use_tls)
{
#ifndef HAVE_LDAP_START_TLS_S
log_error ("ks-ldap: can't connect to the server: no TLS support.");
err = GPG_ERR_LDAP_NOT_SUPPORTED;
goto out;
#endif
}
#ifdef HAVE_W32_SYSTEM
/* Note that host==NULL uses the default domain controller. */
npth_unprotect ();
ldap_conn = ldap_sslinit (host, port, (use_tls == 2));
npth_protect ();
if (!ldap_conn)
{
lerr = LdapGetLastError ();
err = ldap_err_to_gpg_err (lerr);
log_error ("error initializing LDAP '%s:%d': %s\n",
host, port, ldap_err2string (lerr));
goto out;
}
if (use_areconly)
{
lerr = ldap_set_option (ldap_conn, LDAP_OPT_AREC_EXCLUSIVE, LDAP_OPT_ON);
if (lerr != LDAP_SUCCESS)
{
log_error ("ks-ldap: unable to set LDAP_OPT_AREC_EXLUSIVE: %s\n",
ldap_err2string (lerr));
err = ldap_err_to_gpg_err (lerr);
goto out;
}
}
#else /* Unix */
tmpstr = xtryasprintf ("%s://%s:%d",
use_tls == 2? "ldaps" : "ldap",
host, port);
if (!tmpstr)
{
err = gpg_error_from_syserror ();
goto out;
}
npth_unprotect ();
lerr = ldap_initialize (&ldap_conn, tmpstr);
npth_protect ();
if (lerr != LDAP_SUCCESS || !ldap_conn)
{
err = ldap_err_to_gpg_err (lerr);
log_error ("error initializing LDAP '%s': %s\n",
tmpstr, ldap_err2string (lerr));
xfree (tmpstr);
goto out;
}
xfree (tmpstr);
#endif /* Unix */
#ifdef HAVE_LDAP_SET_OPTION
{
int ver = LDAP_VERSION3;
lerr = ldap_set_option (ldap_conn, LDAP_OPT_PROTOCOL_VERSION, &ver);
if (lerr != LDAP_SUCCESS)
{
log_error ("ks-ldap: unable to go to LDAP 3: %s\n",
ldap_err2string (lerr));
err = ldap_err_to_gpg_err (lerr);
goto out;
}
}
if (opt.ldaptimeout)
{
int ver = opt.ldaptimeout;
/* fixme: also use LDAP_OPT_SEND_TIMEOUT? */
lerr = ldap_set_option (ldap_conn, LDAP_OPT_TIMELIMIT, &ver);
if (lerr != LDAP_SUCCESS)
{
log_error ("ks-ldap: unable to set LDAP timelimit to %us: %s\n",
opt.ldaptimeout, ldap_err2string (lerr));
err = ldap_err_to_gpg_err (lerr);
goto out;
}
if (opt.verbose)
log_info ("ldap timeout set to %us\n", opt.ldaptimeout);
}
#endif
#ifdef HAVE_LDAP_START_TLS_S
if (use_tls == 1)
{
#ifndef HAVE_W32_SYSTEM
int check_cert = LDAP_OPT_X_TLS_HARD; /* LDAP_OPT_X_TLS_NEVER */
lerr = ldap_set_option (ldap_conn,
LDAP_OPT_X_TLS_REQUIRE_CERT, &check_cert);
if (lerr)
{
log_error ("ldap: error setting an TLS option: %s\n",
ldap_err2string (lerr));
err = ldap_err_to_gpg_err (lerr);
goto out;
}
#else
/* On Windows, the certificates are checked by default. If the
option to disable checking mentioned above is ever
implemented, the way to do that on Windows is to install a
callback routine using ldap_set_option (..,
LDAP_OPT_SERVER_CERTIFICATE, ..); */
#endif
npth_unprotect ();
lerr = ldap_start_tls_s (ldap_conn,
#ifdef HAVE_W32_SYSTEM
/* ServerReturnValue, result */
NULL, NULL,
#endif
/* ServerControls, ClientControls */
NULL, NULL);
npth_protect ();
if (lerr)
{
log_error ("ldap: error switching to STARTTLS mode: %s\n",
ldap_err2string (lerr));
err = ldap_err_to_gpg_err (lerr);
goto out;
}
}
#endif
if (use_ntds)
{
#ifdef HAVE_W32_SYSTEM
npth_unprotect ();
lerr = ldap_bind_s (ldap_conn, NULL, NULL, LDAP_AUTH_NEGOTIATE);
npth_protect ();
if (lerr != LDAP_SUCCESS)
{
log_error ("error binding to LDAP via AD: %s\n",
ldap_err2string (lerr));
err = ldap_err_to_gpg_err (lerr);
goto out;
}
#else
log_error ("ldap: no Active Directory support but 'ntds' requested\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto out;
#endif
}
else if (bindname)
{
npth_unprotect ();
lerr = ldap_simple_bind_s (ldap_conn, bindname, password);
npth_protect ();
if (lerr != LDAP_SUCCESS)
{
log_error ("error binding to LDAP: %s\n", ldap_err2string (lerr));
err = ldap_err_to_gpg_err (lerr);
goto out;
}
}
else
{
/* By default we don't bind as there is usually no need to. */
}
if (generic)
{
/* Generic use of this function for arbitrary LDAP servers. */
*r_serverinfo |= SERVERINFO_GENERIC;
if (basedn_arg && *basedn_arg)
{
basedn = xtrystrdup (basedn_arg);
if (!basedn)
{
err = gpg_error_from_syserror ();
goto out;
}
}
}
else if (basedn_arg && *basedn_arg)
{
/* User specified base DN. In this case we know the server is a
* real LDAP server. */
const char *user_basedn = basedn_arg;
*r_serverinfo |= SERVERINFO_REALLDAP;
/* First try with provided basedn, else retry up one level.
* Retry assumes that provided entry is for keyspace,
* matching old behavior */
basedn = interrogate_ldap_dn (ldap_conn, user_basedn, r_serverinfo);
if (!basedn)
{
const char *basedn_parent = strchr (user_basedn, ',');
if (basedn_parent && *basedn_parent)
basedn = interrogate_ldap_dn (ldap_conn, basedn_parent + 1,
r_serverinfo);
}
}
else
{ /* Look for namingContexts. */
LDAPMessage *res = NULL;
char *attr[] = { "namingContexts", NULL };
npth_unprotect ();
lerr = ldap_search_s (ldap_conn, "", LDAP_SCOPE_BASE,
"(objectClass=*)", attr, 0, &res);
npth_protect ();
if (lerr == LDAP_SUCCESS)
{
char **context;
npth_unprotect ();
context = ldap_get_values (ldap_conn, res, "namingContexts");
npth_protect ();
if (context)
{
/* We found some, so try each namingContext as the
* search base and look for pgpBaseKeySpaceDN. Because
* we found this, we know we're talking to a regular-ish
* LDAP server and not an LDAP keyserver. */
int i;
*r_serverinfo |= SERVERINFO_REALLDAP;
for (i = 0; context[i] && !basedn; i++)
basedn = interrogate_ldap_dn (ldap_conn, context[i],
r_serverinfo);
ldap_value_free (context);
}
}
else /* ldap_search failed. */
{
/* We don't have an answer yet, which means the server might
be a PGP.com keyserver. */
char **vals;
LDAPMessage *si_res = NULL;
char *attr2[] = { "pgpBaseKeySpaceDN", "version", "software", NULL };
npth_unprotect ();
lerr = ldap_search_s (ldap_conn, "cn=pgpServerInfo", LDAP_SCOPE_BASE,
"(objectClass=*)", attr2, 0, &si_res);
npth_protect ();
if (lerr == LDAP_SUCCESS)
{
/* For the PGP LDAP keyserver, this is always
* "OU=ACTIVE,O=PGP KEYSPACE,C=US", but it might not be
* in the future. */
vals = ldap_get_values (ldap_conn, si_res, "baseKeySpaceDN");
if (vals && vals[0])
{
basedn = xtrystrdup (vals[0]);
}
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, si_res, "software");
if (vals && vals[0])
{
if (opt.debug)
log_debug ("ks-ldap: PGP Server: \t%s\n", vals[0]);
}
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, si_res, "version");
if (vals && vals[0])
{
if (opt.debug)
log_debug ("ks-ldap: PGP Server Version:\t%s\n", vals[0]);
/* If the version is high enough, use the new
pgpKeyV2 attribute. This design is iffy at best,
but it matches how PGP does it. I figure the NAI
folks assumed that there would never be an LDAP
keyserver vendor with a different numbering
scheme. */
if (atoi (vals[0]) > 1)
*r_serverinfo |= SERVERINFO_PGPKEYV2;
}
my_ldap_value_free (vals);
}
ldap_msgfree (si_res);
}
/* From man ldap_search_s: "res parameter of ldap_search_ext_s()
and ldap_search_s() should be freed with ldap_msgfree()
regardless of return value of these functions. */
ldap_msgfree (res);
}
out:
if (!err && opt.debug)
{
log_debug ("ldap_conn: %p\n", ldap_conn);
log_debug ("server_type: %s\n",
((*r_serverinfo & SERVERINFO_GENERIC)
? "Generic" :
(*r_serverinfo & SERVERINFO_REALLDAP)
? "LDAP" : "PGP.com keyserver") );
log_debug ("basedn: %s\n", basedn);
if (!(*r_serverinfo & SERVERINFO_GENERIC))
log_debug ("pgpkeyattr: %s\n",
(*r_serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey");
}
ldapserver_list_free (server);
if (err)
{
xfree (basedn);
if (ldap_conn)
ldap_unbind (ldap_conn);
}
else
{
if (r_basedn)
*r_basedn = basedn;
else
xfree (basedn);
if (r_host)
*r_host = host;
else
xfree (host);
*ldap_connp = ldap_conn;
}
return err;
}
/* Extract keys from an LDAP reply and write them out to the output
stream OUTPUT in a format GnuPG can import (either the OpenPGP
binary format or armored format). */
static void
extract_keys (estream_t output,
LDAP *ldap_conn, const char *certid, LDAPMessage *message)
{
char **vals;
es_fprintf (output, "INFO %s BEGIN\n", certid);
/* Note: ldap_get_values returns a NULL terminated array of
strings. */
vals = ldap_get_values (ldap_conn, message, "gpgfingerprint");
if (vals && vals[0] && vals[0][0])
es_fprintf (output, "pub:%s:", vals[0]);
else
es_fprintf (output, "pub:%s:", certid);
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, message, "pgpkeytype");
if (vals && vals[0])
{
if (strcmp (vals[0], "RSA") == 0)
es_fprintf (output, "1");
else if (strcmp (vals[0],"DSS/DH") == 0)
es_fprintf (output, "17");
}
my_ldap_value_free (vals);
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgpkeysize");
if (vals && vals[0])
{
int v = atoi (vals[0]);
if (v > 0)
es_fprintf (output, "%d", v);
}
my_ldap_value_free (vals);
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgpkeycreatetime");
if (vals && vals[0])
{
if (strlen (vals[0]) == 15)
es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
}
my_ldap_value_free (vals);
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgpkeyexpiretime");
if (vals && vals[0])
{
if (strlen (vals[0]) == 15)
es_fprintf (output, "%u", (unsigned int) ldap2epochtime (vals[0]));
}
my_ldap_value_free (vals);
es_fprintf (output, ":");
vals = ldap_get_values (ldap_conn, message, "pgprevoked");
if (vals && vals[0])
{
if (atoi (vals[0]) == 1)
es_fprintf (output, "r");
}
my_ldap_value_free (vals);
es_fprintf (output, "\n");
vals = ldap_get_values (ldap_conn, message, "pgpuserid");
if (vals && vals[0])
{
int i;
for (i = 0; vals[i]; i++)
es_fprintf (output, "uid:%s\n", vals[i]);
}
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, message, "modifyTimestamp");
if (vals && vals[0])
{
gnupg_isotime_t atime;
if (!rfc4517toisotime (atime, vals[0]))
es_fprintf (output, "chg:%s:\n", atime);
}
my_ldap_value_free (vals);
es_fprintf (output, "INFO %s END\n", certid);
}
/* For now we do not support LDAP over Tor. */
static gpg_error_t
no_ldap_due_to_tor (ctrl_t ctrl)
{
gpg_error_t err = gpg_error (GPG_ERR_NOT_SUPPORTED);
const char *msg = _("LDAP access not possible due to Tor mode");
log_error ("%s", msg);
dirmngr_status_printf (ctrl, "NOTE", "no_ldap_due_to_tor %u %s", err, msg);
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
/* Helper for ks_ldap_get. Returns 0 if a key was fetched and printed
* to FP. The error code GPG_ERR_NO_DATA is returned if no key was
* printed. Note that FP is updated by this function. */
static gpg_error_t
return_one_keyblock (LDAP *ldap_conn, LDAPMessage *msg, unsigned int serverinfo,
estream_t *fp, strlist_t *seenp)
{
gpg_error_t err;
char **vals;
char **certid;
/* Use the long keyid to remove duplicates. The LDAP server returns
* the same keyid more than once if there are multiple user IDs on
* the key. Note that this does NOT mean that a keyid that exists
* multiple times on the keyserver will not be fetched. It means
* that each KEY, no matter how many user IDs share its keyid, will
* be fetched only once. If a keyid that belongs to more than one
* key is fetched, the server quite properly responds with all
* matching keys. -ds
*
* Note that in --first/--next mode we don't do any duplicate
* detection.
*/
certid = ldap_get_values (ldap_conn, msg, "pgpcertid");
if (certid && certid[0])
{
if (!seenp || !strlist_find (*seenp, certid[0]))
{
/* It's not a duplicate, add it */
if (seenp)
add_to_strlist (seenp, certid[0]);
if (!*fp)
{
*fp = es_fopenmem(0, "rw");
if (!*fp)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
extract_keys (*fp, ldap_conn, certid[0], msg);
vals = ldap_get_values (ldap_conn, msg,
(serverinfo & SERVERINFO_PGPKEYV2)?
"pgpKeyV2" : "pgpKey");
if (!vals)
{
err = ldap_to_gpg_err (ldap_conn);
log_error("ks-ldap: unable to retrieve key %s "
"from keyserver\n", certid[0]);
}
else
{
/* We should strip the new lines. */
es_fprintf (*fp, "KEY 0x%s BEGIN\n", certid[0]);
es_fputs (vals[0], *fp);
es_fprintf (*fp, "\nKEY 0x%s END\n", certid[0]);
ldap_value_free (vals);
err = 0;
}
}
else /* Duplicate. */
err = gpg_error (GPG_ERR_NO_DATA);
}
else
err = gpg_error (GPG_ERR_NO_DATA);
leave:
my_ldap_value_free (certid);
return err;
}
/* Helper for ks_ldap_query. Returns 0 if an attr was fetched and
* printed to FP. The error code GPG_ERR_NO_DATA is returned if no
* data was printed. Note that FP is updated by this function. */
static gpg_error_t
return_all_attributes (LDAP *ld, LDAPMessage *msg, estream_t *fp)
{
gpg_error_t err = 0;
BerElement *berctx = NULL;
char *attr = NULL;
const char *attrprefix;
struct berval **values = NULL;
int idx;
int any = 0;
const char *s;
const char *val;
size_t len;
char *mydn;
mydn = ldap_get_dn (ld, msg);
if (!*fp)
{
*fp = es_fopenmem(0, "rw");
if (!*fp)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* Always print the DN - note that by using only unbkown attributes
- * it is pissible to list just the DNs with out addiional
+ * it is possible to list just the DNs with out additional
* linefeeds. */
es_fprintf (*fp, "Dn: %s\n", mydn? mydn : "[oops DN missing]");
for (npth_unprotect (), attr = ldap_first_attribute (ld, msg, &berctx),
npth_protect ();
attr;
npth_unprotect (), attr = ldap_next_attribute (ld, msg, berctx),
npth_protect ())
{
npth_unprotect ();
values = ldap_get_values_len (ld, msg, attr);
npth_protect ();
if (!values)
{
if (opt.verbose)
log_info ("attribute '%s' not found\n", attr);
ldap_memfree (attr);
attr = NULL;
continue;
}
any = 1;
if (opt.verbose > 1)
{
log_info ("found attribute '%s'\n", attr);
for (idx=0; values[idx]; idx++)
log_info (" length[%d]=%d\n",
idx, (int)values[0]->bv_len);
}
if (!ascii_strcasecmp (attr, "Dn"))
attrprefix = "X-";
else if (*attr == '#')
attrprefix = "X-hash-";
else if (*attr == ' ')
attrprefix = "X-blank-";
else
attrprefix = "";
/* FIXME: We should remap all invalid chars in ATTR. */
for (idx=0; values[idx]; idx++)
{
es_fprintf (*fp, "%s%s: ", attrprefix, attr);
val = values[idx]->bv_val;
len = values[idx]->bv_len;
while (len && (s = memchr (val, '\n', len)))
{
- s++; /* We als want to print the LF. */
+ s++; /* We also want to print the LF. */
if (es_fwrite (val, s - val, 1, *fp) != 1)
goto fwrite_failed;
len -= (s-val);
val = s;
if (len && es_fwrite (" ", 1, 1, *fp) != 1)
goto fwrite_failed;
}
if (len && es_fwrite (val, len, 1, *fp) != 1)
goto fwrite_failed;
if (es_fwrite ("\n", 1, 1, *fp) != 1) /* Final LF. */
goto fwrite_failed;
}
ldap_value_free_len (values);
values = NULL;
ldap_memfree (attr);
attr = NULL;
}
/* One final linefeed to prettify the output. */
if (any && es_fwrite ("\n", 1, 1, *fp) != 1)
goto fwrite_failed;
leave:
if (values)
ldap_value_free_len (values);
ldap_memfree (attr);
if (mydn)
ldap_memfree (mydn);
ber_free (berctx, 0);
return err;
fwrite_failed:
err = gpg_error_from_syserror ();
log_error ("error writing to stdout: %s\n", gpg_strerror (err));
goto leave;
}
/* Helper for ks_ldap_get and ks_ldap_query. Note that KEYSPEC is
* only used for diagnostics. */
static gpg_error_t
search_and_parse (ctrl_t ctrl, const char *keyspec,
LDAP *ldap_conn, char *basedn, int scope, char *filter,
char **attrs, LDAPMessage **r_message)
{
gpg_error_t err = 0;
int l_err, l_reserr;
LDAPControl *srvctrls[2] = { NULL, NULL };
int count;
unsigned int totalcount = 0;
LDAPControl *pagectrl = NULL;
LDAPControl **resctrls = NULL;
/* first/next mode is used to retrieve many entries; thus we should
* use paged results. We assume first/next mode if we have a state.
* We make the paged mode non-critical so that we get at least as
* many entries the server delivers anyway. */
if (ctrl->ks_get_state)
{
l_err = ldap_create_page_control (ldap_conn, PAGE_SIZE,
ctrl->ks_get_state->pagecookie, 0,
&pagectrl);
if (err)
{
err = ldap_err_to_gpg_err (l_err);
log_error ("ks-ldap: create_page_control failed: %s\n",
ldap_err2string (l_err));
goto leave;
}
ctrl->ks_get_state->more_pages = 0;
srvctrls[0] = pagectrl;
}
npth_unprotect ();
l_err = ldap_search_ext_s (ldap_conn, basedn, scope,
filter, attrs, 0,
srvctrls[0]? srvctrls : NULL, NULL, NULL, 0,
r_message);
npth_protect ();
if (l_err)
{
err = ldap_err_to_gpg_err (l_err);
log_error ("ks-ldap: LDAP search error: %s\n", ldap_err2string (l_err));
goto leave;
}
if (ctrl->ks_get_state)
{
l_err = ldap_parse_result (ldap_conn, *r_message, &l_reserr,
NULL, NULL, NULL, &resctrls, 0);
if (l_err)
{
err = ldap_err_to_gpg_err (l_err);
log_error ("ks-ldap: LDAP parse result error: %s\n",
ldap_err2string (l_err));
goto leave;
}
/* Get the current cookie. */
if (ctrl->ks_get_state->pagecookie)
{
ber_bvfree (ctrl->ks_get_state->pagecookie);
ctrl->ks_get_state->pagecookie = NULL;
}
l_err = ldap_parse_page_control (ldap_conn, resctrls,
&totalcount,
&ctrl->ks_get_state->pagecookie);
if (l_err)
{
err = ldap_err_to_gpg_err (l_err);
log_error ("ks-ldap: LDAP parse page control error: %s\n",
ldap_err2string (l_err));
goto leave;
}
ctrl->ks_get_state->pageno++;
/* Decide whether there will be more pages. */
ctrl->ks_get_state->more_pages =
(ctrl->ks_get_state->pagecookie
&& ctrl->ks_get_state->pagecookie->bv_val
&& *ctrl->ks_get_state->pagecookie->bv_val);
srvctrls[0] = NULL;
}
count = ldap_count_entries (ldap_conn, *r_message);
if (ctrl->ks_get_state)
{
if (count >= 0)
ctrl->ks_get_state->total += count;
if (opt.verbose)
log_info ("ks-ldap: received result page %u%s (%d/%u/%u)\n",
ctrl->ks_get_state->pageno,
ctrl->ks_get_state->more_pages? "":" (last)",
count, ctrl->ks_get_state->total, totalcount);
}
if (count < 1)
{
if (!ctrl->ks_get_state || ctrl->ks_get_state->pageno == 1)
log_info ("ks-ldap: '%s' not found on LDAP server\n", keyspec);
if (count == -1)
err = ldap_to_gpg_err (ldap_conn);
else
err = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
leave:
if (resctrls)
ldap_controls_free (resctrls);
if (pagectrl)
ldap_control_free (pagectrl);
return err;
}
/* Fetch all entries from the RootDSE and return them as a name value
* object. */
static nvc_t
fetch_rootdse (ctrl_t ctrl, parsed_uri_t uri)
{
gpg_error_t err;
estream_t infp = NULL;
uri_item_t puri; /* The broken down URI (only one item used). */
nvc_t nvc = NULL;
/* FIXME: We need the unparsed URI here - use uri_item_t instead
* of fix the parser to fill in original */
err = ks_action_parse_uri (uri && uri->original? uri->original : "ldap://",
&puri);
if (err)
return NULL;
/* Reset authentication for a serverless. */
puri->parsed_uri->ad_current = 0;
puri->parsed_uri->auth = NULL;
if (!strcmp (puri->parsed_uri->scheme, "ldap")
|| !strcmp (puri->parsed_uri->scheme, "ldaps")
|| !strcmp (puri->parsed_uri->scheme, "ldapi")
|| puri->parsed_uri->opaque)
{
err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_ROOTDSE,
"^&base&(objectclass=*)", NULL, NULL, &infp);
if (err)
log_error ("ldap: reading the rootDES failed: %s\n",
gpg_strerror (err));
else if ((err = nvc_parse (&nvc, NULL, infp)))
log_error ("parsing the rootDES failed: %s\n", gpg_strerror (err));
}
es_fclose (infp);
release_uri_item_list (puri);
if (err)
{
nvc_release (nvc);
nvc = NULL;
}
return nvc;
}
/* Return the DN for the given RID. This is used with the Active
* Directory. */
static char *
map_rid_to_dn (ctrl_t ctrl, const char *rid)
{
gpg_error_t err;
char *result = NULL;
estream_t infp = NULL;
uri_item_t puri; /* The broken down URI. */
nvc_t nvc = NULL;
char *filter = NULL;
const char *s;
char *attr[2] = {"dn", NULL};
err = ks_action_parse_uri ("ldap:///", &puri);
if (err)
return NULL;
filter = strconcat ("(objectSid=S-1-5-21-$sid_domain-", rid, ")", NULL);
if (!filter)
goto leave;
err = ks_ldap_query (ctrl, puri->parsed_uri, KS_GET_FLAG_SUBST,
filter, attr, NULL, &infp);
if (err)
{
log_error ("ldap: AD query '%s' failed: %s\n", filter,gpg_strerror (err));
goto leave;
}
if ((err = nvc_parse (&nvc, NULL, infp)))
{
log_error ("ldap: parsing the result failed: %s\n",gpg_strerror (err));
goto leave;
}
if (!(s = nvc_get_string (nvc, "Dn:")))
{
err = gpg_error (GPG_ERR_NOT_FOUND);
log_error ("ldap: mapping rid '%s'failed: %s\n", rid, gpg_strerror (err));
goto leave;
}
result = xtrystrdup (s);
if (!result)
{
err = gpg_error_from_syserror ();
log_error ("ldap: strdup failed: %s\n", gpg_strerror (err));
goto leave;
}
leave:
es_fclose (infp);
release_uri_item_list (puri);
xfree (filter);
nvc_release (nvc);
return result;
}
/* Return the baseDN for URI which might have already been cached for
* this session. */
static char *
basedn_from_rootdse (ctrl_t ctrl, parsed_uri_t uri)
{
const char *s;
if (!ctrl->rootdse && !ctrl->rootdse_tried)
{
ctrl->rootdse = fetch_rootdse (ctrl, uri);
ctrl->rootdse_tried = 1;
if (ctrl->rootdse)
{
log_debug ("Dump of all rootDSE attributes:\n");
nvc_write (ctrl->rootdse, log_get_stream ());
log_debug ("End of dump\n");
}
}
s = nvc_get_string (ctrl->rootdse, "defaultNamingContext:");
return s? xtrystrdup (s): NULL;
}
/* Get the key described key the KEYSPEC string from the keyserver
* identified by URI. On success R_FP has an open stream to read the
* data. KS_GET_FLAGS conveys flags from the client. */
gpg_error_t
ks_ldap_get (ctrl_t ctrl, parsed_uri_t uri, const char *keyspec,
unsigned int ks_get_flags, gnupg_isotime_t newer, estream_t *r_fp)
{
gpg_error_t err;
unsigned int serverinfo;
char *host = NULL;
int use_tls;
char *filter = NULL;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
int scope = LDAP_SCOPE_SUBTREE;
estream_t fp = NULL;
LDAPMessage *message = NULL;
LDAPMessage *msg;
int anykey = 0;
int first_mode = 0;
int next_mode = 0;
int get_first;
strlist_t seen = NULL; /* The set of entries that we've seen. */
/* The ordering is significant. Specifically, "pgpcertid" needs to
* be the second item in the list, since everything after it may be
* discarded if we aren't in verbose mode. */
char *attrs[] =
{
"dummy", /* (to be be replaced.) */
"pgpcertid", "pgpuserid", "pgpkeyid", "pgprevoked", "pgpdisabled",
"pgpkeycreatetime", "modifyTimestamp", "pgpkeysize", "pgpkeytype",
"gpgfingerprint",
NULL
};
if (dirmngr_use_tor ())
{
return no_ldap_due_to_tor (ctrl);
}
err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode);
if (err)
return err;
if (next_mode)
{
next_again:
if (!ctrl->ks_get_state->msg_iter && ctrl->ks_get_state->more_pages)
{
/* Get the next page of results. */
if (ctrl->ks_get_state->message)
{
ldap_msgfree (ctrl->ks_get_state->message);
ctrl->ks_get_state->message = NULL;
}
attrs[0] = ((ctrl->ks_get_state->serverinfo & SERVERINFO_PGPKEYV2)?
"pgpKeyV2" : "pgpKey");
err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec,
ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->basedn,
ctrl->ks_get_state->scope,
ctrl->ks_get_state->filter,
attrs,
&ctrl->ks_get_state->message);
if (err)
goto leave;
ctrl->ks_get_state->msg_iter = ctrl->ks_get_state->message;
get_first = 1;
}
else
get_first = 0;
while (ctrl->ks_get_state->msg_iter)
{
npth_unprotect ();
ctrl->ks_get_state->msg_iter
= get_first? ldap_first_entry (ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->msg_iter)
/* */ : ldap_next_entry (ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->msg_iter);
npth_protect ();
get_first = 0;
if (ctrl->ks_get_state->msg_iter)
{
err = return_one_keyblock (ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->msg_iter,
ctrl->ks_get_state->serverinfo,
&fp, NULL);
if (!err)
break; /* Found. */
else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0; /* Skip empty attributes. */
else
goto leave;
}
}
if (!ctrl->ks_get_state->msg_iter || !fp)
{
ctrl->ks_get_state->msg_iter = NULL;
if (ctrl->ks_get_state->more_pages)
goto next_again;
err = gpg_error (GPG_ERR_NO_DATA);
}
}
else /* Not in --next mode. */
{
/* Make sure we are talking to an OpenPGP LDAP server. */
err = my_ldap_connect (uri, 0, &ldap_conn,
&basedn, &host, &use_tls, &serverinfo);
if (err || !basedn)
{
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Now that we have information about the server we can construct a
* query best suited for the capabilities of the server. */
if (first_mode && !*keyspec)
{
filter = xtrystrdup("(!(|(pgpRevoked=1)(pgpDisabled=1)))");
err = filter? 0 : gpg_error_from_syserror ();
}
else
err = keyspec_to_ldap_filter (keyspec, &filter, 1, serverinfo);
if (err)
goto leave;
if (*newer)
{
char *tstr, *fstr;
tstr = isotime2rfc4517 (newer);
if (!tstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
fstr = strconcat ("(&", filter,
"(modifyTimestamp>=", tstr, "))", NULL);
xfree (tstr);
if (!fstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (filter);
filter = fstr;
}
if (opt.debug)
log_debug ("ks-ldap: using filter: %s\n", filter);
/* Replace "dummy". */
attrs[0] = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2" : "pgpKey";
err = search_and_parse (ctrl, keyspec, ldap_conn, basedn, scope,
filter, attrs, &message);
if (err)
goto leave;
for (npth_unprotect (),
msg = ldap_first_entry (ldap_conn, message),
npth_protect ();
msg;
npth_unprotect (),
msg = ldap_next_entry (ldap_conn, msg),
npth_protect ())
{
err = return_one_keyblock (ldap_conn, msg, serverinfo,
&fp, first_mode? NULL : &seen);
if (!err)
{
anykey = 1;
if (first_mode)
break;
}
else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0; /* Skip empty/duplicate attributes. */
else
goto leave;
}
if (ctrl->ks_get_state) /* Save the iterator. */
ctrl->ks_get_state->msg_iter = msg;
if (!fp) /* Nothing was found. */
err = gpg_error (GPG_ERR_NO_DATA);
if (!err && anykey)
err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s",
use_tls? "ldaps" : "ldap",
host? host:"");
}
leave:
/* Store our state if needed. */
if (!err && (ks_get_flags & KS_GET_FLAG_FIRST))
{
log_assert (!ctrl->ks_get_state->ldap_conn);
ctrl->ks_get_state->ldap_conn = ldap_conn;
ldap_conn = NULL;
log_assert (!ctrl->ks_get_state->message);
ctrl->ks_get_state->message = message;
message = NULL;
ctrl->ks_get_state->serverinfo = serverinfo;
ctrl->ks_get_state->scope = scope;
ctrl->ks_get_state->basedn = basedn;
basedn = NULL;
ctrl->ks_get_state->keyspec = keyspec? xtrystrdup (keyspec) : NULL;
ctrl->ks_get_state->filter = filter;
filter = NULL;
}
if ((ks_get_flags & KS_GET_FLAG_NEXT))
{
/* Keep the state in --next mode even with errors. */
ldap_conn = NULL;
message = NULL;
}
if (message)
ldap_msgfree (message);
if (err)
es_fclose (fp);
else
{
if (fp)
es_fseek (fp, 0, SEEK_SET);
*r_fp = fp;
}
free_strlist (seen);
xfree (basedn);
xfree (host);
if (ldap_conn)
ldap_unbind (ldap_conn);
xfree (filter);
return err;
}
/* Search the keyserver identified by URI for keys matching PATTERN.
On success R_FP has an open stream to read the data. */
gpg_error_t
ks_ldap_search (ctrl_t ctrl, parsed_uri_t uri, const char *pattern,
estream_t *r_fp)
{
gpg_error_t err;
int ldap_err;
unsigned int serverinfo;
char *filter = NULL;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
estream_t fp = NULL;
(void) ctrl;
if (dirmngr_use_tor ())
{
return no_ldap_due_to_tor (ctrl);
}
/* Make sure we are talking to an OpenPGP LDAP server. */
err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
if (err || !basedn)
{
if (!err)
err = GPG_ERR_GENERAL;
goto out;
}
/* Now that we have information about the server we can construct a
* query best suited for the capabilities of the server. */
err = keyspec_to_ldap_filter (pattern, &filter, 0, serverinfo);
if (err)
{
log_error ("Bad search pattern: '%s'\n", pattern);
goto out;
}
/* Even if we have no results, we want to return a stream. */
fp = es_fopenmem(0, "rw");
if (!fp)
{
err = gpg_error_from_syserror ();
goto out;
}
{
char **vals;
LDAPMessage *res, *each;
int count = 0;
strlist_t dupelist = NULL;
/* The maximum size of the search, including the optional stuff
and the trailing \0 */
char *attrs[] =
{
"pgpcertid", "pgpuserid", "pgprevoked", "pgpdisabled",
"pgpkeycreatetime", "pgpkeyexpiretime", "modifyTimestamp",
"pgpkeysize", "pgpkeytype", "gpgfingerprint",
NULL
};
if (opt.debug)
log_debug ("SEARCH '%s' => '%s' BEGIN\n", pattern, filter);
npth_unprotect ();
ldap_err = ldap_search_s (ldap_conn, basedn,
LDAP_SCOPE_SUBTREE, filter, attrs, 0, &res);
npth_protect ();
xfree (filter);
filter = NULL;
if (ldap_err != LDAP_SUCCESS && ldap_err != LDAP_SIZELIMIT_EXCEEDED)
{
err = ldap_err_to_gpg_err (ldap_err);
log_error ("SEARCH %s FAILED %d\n", pattern, err);
log_error ("ks-ldap: LDAP search error: %s\n",
ldap_err2string (err));
goto out;
}
/* The LDAP server doesn't return a real count of unique keys, so we
can't use ldap_count_entries here. */
for (npth_unprotect (),
each = ldap_first_entry (ldap_conn, res),
npth_protect ();
each;
npth_unprotect (),
each = ldap_next_entry (ldap_conn, each),
npth_protect ())
{
char **certid = ldap_get_values (ldap_conn, each, "pgpcertid");
if (certid && certid[0] && ! strlist_find (dupelist, certid[0]))
{
add_to_strlist (&dupelist, certid[0]);
count++;
}
my_ldap_value_free (certid);
}
if (ldap_err == LDAP_SIZELIMIT_EXCEEDED)
{
if (count == 1)
log_error ("ks-ldap: search results exceeded server limit."
" First 1 result shown.\n");
else
log_error ("ks-ldap: search results exceeded server limit."
" First %d results shown.\n", count);
}
free_strlist (dupelist);
dupelist = NULL;
if (count < 1)
es_fputs ("info:1:0\n", fp);
else
{
es_fprintf (fp, "info:1:%d\n", count);
for (each = ldap_first_entry (ldap_conn, res);
each;
each = ldap_next_entry (ldap_conn, each))
{
char **certid;
LDAPMessage *uids;
certid = ldap_get_values (ldap_conn, each, "pgpcertid");
if (!certid || !certid[0])
{
my_ldap_value_free (certid);
continue;
}
/* Have we seen this certid before? */
if (! strlist_find (dupelist, certid[0]))
{
add_to_strlist (&dupelist, certid[0]);
vals = ldap_get_values (ldap_conn, each, "gpgfingerprint");
if (vals && vals[0] && vals[0][0])
es_fprintf (fp, "pub:%s:", vals[0]);
else
es_fprintf (fp, "pub:%s:", certid[0]);
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, each, "pgpkeytype");
if (vals && vals[0])
{
/* The LDAP server doesn't exactly handle this
well. */
if (strcasecmp (vals[0], "RSA") == 0)
es_fputs ("1", fp);
else if (strcasecmp (vals[0], "DSS/DH") == 0)
es_fputs ("17", fp);
}
my_ldap_value_free (vals);
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "pgpkeysize");
if (vals && vals[0])
{
/* Not sure why, but some keys are listed with a
key size of 0. Treat that like an unknown. */
if (atoi (vals[0]) > 0)
es_fprintf (fp, "%d", atoi (vals[0]));
}
my_ldap_value_free (vals);
es_fputc (':', fp);
/* YYYYMMDDHHmmssZ */
vals = ldap_get_values (ldap_conn, each, "pgpkeycreatetime");
if(vals && vals[0] && strlen (vals[0]) == 15)
{
es_fprintf (fp, "%u",
(unsigned int) ldap2epochtime(vals[0]));
}
my_ldap_value_free (vals);
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "pgpkeyexpiretime");
if (vals && vals[0] && strlen (vals[0]) == 15)
{
es_fprintf (fp, "%u",
(unsigned int) ldap2epochtime (vals[0]));
}
my_ldap_value_free (vals);
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "pgprevoked");
if (vals && vals[0])
{
if (atoi (vals[0]) == 1)
es_fprintf (fp, "r");
}
my_ldap_value_free (vals);
vals = ldap_get_values (ldap_conn, each, "pgpdisabled");
if (vals && vals[0])
{
if (atoi (vals[0]) ==1)
es_fprintf (fp, "d");
}
my_ldap_value_free (vals);
es_fputc (':', fp);
vals = ldap_get_values (ldap_conn, each, "modifyTimestamp");
if(vals && vals[0])
{
gnupg_isotime_t atime;
if (rfc4517toisotime (atime, vals[0]))
*atime = 0;
es_fprintf (fp, "%s", atime);
}
my_ldap_value_free (vals);
es_fprintf (fp, "\n");
/* Now print all the uids that have this certid */
for (uids = ldap_first_entry (ldap_conn, res);
uids;
uids = ldap_next_entry (ldap_conn, uids))
{
vals = ldap_get_values (ldap_conn, uids, "pgpcertid");
if (!vals || !vals[0])
{
my_ldap_value_free (vals);
continue;
}
if (!ascii_strcasecmp (certid[0], vals[0]))
{
char **uidvals;
es_fprintf (fp, "uid:");
uidvals = ldap_get_values (ldap_conn,
uids, "pgpuserid");
if (uidvals)
{
/* Need to percent escape any colons */
char *quoted = try_percent_escape (uidvals[0],
NULL);
if (quoted)
es_fputs (quoted, fp);
xfree (quoted);
}
my_ldap_value_free (uidvals);
es_fprintf (fp, "\n");
}
ldap_value_free(vals);
}
}
my_ldap_value_free (certid);
}
}
ldap_msgfree (res);
free_strlist (dupelist);
}
if (opt.debug)
log_debug ("SEARCH %s END\n", pattern);
out:
if (err)
{
es_fclose (fp);
}
else
{
/* Return the read stream. */
if (fp)
es_fseek (fp, 0, SEEK_SET);
*r_fp = fp;
}
xfree (basedn);
if (ldap_conn)
ldap_unbind (ldap_conn);
xfree (filter);
return err;
}
/* A modlist describes a set of changes to an LDAP entry. (An entry
consists of 1 or more attributes. Attributes are <name, value>
pairs. Note: an attribute may be multi-valued in which case
multiple values are associated with a single name.)
A modlist is a NULL terminated array of struct LDAPMod's.
Thus, if we have:
LDAPMod **modlist;
Then:
modlist[i]
Is the ith modification.
Each LDAPMod describes a change to a single attribute. Further,
there is one modification for each attribute that we want to
change. The attribute's new value is stored in LDAPMod.mod_values.
If the attribute is multi-valued, we still only use a single
LDAPMod structure: mod_values is a NULL-terminated array of
strings. To delete an attribute from an entry, we set mod_values
to NULL.
Thus, if:
modlist[i]->mod_values == NULL
then we remove the attribute.
(Using LDAP_MOD_DELETE doesn't work here as we don't know if the
attribute in question exists or not.)
Note: this function does NOT copy or free ATTR. It does copy
VALUE. */
static void
modlist_add (LDAPMod ***modlistp, char *attr, const char *value)
{
LDAPMod **modlist = *modlistp;
LDAPMod **m;
int nummods = 0;
/* Search modlist for the attribute we're playing with. If modlist
is NULL, then the list is empty. Recall: modlist is a NULL
terminated array. */
for (m = modlist; m && *m; m++, nummods ++)
{
/* The attribute is already on the list. */
char **ptr;
int numvalues = 0;
if (strcasecmp ((*m)->mod_type, attr) != 0)
continue;
/* We have this attribute already, so when the REPLACE happens,
the server attributes will be replaced anyway. */
if (! value)
return;
/* Attributes can be multi-valued. See if the value is already
present. mod_values is a NULL terminated array of pointers.
Note: mod_values can be NULL. */
for (ptr = (*m)->mod_values; ptr && *ptr; ptr++)
{
if (strcmp (*ptr, value) == 0)
/* Duplicate value, we're done. */
return;
numvalues ++;
}
/* Append the value. */
ptr = xrealloc ((*m)->mod_values, sizeof (char *) * (numvalues + 2));
(*m)->mod_values = ptr;
ptr[numvalues] = xstrdup (value);
ptr[numvalues + 1] = NULL;
return;
}
/* We didn't find the attr, so make one and add it to the end */
/* Like attribute values, the list of attributes is NULL terminated
array of pointers. */
modlist = xrealloc (modlist, sizeof (LDAPMod *) * (nummods + 2));
*modlistp = modlist;
modlist[nummods] = xmalloc (sizeof (LDAPMod));
modlist[nummods]->mod_op = LDAP_MOD_REPLACE;
modlist[nummods]->mod_type = attr;
if (value)
{
modlist[nummods]->mod_values = xmalloc (sizeof(char *) * 2);
modlist[nummods]->mod_values[0] = xstrdup (value);
modlist[nummods]->mod_values[1] = NULL;
}
else
modlist[nummods]->mod_values = NULL;
modlist[nummods + 1] = NULL;
return;
}
/* Look up the value of an attribute in the specified modlist. If the
attribute is not on the mod list, returns NULL. The result is a
NULL-terminated array of strings. Don't change it. */
static char **
modlist_lookup (LDAPMod **modlist, const char *attr)
{
LDAPMod **m;
for (m = modlist; m && *m; m++)
{
if (strcasecmp ((*m)->mod_type, attr) != 0)
continue;
return (*m)->mod_values;
}
return NULL;
}
/* Dump a modlist to a file. This is useful for debugging. */
static estream_t modlist_dump (LDAPMod **modlist, estream_t output)
GPGRT_ATTR_USED;
static estream_t
modlist_dump (LDAPMod **modlist, estream_t output)
{
LDAPMod **m;
int opened = 0;
if (! output)
{
output = es_fopenmem (0, "rw");
if (!output)
return NULL;
opened = 1;
}
for (m = modlist; m && *m; m++)
{
es_fprintf (output, " %s:", (*m)->mod_type);
if (! (*m)->mod_values)
es_fprintf(output, " delete.\n");
else
{
char **ptr;
int i;
int multi = 0;
if ((*m)->mod_values[0] && (*m)->mod_values[1])
/* Have at least 2. */
multi = 1;
if (multi)
es_fprintf (output, "\n");
for ((ptr = (*m)->mod_values), (i = 1); ptr && *ptr; ptr++, i ++)
{
/* Assuming terminals are about 80 characters wide,
display at most about 10 lines of debugging
output. If we do trim the buffer, append '...' to
the end. */
const int max_len = 10 * 70;
size_t value_len = strlen (*ptr);
int elide = value_len > max_len;
if (multi)
es_fprintf (output, " %d. ", i);
es_fprintf (output, "`%.*s", max_len, *ptr);
if (elide)
es_fprintf (output, "...' (%zd bytes elided)",
value_len - max_len);
else
es_fprintf (output, "'");
es_fprintf (output, "\n");
}
}
}
if (opened)
es_fseek (output, 0, SEEK_SET);
return output;
}
/* Free all of the memory allocated by the mod list. This assumes
that the attribute names don't have to be freed, but the attributes
values do. (Which is what modlist_add does.) */
static void
modlist_free (LDAPMod **modlist)
{
LDAPMod **ml;
if (! modlist)
return;
/* Unwind and free the whole modlist structure */
/* The modlist is a NULL terminated array of pointers. */
for (ml = modlist; *ml; ml++)
{
LDAPMod *mod = *ml;
char **ptr;
- /* The list of values is a NULL termianted array of pointers.
+ /* The list of values is a NULL terminated array of pointers.
If the list is NULL, there are no values. */
if (mod->mod_values)
{
for (ptr = mod->mod_values; *ptr; ptr++)
xfree (*ptr);
xfree (mod->mod_values);
}
xfree (mod);
}
xfree (modlist);
}
/* Append two onto the end of one. Two is not freed, but its pointers
are now part of one. Make sure you don't free them both!
As long as you don't add anything to ONE, TWO is still valid.
After that all bets are off. */
static void
modlists_join (LDAPMod ***one, LDAPMod **two)
{
int i, one_count = 0, two_count = 0;
LDAPMod **grow;
if (!*two)
/* two is empty. Nothing to do. */
return;
if (!*one)
/* one is empty. Just set it equal to *two. */
{
*one = two;
return;
}
for (grow = *one; *grow; grow++)
one_count ++;
for (grow = two; *grow; grow++)
two_count ++;
grow = xrealloc (*one, sizeof(LDAPMod *) * (one_count + two_count + 1));
for (i = 0; i < two_count; i++)
grow[one_count + i] = two[i];
grow[one_count + i] = NULL;
*one = grow;
}
/* Given a string, unescape C escapes. In particular, \xXX. This
modifies the string in place. */
static void
uncescape (char *str)
{
size_t r = 0;
size_t w = 0;
char *first = strchr (str, '\\');
if (! first)
/* No backslashes => no escaping. We're done. */
return;
/* Start at the first '\\'. */
r = w = (uintptr_t) first - (uintptr_t) str;
while (str[r])
{
/* XXX: What to do about bad escapes?
XXX: hextobyte already checks the string thus the hexdigitp
could be removed. */
if (str[r] == '\\' && str[r + 1] == 'x'
&& str[r+2] && str[r+3]
&& hexdigitp (str + r + 2)
&& hexdigitp (str + r + 3))
{
int x = hextobyte (&str[r + 2]);
log_assert (0 <= x && x <= 0xff);
str[w] = x;
/* We consumed 4 characters and wrote 1. */
r += 4;
w ++;
}
else
str[w ++] = str[r ++];
}
str[w] = '\0';
}
/* Given one line from an info block (`gpg --list-{keys,sigs}
--with-colons KEYID'), pull it apart and fill in the modlist with
the relevant (for the LDAP schema) attributes. EXTRACT_STATE
- should initally be set to 0 by the caller. SCHEMAV2 is set if the
+ should initially be set to 0 by the caller. SCHEMAV2 is set if the
server supports the version 2 schema. */
static void
extract_attributes (LDAPMod ***modlist, int *extract_state,
char *line, int schemav2)
{
int field_count;
char **fields;
char *keyid;
int is_pub, is_sub, is_uid, is_sig;
/* Remove trailing whitespace */
trim_trailing_spaces (line);
fields = strsplit (line, ':', '\0', &field_count);
if (field_count == 1)
/* We only have a single field. There is definitely nothing to
do. */
goto out;
if (field_count < 7)
goto out;
is_pub = !ascii_strcasecmp ("pub", fields[0]);
is_sub = !ascii_strcasecmp ("sub", fields[0]);
is_uid = !ascii_strcasecmp ("uid", fields[0]);
is_sig = !ascii_strcasecmp ("sig", fields[0]);
if (!ascii_strcasecmp ("fpr", fields[0]))
{
/* Special treatment for a fingerprint. */
if (!(*extract_state & 1))
goto out; /* Stray fingerprint line - ignore. */
*extract_state &= ~1;
if (field_count >= 10 && schemav2)
{
if ((*extract_state & 2))
modlist_add (modlist, "gpgFingerprint", fields[9]);
else
modlist_add (modlist, "gpgSubFingerprint", fields[9]);
}
goto out;
}
*extract_state &= ~(1|2);
if (is_pub)
*extract_state |= (1|2);
else if (is_sub)
*extract_state |= 1;
if (!is_pub && !is_sub && !is_uid && !is_sig)
goto out; /* Not a relevant line. */
keyid = fields[4];
if (is_uid && strlen (keyid) == 0)
; /* The uid record type can have an empty keyid. */
else if (strlen (keyid) == 16
&& strspn (keyid, "0123456789aAbBcCdDeEfF") == 16)
; /* Otherwise, we expect exactly 16 hex characters. */
else
{
log_error ("malformed record!\n");
goto out;
}
if (is_pub)
{
int disabled = 0;
int revoked = 0;
char *flags;
for (flags = fields[1]; *flags; flags ++)
switch (*flags)
{
case 'r':
case 'R':
revoked = 1;
break;
case 'd':
case 'D':
disabled = 1;
break;
}
/* Note: we always create the pgpDisabled and pgpRevoked
attributes, regardless of whether the key is disabled/revoked
or not. This is because a very common search is like
"(&(pgpUserID=*isabella*)(pgpDisabled=0))" */
if (is_pub)
{
modlist_add (modlist,"pgpDisabled", disabled ? "1" : "0");
modlist_add (modlist,"pgpRevoked", revoked ? "1" : "0");
}
}
if (is_pub || is_sub)
{
char padded[6];
int val;
val = atoi (fields[2]);
if (val < 99999 && val > 0)
{
/* We zero pad this on the left to make PGP happy. */
snprintf (padded, sizeof padded, "%05u", val);
modlist_add (modlist, "pgpKeySize", padded);
}
}
if (is_pub)
{
char *algo = fields[3];
int val = atoi (algo);
switch (val)
{
case 1:
algo = "RSA";
break;
case 17:
algo = "DSS/DH";
break;
default:
algo = NULL;
break;
}
if (algo)
modlist_add (modlist, "pgpKeyType", algo);
}
if (is_pub || is_sub || is_sig)
{
if (is_pub)
{
modlist_add (modlist, "pgpCertID", keyid); /* Long keyid(!) */
modlist_add (modlist, "pgpKeyID", &keyid[8]); /* Short keyid */
}
if (is_sub)
modlist_add (modlist, "pgpSubKeyID", keyid); /* Long keyid(!) */
}
if (is_pub)
{
char *create_time = fields[5];
if (strlen (create_time) == 0)
create_time = NULL;
else
{
char *create_time_orig = create_time;
struct tm tm;
time_t t;
char *end;
memset (&tm, 0, sizeof (tm));
- /* parse_timestamp handles both seconds fromt he epoch and
+ /* parse_timestamp handles both seconds from the epoch and
ISO 8601 format. We also need to handle YYYY-MM-DD
format (as generated by gpg1 --with-colons --list-key).
Check that first and then if it fails, then try
parse_timestamp. */
if (!isodate_human_to_tm (create_time, &tm))
create_time = tm2ldaptime (&tm);
else if ((t = parse_timestamp (create_time, &end)) != (time_t) -1
&& *end == '\0')
{
if (!gnupg_gmtime (&t, &tm))
create_time = NULL;
else
create_time = tm2ldaptime (&tm);
}
else
create_time = NULL;
if (! create_time)
/* Failed to parse string. */
log_error ("Failed to parse creation time ('%s')",
create_time_orig);
}
if (create_time)
{
modlist_add (modlist, "pgpKeyCreateTime", create_time);
xfree (create_time);
}
}
if (is_pub)
{
char *expire_time = fields[6];
if (strlen (expire_time) == 0)
expire_time = NULL;
else
{
char *expire_time_orig = expire_time;
struct tm tm;
time_t t;
char *end;
memset (&tm, 0, sizeof (tm));
- /* parse_timestamp handles both seconds fromt he epoch and
+ /* parse_timestamp handles both seconds from the epoch and
ISO 8601 format. We also need to handle YYYY-MM-DD
format (as generated by gpg1 --with-colons --list-key).
Check that first and then if it fails, then try
parse_timestamp. */
if (!isodate_human_to_tm (expire_time, &tm))
expire_time = tm2ldaptime (&tm);
else if ((t = parse_timestamp (expire_time, &end)) != (time_t) -1
&& *end == '\0')
{
if (!gnupg_gmtime (&t, &tm))
expire_time = NULL;
else
expire_time = tm2ldaptime (&tm);
}
else
expire_time = NULL;
if (! expire_time)
/* Failed to parse string. */
log_error ("Failed to parse creation time ('%s')",
expire_time_orig);
}
if (expire_time)
{
modlist_add (modlist, "pgpKeyExpireTime", expire_time);
xfree (expire_time);
}
}
if (is_uid && field_count >= 10)
{
char *uid = fields[9];
char *mbox;
uncescape (uid);
modlist_add (modlist, "pgpUserID", uid);
if (schemav2 && (mbox = mailbox_from_userid (uid, 0)))
{
modlist_add (modlist, "gpgMailbox", mbox);
xfree (mbox);
}
}
out:
xfree (fields);
}
/* Send the key in {KEY,KEYLEN} with the metadata {INFO,INFOLEN} to
the keyserver identified by URI. See server.c:cmd_ks_put for the
format of the data and metadata. */
gpg_error_t
ks_ldap_put (ctrl_t ctrl, parsed_uri_t uri,
void *data, size_t datalen,
void *info, size_t infolen)
{
gpg_error_t err = 0;
int ldap_err;
unsigned int serverinfo;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
LDAPMod **modlist = NULL;
LDAPMod **addlist = NULL;
char *data_armored = NULL;
int extract_state;
/* The last byte of the info block. */
const char *infoend = (const char *) info + infolen - 1;
/* Enable this code to dump the modlist to /tmp/modlist.txt. */
#if 0
# warning Disable debug code before checking in.
const int dump_modlist = 1;
#else
const int dump_modlist = 0;
#endif
estream_t dump = NULL;
/* Elide a warning. */
(void) ctrl;
if (dirmngr_use_tor ())
{
return no_ldap_due_to_tor (ctrl);
}
err = my_ldap_connect (uri, 0, &ldap_conn, &basedn, NULL, NULL, &serverinfo);
if (err || !basedn)
{
if (!err)
err = GPG_ERR_GENERAL;
goto out;
}
if (!(serverinfo & SERVERINFO_REALLDAP))
{
/* We appear to have a PGP.com Keyserver, which can unpack the
* key on its own (not just a dump LDAP server). This will
* rarely be the case these days. */
LDAPMod mod;
LDAPMod *attrs[2];
char *key[2];
char *dn;
key[0] = data;
key[1] = NULL;
memset (&mod, 0, sizeof (mod));
mod.mod_op = LDAP_MOD_ADD;
mod.mod_type = (serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey";
mod.mod_values = key;
attrs[0] = &mod;
attrs[1] = NULL;
dn = xtryasprintf ("pgpCertid=virtual,%s", basedn);
if (!dn)
{
err = gpg_error_from_syserror ();
goto out;
}
ldap_err = ldap_add_s (ldap_conn, dn, attrs);
xfree (dn);
if (ldap_err != LDAP_SUCCESS)
{
err = ldap_err_to_gpg_err (err);
goto out;
}
goto out;
}
modlist = xtrymalloc (sizeof (LDAPMod *));
if (!modlist)
{
err = gpg_error_from_syserror ();
goto out;
}
*modlist = NULL;
if (dump_modlist)
{
dump = es_fopen("/tmp/modlist.txt", "w");
if (! dump)
log_error ("failed to open /tmp/modlist.txt: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
if (dump)
{
es_fprintf(dump, "data (%zd bytes)\n", datalen);
es_fprintf(dump, "info (%zd bytes): '\n", infolen);
es_fwrite(info, infolen, 1, dump);
es_fprintf(dump, "'\n");
}
}
/* Start by nulling out all attributes. We try and do a modify
operation first, so this ensures that we don't leave old
attributes lying around. */
modlist_add (&modlist, "pgpDisabled", NULL);
modlist_add (&modlist, "pgpKeyID", NULL);
modlist_add (&modlist, "pgpKeyType", NULL);
modlist_add (&modlist, "pgpUserID", NULL);
modlist_add (&modlist, "pgpKeyCreateTime", NULL);
modlist_add (&modlist, "pgpRevoked", NULL);
modlist_add (&modlist, "pgpSubKeyID", NULL);
modlist_add (&modlist, "pgpKeySize", NULL);
modlist_add (&modlist, "pgpKeyExpireTime", NULL);
modlist_add (&modlist, "pgpCertID", NULL);
if ((serverinfo & SERVERINFO_SCHEMAV2))
{
modlist_add (&modlist, "gpgFingerprint", NULL);
modlist_add (&modlist, "gpgSubFingerprint", NULL);
modlist_add (&modlist, "gpgMailbox", NULL);
}
/* Assemble the INFO stuff into LDAP attributes */
extract_state = 0;
while (infolen > 0)
{
char *temp = NULL;
char *newline = memchr (info, '\n', infolen);
if (! newline)
/* The last line is not \n terminated! Make a copy so we can
add a NUL terminator. */
{
temp = xmalloc (infolen + 1);
memcpy (temp, info, infolen);
info = temp;
newline = (char *) info + infolen;
}
*newline = '\0';
extract_attributes (&addlist, &extract_state, info,
(serverinfo & SERVERINFO_SCHEMAV2));
infolen = infolen - ((uintptr_t) newline - (uintptr_t) info + 1);
info = newline + 1;
/* Sanity check. */
if (! temp)
log_assert ((char *) info + infolen - 1 == infoend);
else
{
log_assert (infolen == -1);
xfree (temp);
}
}
modlist_add (&addlist, "objectClass", "pgpKeyInfo");
err = armor_data (&data_armored, data, datalen);
if (err)
goto out;
modlist_add (&addlist,
(serverinfo & SERVERINFO_PGPKEYV2)? "pgpKeyV2":"pgpKey",
data_armored);
/* Now append addlist onto modlist. */
modlists_join (&modlist, addlist);
if (dump)
{
estream_t input = modlist_dump (modlist, NULL);
if (input)
{
copy_stream (input, dump);
es_fclose (input);
}
}
/* Going on the assumption that modify operations are more frequent
than adds, we try a modify first. If it's not there, we just
turn around and send an add command for the same key. Otherwise,
the modify brings the server copy into compliance with our copy.
Note that unlike the LDAP keyserver (and really, any other
keyserver) this does NOT merge signatures, but replaces the whole
key. This should make some people very happy. */
{
char **attrval;
char *dn;
if ((serverinfo & SERVERINFO_NTDS))
{
/* The modern way using a CN RDN with the fingerprint. This
* has the advantage that we won't have duplicate 64 bit
* keyids in the store. In particular NTDS requires the
* DN to be unique. */
attrval = modlist_lookup (addlist, "gpgFingerprint");
/* We should have exactly one value. */
if (!attrval || !(attrval[0] && !attrval[1]))
{
log_error ("ks-ldap: bad gpgFingerprint provided\n");
err = GPG_ERR_GENERAL;
goto out;
}
dn = xtryasprintf ("CN=%s,%s", attrval[0], basedn);
}
else /* The old style way. */
{
attrval = modlist_lookup (addlist, "pgpCertID");
/* We should have exactly one value. */
if (!attrval || !(attrval[0] && !attrval[1]))
{
log_error ("ks-ldap: bad pgpCertID provided\n");
err = GPG_ERR_GENERAL;
goto out;
}
dn = xtryasprintf ("pgpCertID=%s,%s", attrval[0], basedn);
}
if (!dn)
{
err = gpg_error_from_syserror ();
goto out;
}
if (opt.debug)
log_debug ("ks-ldap: using DN: %s\n", dn);
npth_unprotect ();
err = ldap_modify_s (ldap_conn, dn, modlist);
if (err == LDAP_NO_SUCH_OBJECT)
err = ldap_add_s (ldap_conn, dn, addlist);
npth_protect ();
xfree (dn);
if (err != LDAP_SUCCESS)
{
log_error ("ks-ldap: error adding key to keyserver: %s\n",
ldap_err2string (err));
err = ldap_err_to_gpg_err (err);
}
}
out:
if (dump)
es_fclose (dump);
if (ldap_conn)
ldap_unbind (ldap_conn);
xfree (basedn);
modlist_free (modlist);
xfree (addlist);
xfree (data_armored);
return err;
}
/* Get the data described by FILTER_ARG from URI. On success R_FP has
* an open stream to read the data. KS_GET_FLAGS conveys flags from
* the client. ATTRS is a NULL terminated list of attributes to
* return or NULL for all. */
gpg_error_t
ks_ldap_query (ctrl_t ctrl, parsed_uri_t uri, unsigned int ks_get_flags,
const char *filter_arg, char **attrs,
gnupg_isotime_t newer, estream_t *r_fp)
{
gpg_error_t err;
unsigned int serverinfo;
char *host = NULL;
int use_tls;
LDAP *ldap_conn = NULL;
char *basedn = NULL;
estream_t fp = NULL;
char *filter_arg_buffer = NULL;
char *filter = NULL;
int scope = LDAP_SCOPE_SUBTREE;
LDAPMessage *message = NULL;
LDAPMessage *msg;
int anydata = 0;
int first_mode = 0;
int next_mode = 0;
int get_first;
if (dirmngr_use_tor ())
return no_ldap_due_to_tor (ctrl);
if ((!filter_arg || !*filter_arg) && (ks_get_flags & KS_GET_FLAG_ROOTDSE))
filter_arg = "^&base&(objectclass=*)";
if ((ks_get_flags & KS_GET_FLAG_SUBST)
&& filter_arg && strchr (filter_arg, '$'))
{
filter_arg_buffer = substitute_vars (filter_arg, getval_for_filter, ctrl);
if (!filter_arg_buffer)
{
err = gpg_error_from_syserror ();
log_error ("substituting filter variables failed: %s\n",
gpg_strerror (err));
goto leave;
}
filter_arg = filter_arg_buffer;
}
err = ks_ldap_prepare_my_state (ctrl, ks_get_flags, &first_mode, &next_mode);
if (err)
goto leave;
if (!next_mode) /* (In --next mode the filter is ignored.) */
{
if (!filter_arg || !*filter_arg)
{
err = gpg_error (GPG_ERR_LDAP_FILTER);
goto leave;
}
err = ldap_parse_extfilter (filter_arg, 0, &basedn, &scope, &filter);
if (err)
goto leave;
if (newer && *newer)
{
char *tstr, *fstr;
tstr = isotime2rfc4517 (newer);
if (!tstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (filter && *filter)
fstr = strconcat ("(&", filter,
"(modifyTimestamp>=", tstr, "))", NULL);
else
fstr = strconcat ("(modifyTimestamp>=", tstr, ")", NULL);
xfree (tstr);
if (!fstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
xfree (filter);
filter = fstr;
}
}
if (next_mode)
{
next_again:
if (!ctrl->ks_get_state->msg_iter && ctrl->ks_get_state->more_pages)
{
/* Get the next page of results. */
if (ctrl->ks_get_state->message)
{
ldap_msgfree (ctrl->ks_get_state->message);
ctrl->ks_get_state->message = NULL;
}
err = search_and_parse (ctrl, ctrl->ks_get_state->keyspec,
ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->basedn,
ctrl->ks_get_state->scope,
ctrl->ks_get_state->filter,
attrs,
&ctrl->ks_get_state->message);
if (err)
goto leave;
ctrl->ks_get_state->msg_iter = ctrl->ks_get_state->message;
get_first = 1;
}
else
get_first = 0;
while (ctrl->ks_get_state->msg_iter)
{
npth_unprotect ();
ctrl->ks_get_state->msg_iter
= get_first? ldap_first_entry (ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->msg_iter)
/* */ : ldap_next_entry (ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->msg_iter);
npth_protect ();
get_first = 0;
if (ctrl->ks_get_state->msg_iter)
{
err = return_all_attributes (ctrl->ks_get_state->ldap_conn,
ctrl->ks_get_state->msg_iter,
&fp);
if (!err)
break; /* Found. */
else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0; /* Skip empty attributes. */
else
goto leave;
}
}
if (!ctrl->ks_get_state->msg_iter || !fp)
{
ctrl->ks_get_state->msg_iter = NULL;
if (ctrl->ks_get_state->more_pages)
goto next_again;
err = gpg_error (GPG_ERR_NO_DATA);
}
}
else /* Not in --next mode. */
{
/* Connect to the LDAP server in generic mode. */
char *tmpbasedn;
err = my_ldap_connect (uri, 1 /*generic*/, &ldap_conn,
&tmpbasedn, &host, &use_tls, &serverinfo);
if (err)
goto leave;
if (basedn)
xfree (tmpbasedn); /* Extended syntax overrides. */
else if (tmpbasedn)
basedn = tmpbasedn;
else if (!(ks_get_flags & KS_GET_FLAG_ROOTDSE))
{
/* No BaseDN known - get one. */
basedn = basedn_from_rootdse (ctrl, uri);
}
if (opt.debug)
{
log_debug ("ks-ldap: using basedn: %s\n", basedn);
log_debug ("ks-ldap: using filter: %s\n", filter);
}
err = search_and_parse (ctrl, filter, ldap_conn, basedn, scope, filter,
attrs, &message);
if (err)
goto leave;
for (npth_unprotect (),
msg = ldap_first_entry (ldap_conn, message),
npth_protect ();
msg;
npth_unprotect (),
msg = ldap_next_entry (ldap_conn, msg),
npth_protect ())
{
err = return_all_attributes (ldap_conn, msg, &fp);
if (!err)
{
anydata = 1;
if (first_mode)
break;
}
else if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0; /* Skip empty/duplicate attributes. */
else
goto leave;
}
if (ctrl->ks_get_state) /* Save the iterator. */
ctrl->ks_get_state->msg_iter = msg;
if (!fp) /* Nothing was found. */
err = gpg_error (GPG_ERR_NO_DATA);
if (!err && anydata)
err = dirmngr_status_printf (ctrl, "SOURCE", "%s://%s",
use_tls? "ldaps" : "ldap",
host? host:"");
}
leave:
/* Store our state if needed. */
if (!err && (ks_get_flags & KS_GET_FLAG_FIRST))
{
log_assert (!ctrl->ks_get_state->ldap_conn);
ctrl->ks_get_state->ldap_conn = ldap_conn;
ldap_conn = NULL;
log_assert (!ctrl->ks_get_state->message);
ctrl->ks_get_state->message = message;
message = NULL;
ctrl->ks_get_state->serverinfo = serverinfo;
ctrl->ks_get_state->scope = scope;
ctrl->ks_get_state->basedn = basedn;
basedn = NULL;
ctrl->ks_get_state->keyspec = filter? xtrystrdup (filter) : NULL;
ctrl->ks_get_state->filter = filter;
filter = NULL;
}
if ((ks_get_flags & KS_GET_FLAG_NEXT))
{
/* Keep the state in --next mode even with errors. */
ldap_conn = NULL;
message = NULL;
}
if (message)
ldap_msgfree (message);
if (err)
es_fclose (fp);
else
{
if (fp)
es_fseek (fp, 0, SEEK_SET);
*r_fp = fp;
}
xfree (basedn);
xfree (host);
if (ldap_conn)
ldap_unbind (ldap_conn);
xfree (filter);
xfree (filter_arg_buffer);
return err;
}
diff --git a/dirmngr/ldap-misc.c b/dirmngr/ldap-misc.c
index c3a659d5c..a3aef0e62 100644
--- a/dirmngr/ldap-misc.c
+++ b/dirmngr/ldap-misc.c
@@ -1,422 +1,422 @@
/* ldap-misc.c - Miscellaneous helpers for LDAP functions
* Copyright (C) 2015, 2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "dirmngr-err.h"
#include "../common/util.h"
#include "ldap-misc.h"
/* Convert an LDAP error to a GPG error. */
gpg_err_code_t
ldap_err_to_gpg_err (int code)
{
gpg_err_code_t ec;
switch (code)
{
#ifdef LDAP_X_CONNECTING
case LDAP_X_CONNECTING: ec = GPG_ERR_LDAP_X_CONNECTING; break;
#endif
case LDAP_REFERRAL_LIMIT_EXCEEDED: ec = GPG_ERR_LDAP_REFERRAL_LIMIT; break;
case LDAP_CLIENT_LOOP: ec = GPG_ERR_LDAP_CLIENT_LOOP; break;
case LDAP_NO_RESULTS_RETURNED: ec = GPG_ERR_LDAP_NO_RESULTS; break;
case LDAP_CONTROL_NOT_FOUND: ec = GPG_ERR_LDAP_CONTROL_NOT_FOUND; break;
case LDAP_NOT_SUPPORTED: ec = GPG_ERR_LDAP_NOT_SUPPORTED; break;
case LDAP_CONNECT_ERROR: ec = GPG_ERR_LDAP_CONNECT; break;
case LDAP_NO_MEMORY: ec = GPG_ERR_LDAP_NO_MEMORY; break;
case LDAP_PARAM_ERROR: ec = GPG_ERR_LDAP_PARAM; break;
case LDAP_USER_CANCELLED: ec = GPG_ERR_LDAP_USER_CANCELLED; break;
case LDAP_FILTER_ERROR: ec = GPG_ERR_LDAP_FILTER; break;
case LDAP_AUTH_UNKNOWN: ec = GPG_ERR_LDAP_AUTH_UNKNOWN; break;
case LDAP_TIMEOUT: ec = GPG_ERR_LDAP_TIMEOUT; break;
case LDAP_DECODING_ERROR: ec = GPG_ERR_LDAP_DECODING; break;
case LDAP_ENCODING_ERROR: ec = GPG_ERR_LDAP_ENCODING; break;
case LDAP_LOCAL_ERROR: ec = GPG_ERR_LDAP_LOCAL; break;
case LDAP_SERVER_DOWN: ec = GPG_ERR_LDAP_SERVER_DOWN; break;
case LDAP_SUCCESS: ec = GPG_ERR_LDAP_SUCCESS; break;
case LDAP_OPERATIONS_ERROR: ec = GPG_ERR_LDAP_OPERATIONS; break;
case LDAP_PROTOCOL_ERROR: ec = GPG_ERR_LDAP_PROTOCOL; break;
case LDAP_TIMELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_TIMELIMIT; break;
case LDAP_SIZELIMIT_EXCEEDED: ec = GPG_ERR_LDAP_SIZELIMIT; break;
case LDAP_COMPARE_FALSE: ec = GPG_ERR_LDAP_COMPARE_FALSE; break;
case LDAP_COMPARE_TRUE: ec = GPG_ERR_LDAP_COMPARE_TRUE; break;
case LDAP_AUTH_METHOD_NOT_SUPPORTED: ec=GPG_ERR_LDAP_UNSUPPORTED_AUTH;break;
case LDAP_STRONG_AUTH_REQUIRED: ec = GPG_ERR_LDAP_STRONG_AUTH_RQRD; break;
case LDAP_PARTIAL_RESULTS: ec = GPG_ERR_LDAP_PARTIAL_RESULTS; break;
case LDAP_REFERRAL: ec = GPG_ERR_LDAP_REFERRAL; break;
#ifdef LDAP_ADMINLIMIT_EXCEEDED
case LDAP_ADMINLIMIT_EXCEEDED: ec = GPG_ERR_LDAP_ADMINLIMIT; break;
#endif
#ifdef LDAP_UNAVAILABLE_CRITICAL_EXTENSION
case LDAP_UNAVAILABLE_CRITICAL_EXTENSION:
ec = GPG_ERR_LDAP_UNAVAIL_CRIT_EXTN; break;
#endif
case LDAP_CONFIDENTIALITY_REQUIRED: ec = GPG_ERR_LDAP_CONFIDENT_RQRD; break;
case LDAP_SASL_BIND_IN_PROGRESS: ec = GPG_ERR_LDAP_SASL_BIND_INPROG; break;
case LDAP_NO_SUCH_ATTRIBUTE: ec = GPG_ERR_LDAP_NO_SUCH_ATTRIBUTE; break;
case LDAP_UNDEFINED_TYPE: ec = GPG_ERR_LDAP_UNDEFINED_TYPE; break;
case LDAP_INAPPROPRIATE_MATCHING: ec = GPG_ERR_LDAP_BAD_MATCHING; break;
case LDAP_CONSTRAINT_VIOLATION: ec = GPG_ERR_LDAP_CONST_VIOLATION; break;
#ifdef LDAP_TYPE_OR_VALUE_EXISTS
case LDAP_TYPE_OR_VALUE_EXISTS: ec = GPG_ERR_LDAP_TYPE_VALUE_EXISTS; break;
#endif
case LDAP_INVALID_SYNTAX: ec = GPG_ERR_LDAP_INV_SYNTAX; break;
case LDAP_NO_SUCH_OBJECT: ec = GPG_ERR_LDAP_NO_SUCH_OBJ; break;
case LDAP_ALIAS_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_PROBLEM; break;
case LDAP_INVALID_DN_SYNTAX: ec = GPG_ERR_LDAP_INV_DN_SYNTAX; break;
case LDAP_IS_LEAF: ec = GPG_ERR_LDAP_IS_LEAF; break;
case LDAP_ALIAS_DEREF_PROBLEM: ec = GPG_ERR_LDAP_ALIAS_DEREF; break;
#ifdef LDAP_X_PROXY_AUTHZ_FAILURE
case LDAP_X_PROXY_AUTHZ_FAILURE: ec = GPG_ERR_LDAP_X_PROXY_AUTH_FAIL; break;
#endif
case LDAP_INAPPROPRIATE_AUTH: ec = GPG_ERR_LDAP_BAD_AUTH; break;
case LDAP_INVALID_CREDENTIALS: ec = GPG_ERR_LDAP_INV_CREDENTIALS; break;
#ifdef LDAP_INSUFFICIENT_ACCESS
case LDAP_INSUFFICIENT_ACCESS: ec = GPG_ERR_LDAP_INSUFFICIENT_ACC; break;
#endif
case LDAP_BUSY: ec = GPG_ERR_LDAP_BUSY; break;
case LDAP_UNAVAILABLE: ec = GPG_ERR_LDAP_UNAVAILABLE; break;
case LDAP_UNWILLING_TO_PERFORM: ec = GPG_ERR_LDAP_UNWILL_TO_PERFORM; break;
case LDAP_LOOP_DETECT: ec = GPG_ERR_LDAP_LOOP_DETECT; break;
case LDAP_NAMING_VIOLATION: ec = GPG_ERR_LDAP_NAMING_VIOLATION; break;
case LDAP_OBJECT_CLASS_VIOLATION: ec = GPG_ERR_LDAP_OBJ_CLS_VIOLATION; break;
case LDAP_NOT_ALLOWED_ON_NONLEAF: ec=GPG_ERR_LDAP_NOT_ALLOW_NONLEAF;break;
case LDAP_NOT_ALLOWED_ON_RDN: ec = GPG_ERR_LDAP_NOT_ALLOW_ON_RDN; break;
case LDAP_ALREADY_EXISTS: ec = GPG_ERR_LDAP_ALREADY_EXISTS; break;
case LDAP_NO_OBJECT_CLASS_MODS: ec = GPG_ERR_LDAP_NO_OBJ_CLASS_MODS; break;
case LDAP_RESULTS_TOO_LARGE: ec = GPG_ERR_LDAP_RESULTS_TOO_LARGE; break;
case LDAP_AFFECTS_MULTIPLE_DSAS: ec = GPG_ERR_LDAP_AFFECTS_MULT_DSAS; break;
#ifdef LDAP_VLV_ERROR
case LDAP_VLV_ERROR: ec = GPG_ERR_LDAP_VLV; break;
#endif
case LDAP_OTHER: ec = GPG_ERR_LDAP_OTHER; break;
#ifdef LDAP_CUP_RESOURCES_EXHAUSTED
case LDAP_CUP_RESOURCES_EXHAUSTED: ec=GPG_ERR_LDAP_CUP_RESOURCE_LIMIT;break;
case LDAP_CUP_SECURITY_VIOLATION: ec=GPG_ERR_LDAP_CUP_SEC_VIOLATION; break;
case LDAP_CUP_INVALID_DATA: ec = GPG_ERR_LDAP_CUP_INV_DATA; break;
case LDAP_CUP_UNSUPPORTED_SCHEME: ec = GPG_ERR_LDAP_CUP_UNSUP_SCHEME; break;
case LDAP_CUP_RELOAD_REQUIRED: ec = GPG_ERR_LDAP_CUP_RELOAD; break;
#endif
#ifdef LDAP_CANCELLED
case LDAP_CANCELLED: ec = GPG_ERR_LDAP_CANCELLED; break;
#endif
#ifdef LDAP_NO_SUCH_OPERATION
case LDAP_NO_SUCH_OPERATION: ec = GPG_ERR_LDAP_NO_SUCH_OPERATION; break;
#endif
#ifdef LDAP_TOO_LATE
case LDAP_TOO_LATE: ec = GPG_ERR_LDAP_TOO_LATE; break;
#endif
#ifdef LDAP_CANNOT_CANCEL
case LDAP_CANNOT_CANCEL: ec = GPG_ERR_LDAP_CANNOT_CANCEL; break;
#endif
#ifdef LDAP_ASSERTION_FAILED
case LDAP_ASSERTION_FAILED: ec = GPG_ERR_LDAP_ASSERTION_FAILED; break;
#endif
#ifdef LDAP_PROXIED_AUTHORIZATION_DENIED
case LDAP_PROXIED_AUTHORIZATION_DENIED:
ec = GPG_ERR_LDAP_PROX_AUTH_DENIED; break;
#endif
default:
#if defined(LDAP_E_ERROR) && defined(LDAP_X_ERROR)
if (LDAP_E_ERROR (code))
ec = GPG_ERR_LDAP_E_GENERAL;
else if (LDAP_X_ERROR (code))
ec = GPG_ERR_LDAP_X_GENERAL;
else
#endif
ec = GPG_ERR_LDAP_GENERAL;
break;
}
return ec;
}
/* Retrieve an LDAP error and return it's GPG equivalent. */
gpg_err_code_t
ldap_to_gpg_err (LDAP *ld)
{
#if defined(HAVE_LDAP_GET_OPTION) && defined(LDAP_OPT_ERROR_NUMBER)
int err;
if (ldap_get_option (ld, LDAP_OPT_ERROR_NUMBER, &err) == 0)
return ldap_err_to_gpg_err (err);
else
return GPG_ERR_GENERAL;
#elif defined(HAVE_LDAP_LD_ERRNO)
return ldap_err_to_gpg_err (ld->ld_errno);
#else
/* We should never get here since the LDAP library should always
have either ldap_get_option or ld_errno, but just in case... */
return GPG_ERR_INTERNAL;
#endif
}
/* Parse an extended filter syntax as used by dirmngr_ldap.c
* For example:
*
* ^CN=foo, OU=My Users&(objectClasses=*)
*
* Uses "CN=foo, OU=My Users" as base DN and "(objectClasses=*)" as
* filter. If the base prefix includes an ampersand, it needs to be
* doubled. The usual escaping rules for DNs (for the base) and
* filters apply. Other examples:
*
* ^CN=foo, OU=My Users&
*
* Use just the base DN.
*
* ^CN=foo, OU=My Users&SCOPE&
*
* Specify the scope which is "base", "one", or "sub". May of course
* also be followed by a filter.
*
* ^&SCOPE&(objectClasses=*)
*
* Give a scope and a filter. Note that R_SCOPE is only changed if a
- * STRING has scope parameter. Setting this initally to -1 allows to
+ * STRING has scope parameter. Setting this initially to -1 allows to
* detect this case.
*/
gpg_error_t
ldap_parse_extfilter (const char *string, int silent,
char **r_base, int *r_scope, char **r_filter)
{
gpg_error_t err = 0;
char *base = NULL;
char *filter = NULL;
const char *s;
char *p;
if (r_base)
*r_base = NULL;
if (r_filter)
*r_filter = NULL;
if (*string == '^')
{
string++;
base = xtrymalloc (strlen (string)+1);
if (!base)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (s=string, p=base; *s; s++)
{
*p++ = *s;
if (*s == '&' && s[1] == '&')
s++; /* Skip quoted ampersand. */
else if (*s == '&')
{
p--;
break;
}
}
*p = 0;
if (!*s)
{
if (!silent)
log_info ("LDAP extended filter is not terminated\n");
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
string = s + 1;
}
if (!*string)
goto leave; /* ready. */
if (!strncmp (string, "base&", 5))
{
string += 5;
if (r_scope)
*r_scope = LDAP_SCOPE_BASE;
}
else if (!strncmp (string, "one&", 4))
{
string += 4;
if (r_scope)
*r_scope = LDAP_SCOPE_ONELEVEL;
}
else if (!strncmp (string, "sub&", 4))
{
string += 4;
if (r_scope)
*r_scope = LDAP_SCOPE_SUBTREE;
}
if (!*string)
goto leave; /* ready. */
if (*string != '(')
{
if (!silent)
log_info ("LDAP filter does not start with a left parentheses\n");
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
if (string[strlen(string)-1] != ')')
{
if (!silent)
log_info ("LDAP filter does not end with a right parentheses\n");
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
filter = xtrystrdup (string);
if (!filter)
err = gpg_error_from_syserror ();
leave:
if (err)
{
xfree (base);
xfree (filter);
}
else
{
if (r_base)
*r_base = base;
else
xfree (base);
if (r_filter)
*r_filter = filter;
else
xfree (filter);
}
return err;
}
/* Scan an ISO timestamp and return a Generalized Time according to
* RFC-4517. The only supported format is "yyyymmddThhmmss[Z]"
* delimited by white space, nul, a colon or a comma. Returns a
* malloced string or NULL for an invalid string or on memory
* error. */
char *
isotime2rfc4517 (const char *string)
{
int year, month, day, hour, minu, sec;
if (!isotime_p (string))
{
errno = 0;
return NULL;
}
year = atoi_4 (string);
month = atoi_2 (string + 4);
day = atoi_2 (string + 6);
hour = atoi_2 (string + 9);
minu = atoi_2 (string + 11);
sec = atoi_2 (string + 13);
/* Basic checks (1600 due to the LDAP time format base) */
if (year < 1600 || month < 1 || month > 12 || day < 1 || day > 31
|| hour > 23 || minu > 59 || sec > 61 )
{
errno = 0;
return NULL;
}
return gpgrt_bsprintf ("%04d%02d%02d%02d%02d%02d.0Z",
year, month, day, hour, minu, sec);
}
/* Parse an LDAP Generalized Time string and update the provided
* isotime buffer. On error return and error code. */
gpg_error_t
rfc4517toisotime (gnupg_isotime_t timebuf, const char *string)
{
int i;
int year, month, day, hour, minu, sec;
const char *s;
/* Sample value: "20230823141623Z"; */
for (i=0, s=string; i < 10; i++, s++) /* Need yyyymmddhh */
if (!digitp (s))
return gpg_error (GPG_ERR_INV_TIME);
year = atoi_4 (string);
month = atoi_2 (string + 4);
day = atoi_2 (string + 6);
hour = atoi_2 (string + 8);
minu = 0;
sec = 0;
if (digitp (s) && digitp (s+1))
{
minu = atoi_2 (s);
s += 2;
if (digitp (s) && digitp (s+1))
{
sec = atoi_2 (s);
s += 2;
}
}
if (*s == '.' || *s == ',')
{
s++;
if (!digitp (s)) /* At least one digit of the fraction required. */
return gpg_error (GPG_ERR_INV_TIME);
s++;
while (digitp (s))
s++;
}
if (*s == 'Z' && (!s[1] || spacep (s+1)))
; /* stop here. */
else if (*s == '-' || *s == '+')
return gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
else
return gpg_error (GPG_ERR_INV_TIME);
snprintf (timebuf, sizeof (gnupg_isotime_t), "%04d%02d%02dT%02d%02d%02d",
year, month, day, hour, minu, sec);
return 0;
}
diff --git a/dirmngr/ldap.c b/dirmngr/ldap.c
index b80012d03..c1ea04979 100644
--- a/dirmngr/ldap.c
+++ b/dirmngr/ldap.c
@@ -1,1027 +1,1027 @@
/* ldap.c - LDAP access
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2005, 2007, 2008, 2010, 2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* DirMngr is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <time.h>
#include <npth.h>
#include "dirmngr.h"
#include "../common/exechelp.h"
#include "crlfetch.h"
#include "ldapserver.h"
#include "misc.h"
#include "ldap-wrapper.h"
#include "ldap-url.h"
#include "../common/host2net.h"
#define UNENCODED_URL_CHARS "abcdefghijklmnopqrstuvwxyz" \
"ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
"01234567890" \
"$-_.+!*'(),"
#define USERCERTIFICATE "userCertificate"
#define CACERTIFICATE "caCertificate"
#define X509CACERT "x509caCert"
#define USERSMIMECERTIFICATE "userSMIMECertificate"
/* Definition for the context of the cert fetch functions. */
struct cert_fetch_context_s
{
ksba_reader_t reader; /* The reader used (shallow copy). */
unsigned char *tmpbuf; /* Helper buffer. */
size_t tmpbufsize; /* Allocated size of tmpbuf. */
int truncated; /* Flag to indicate a truncated output. */
};
/* Add HOST and PORT to our list of LDAP servers. Fixme: We should
better use an extra list of servers. */
static void
add_server_to_servers (const char *host, int port)
{
ldap_server_t server;
ldap_server_t last = NULL;
const char *s;
if (!port)
port = 389;
for (server=opt.ldapservers; server; server = server->next)
{
if (!strcmp (server->host, host) && server->port == port)
return; /* already in list... */
last = server;
}
/* We assume that the host names are all supplied by our
configuration files and thus are sane. To keep this assumption
we must reject all invalid host names. */
for (s=host; *s; s++)
if (!strchr ("abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"01234567890.-", *s))
{
log_error (_("invalid char 0x%02x in host name - not added\n"), *s);
return;
}
log_info (_("adding '%s:%d' to the ldap server list\n"), host, port);
server = xtrycalloc (1, sizeof *s);
if (!server)
log_error (_("malloc failed: %s\n"), strerror (errno));
else
{
server->host = xstrdup (host);
server->port = port;
if (last)
last->next = server;
else
opt.ldapservers = server;
}
}
/* Perform an LDAP query. Returns an gpg error code or 0 on success.
The function returns a new reader object at READER. */
static gpg_error_t
run_ldap_wrapper (ctrl_t ctrl,
int ignore_timeout,
int multi_mode,
int tls_mode,
int ntds,
int areconly,
const char *proxy,
const char *host, int port,
const char *user, const char *pass,
const char *base, const char *filter, const char *attr,
ksba_reader_t *reader)
{
const char *argv[51];
int argc;
char portbuf[30], timeoutbuf[30];
*reader = NULL;
argc = 0;
if (pass && *pass) /* Note, that the password must be the first item. */
{
argv[argc++] = "--pass";
argv[argc++] = pass;
}
if (DBG_LOOKUP)
argv[argc++] = "-vv";
else if (DBG_EXTPROG)
argv[argc++] = "-v";
argv[argc++] = "--log-with-pid";
if (multi_mode)
argv[argc++] = "--multi";
if (tls_mode == 1)
argv[argc++] = "--starttls";
else if (tls_mode)
argv[argc++] = "--ldaptls";
if (ntds)
argv[argc++] = "--ntds";
if (areconly)
argv[argc++] = "--areconly";
if (opt.ldaptimeout)
{
snprintf (timeoutbuf, sizeof timeoutbuf, "%u", opt.ldaptimeout);
argv[argc++] = "--timeout";
argv[argc++] = timeoutbuf;
if (ignore_timeout)
argv[argc++] = "--only-search-timeout";
}
if (proxy)
{
argv[argc++] = "--proxy";
argv[argc++] = proxy;
}
if (host && *host)
{
argv[argc++] = "--host";
argv[argc++] = host;
}
if (port)
{
sprintf (portbuf, "%d", port);
argv[argc++] = "--port";
argv[argc++] = portbuf;
}
if (user && *user)
{
argv[argc++] = "--user";
argv[argc++] = user;
}
if (base && *base)
{
argv[argc++] = "--base";
argv[argc++] = base;
}
if (attr)
{
argv[argc++] = "--attr";
argv[argc++] = attr;
}
if (filter)
argv[argc++] = filter;
argv[argc] = NULL;
return ldap_wrapper (ctrl, reader, argv);
}
/* Perform a LDAP query using a given URL. On success a new ksba
reader is returned. If HOST or PORT are not 0, they are used to
override the values from the URL. */
gpg_error_t
url_fetch_ldap (ctrl_t ctrl, const char *url, ksba_reader_t *reader)
{
gpg_error_t err;
LDAPURLDesc *ludp = NULL;
int tls_mode;
if (!ldap_is_ldap_url (url))
{
log_error (_("'%s' is not an LDAP URL\n"), url);
return gpg_error (GPG_ERR_INV_URI);
}
if (ldap_url_parse (url, &ludp))
{
log_error (_("'%s' is an invalid LDAP URL\n"), url);
return gpg_error (GPG_ERR_INV_URI);
}
if (ludp->lud_filter && ludp->lud_filter[0] != '(')
{
if (!strcmp (ludp->lud_filter, "objectClass=cRLDistributionPoint"))
{
/* Hack for broken DPs in DGN certs. */
log_info ("fixing broken LDAP URL\n");
free (ludp->lud_filter);
ludp->lud_filter
= strdup ("(objectClass=cRLDistributionPoint)");
if (!ludp->lud_filter)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else
{
log_error (_("'%s' is an invalid LDAP URL\n"), url);
err = gpg_error (GPG_ERR_BAD_URI);
goto leave;
}
}
if (ludp->lud_scheme && !strcmp (ludp->lud_scheme, "ldaps"))
- tls_mode = 2; /* LDAP-over-TLS here becuase we get it from certs. */
+ tls_mode = 2; /* LDAP-over-TLS here because we get it from certs. */
else
tls_mode = 0;
err = run_ldap_wrapper (ctrl,
1, /* Ignore explicit timeout because CRLs
might be very large. */
0, /* No Multi-mode. */
tls_mode,
0, /* No AD authentication. */
0, /* No areconly. */
opt.ldap_proxy,
ludp->lud_host, ludp->lud_port,
NULL, NULL, /* user, password */
ludp->lud_dn, /* Base DN */
ludp->lud_filter,
ludp->lud_attrs? ludp->lud_attrs[0] : NULL,
reader);
/* FIXME: This option might be used for DoS attacks. Because it
will enlarge the list of servers to consult without a limit and
all LDAP queries w/o a host are will then try each host in
turn. */
if (!err && opt.add_new_ldapservers && !opt.ldap_proxy)
{
if (ludp->lud_host)
add_server_to_servers (ludp->lud_host, ludp->lud_port);
}
/* If the lookup failed and we are not only using the proxy, we try
again using our default list of servers. */
if (err && !(opt.ldap_proxy && opt.only_ldap_proxy))
{
struct ldapserver_iter iter;
if (DBG_LOOKUP)
log_debug ("no hostname in URL or query failed; "
"trying all default hostnames\n");
for (ldapserver_iter_begin (&iter, ctrl);
err && ! ldapserver_iter_end_p (&iter);
ldapserver_iter_next (&iter))
{
ldap_server_t server = iter.server;
if (server->starttls)
tls_mode = 1;
else if (server->ldap_over_tls)
tls_mode = 2;
else
tls_mode = 0;
err = run_ldap_wrapper (ctrl,
0,
0, /* No Multi-mode */
tls_mode,
server->ntds,
server->areconly,
NULL,
server->host, server->port,
server->user, server->pass,
server->base,
ludp->lud_filter,
ludp->lud_attrs? ludp->lud_attrs[0] : NULL,
reader);
if (!err)
break;
}
}
leave:
ldap_free_urldesc (ludp);
return err;
}
/* Perform an LDAP query on all configured servers. On error the
error code of the last try is returned. */
gpg_error_t
attr_fetch_ldap (ctrl_t ctrl,
const char *dn, const char *attr, ksba_reader_t *reader)
{
gpg_error_t err = gpg_error (GPG_ERR_CONFIGURATION);
struct ldapserver_iter iter;
*reader = NULL;
/* FIXME; we might want to look at the Base DN to try matching
servers first. */
for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter);
ldapserver_iter_next (&iter))
{
ldap_server_t server = iter.server;
int tls_mode;
if (server->starttls)
tls_mode = 1;
else if (server->ldap_over_tls)
tls_mode = 2;
else
tls_mode = 0;
err = run_ldap_wrapper (ctrl,
0,
0,
tls_mode,
server->ntds,
server->areconly,
opt.ldap_proxy,
server->host, server->port,
server->user, server->pass,
dn,
"(objectClass=*)",
attr,
reader);
if (!err)
break; /* Probably found a result. Ready. */
}
return err;
}
/* Return true if VALUE needs escaping. */
static int
rfc2254_need_escape (const char *value)
{
/* NUL needs to be escaped as well but we can represent that in
* VALUE, so no need for it. */
return !!strpbrk (value, "*()\\");
}
/* Escape VALUE using RFC-2254 rules. Returns NULL on error. */
static char *
rfc2254_escape (const char *value)
{
const char *s;
char *buffer, *p;
size_t length = 0;
for (s=value; *s; s++)
switch (*s)
{
case '*':
case '(':
case ')':
case '\\': length += 3; break;
default: length++; break;
}
buffer = xtrymalloc (length+1);
if (!buffer)
return NULL;
p = buffer;
for (s=value; *s; s++)
switch (*s)
{
case '*': p = stpcpy (p, "\\2a"); break;
case '(': p = stpcpy (p, "\\28"); break;
case ')': p = stpcpy (p, "\\29"); break;
case '\\': p = stpcpy (p, "\\5c"); break;
default: *p++ = *s; break;
}
*p = 0;
return buffer;
}
/* Return true if VALUE needs escaping. */
static int
extfilt_need_escape (const char *value)
{
/* NUL needs to be escaped as well but we can represent that in
* VALUE, so no need for it. */
return !!strchr (value, '&');
}
/* Escape VALUE using our extended filter rules from dirmngr_ldap.c.
* Returns NULL on error. */
static char *
extfilt_escape (const char *value)
{
const char *s;
char *buffer, *p;
size_t length = 0;
for (s=value; *s; s++)
{
length++;
if (*s == '&')
length++;
}
buffer = xtrymalloc (length+1);
if (!buffer)
return NULL;
p = buffer;
for (s=value; *s; s++)
{
*p++ = *s;
if (*s == '&')
*p++ = '&';
}
*p = 0;
return buffer;
}
/* Parse PATTERN and return a new filter expression for an LDAP query.
* The extended filter syntax as known by dirmngr_ldap.c is used.
* Caller must release the returned value. R_RESULT is set to NULL on
* error.
*
* Supported patterns:
*
* | Ok | gpg style user id type |
* |-----+------------------------------------------------------|
* | no | KeyID |
* | no | Fingerprint |
* | no | OpenPGP userid |
* | yes | Email address Indicated by a left angle bracket. |
* | no | Exact word match in user id or subj. name |
* | yes | Subj. DN indicated by a leading slash |
* | no | Issuer DN |
* | no | Serial number + subj. DN |
* | yes | Substring match indicated by a leading '*; (default) |
*/
static gpg_error_t
make_one_filter (const char *pattern, char **r_result)
{
gpg_error_t err = 0;
char *pattern_buffer = NULL;
char *result = NULL;
size_t n;
*r_result = NULL;
switch (*pattern)
{
case '<': /* Email. */
{
pattern++;
if (rfc2254_need_escape (pattern)
&& !(pattern = pattern_buffer = rfc2254_escape (pattern)))
{
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("(mail=", pattern, ")", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
n = strlen (result);
if (result[n-2] == '>') /* Strip trailing '>' */
{
result[n-2] = ')';
result[n-1] = 0;
}
break;
}
case '/': /* Subject DN. */
pattern++;
if (*pattern)
{
/* We need just the BaseDN. This assumes that the Subject
- * is correcly stored in the DT. This is however not always
+ * is correctly stored in the DT. This is however not always
* the case and the actual DN is different from the
* subject. In this case we won't find anything. */
if (extfilt_need_escape (pattern)
&& !(pattern = pattern_buffer = extfilt_escape (pattern)))
{
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("^", pattern, "&base&", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
break;
case '#': /* Issuer DN - Not yet working. */
pattern++;
if (*pattern == '/') /* Just issuer DN. */
{
pattern++;
if (extfilt_need_escape (pattern)
&& !(pattern = pattern_buffer = extfilt_escape (pattern)))
{
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("^", pattern, "&base&", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
else /* Serial number + issuer DN */
{
}
break;
case '*':
pattern++;
/* fall through */
default: /* Take as substring match. */
if (*pattern)
{
if (rfc2254_need_escape (pattern)
&& !(pattern = pattern_buffer = rfc2254_escape (pattern)))
{
err = gpg_error_from_syserror ();
goto leave;
}
result = strconcat ("(|(sn=*", pattern,
"*)(|(cn=*", pattern,
"*)(mail=*", pattern,
"*)))", NULL);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
break;
}
if (!result)
err = gpg_error (GPG_ERR_INV_USER_ID);
leave:
xfree (pattern_buffer);
if (err)
xfree (result);
else
*r_result = result;
return err;
}
/* Prepare an LDAP query to return the cACertificate attribute for DN.
* All configured default servers are queried until one responds.
* This function returns an error code or 0 and stored a newly
- * allocated contect object at CONTEXT on success. */
+ * allocated context object at CONTEXT on success. */
gpg_error_t
start_cacert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
const char *dn)
{
gpg_error_t err;
struct ldapserver_iter iter;
*r_context = xtrycalloc (1, sizeof **r_context);
if (!*r_context)
return gpg_error_from_errno (errno);
/* FIXME; we might want to look at the Base DN to try matching
servers first. */
err = gpg_error (GPG_ERR_CONFIGURATION);
for (ldapserver_iter_begin (&iter, ctrl); ! ldapserver_iter_end_p (&iter);
ldapserver_iter_next (&iter))
{
ldap_server_t server = iter.server;
err = run_ldap_wrapper (ctrl,
0,
1, /* --multi (record format) */
0, /* No TLS */
0, /* No AD authentication. */
server->areconly,
opt.ldap_proxy,
server->host, server->port,
server->user, server->pass,
dn, "objectClass=*", "cACertificate",
&(*r_context)->reader);
if (!err)
break; /* Probably found a result. */
}
if (err)
{
xfree (*r_context);
*r_context = NULL;
}
return err;
}
/* Prepare an LDAP query to return certificates matching PATTERNS
* using the SERVER. This function returns an error code or 0 and
* stores a newly allocated object at R_CONTEXT on success. */
gpg_error_t
start_cert_fetch_ldap (ctrl_t ctrl, cert_fetch_context_t *r_context,
strlist_t patterns, const ldap_server_t server)
{
gpg_error_t err;
char *proxy = NULL;
char *host = NULL;
int port;
char *user = NULL;
char *pass = NULL;
char *base = NULL;
char *argv[50];
int argc = 0;
int argc_malloced = 0;
char portbuf[30], timeoutbuf[30];
int starttls, ldaptls, ntds;
*r_context = NULL;
if (opt.ldap_proxy && !(proxy = xtrystrdup (opt.ldap_proxy)))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (server)
{
if (server->host && !(host = xtrystrdup (server->host)))
{
err = gpg_error_from_syserror ();
goto leave;
}
port = server->port;
if (server->user && !(user = xtrystrdup (server->user)))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (server->pass && !(pass = xtrystrdup (server->pass)))
{
err = gpg_error_from_syserror ();
goto leave;
}
if (server->base && !(base = xtrystrdup (server->base)))
{
err = gpg_error_from_syserror ();
goto leave;
}
starttls = server->starttls;
ldaptls = server->ldap_over_tls;
ntds = server->ntds;
}
else /* Use a default server. */
{
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
if (pass && *pass) /* Note: Must be the first item. */
{
argv[argc++] = "--pass";
argv[argc++] = pass;
}
if (DBG_LOOKUP)
argv[argc++] = "-vv";
else if (DBG_EXTPROG)
argv[argc++] = "-v";
argv[argc++] = "--log-with-pid";
argv[argc++] = "--multi";
if (starttls)
argv[argc++] = "--starttls";
else if (ldaptls)
argv[argc++] = "--ldaptls";
if (ntds)
argv[argc++] = "--ntds";
if (opt.ldaptimeout)
{
snprintf (timeoutbuf, sizeof timeoutbuf, "%u", opt.ldaptimeout);
argv[argc++] = "--timeout";
argv[argc++] = timeoutbuf;
}
if (proxy && *proxy)
{
argv[argc++] = "--proxy";
argv[argc++] = proxy;
}
if (host && *host)
{
argv[argc++] = "--host";
argv[argc++] = host;
}
if (port)
{
snprintf (portbuf, sizeof portbuf, "%d", port);
argv[argc++] = "--port";
argv[argc++] = portbuf;
}
if (user && *user)
{
argv[argc++] = "--user";
argv[argc++] = user;
}
if (base && *base)
{
argv[argc++] = "--base";
argv[argc++] = base;
}
/* All entries in argv from this index on are malloc'ed. */
argc_malloced = argc;
for (; patterns; patterns = patterns->next)
{
if (argc >= DIM (argv) - 1)
{
/* Too many patterns. It does not make sense to allow an
- arbitrary number of patters because the length of the
+ arbitrary number of patterns because the length of the
command line is limited anyway. */
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
goto leave;
}
if (*patterns->d)
{
err = make_one_filter (patterns->d, &argv[argc]);
if (err)
goto leave;
argc++;
}
}
argv[argc] = NULL;
*r_context = xtrycalloc (1, sizeof **r_context);
if (!*r_context)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = ldap_wrapper (ctrl, &(*r_context)->reader, (const char**)argv);
if (err)
{
xfree (*r_context);
*r_context = NULL;
}
leave:
for (; argc_malloced < argc; argc_malloced++)
xfree (argv[argc_malloced]);
xfree (proxy);
xfree (host);
xfree (base);
xfree (user);
xfree (pass);
return err;
}
/* Read a fixed amount of data from READER into BUFFER. */
static gpg_error_t
read_buffer (ksba_reader_t reader, unsigned char *buffer, size_t count)
{
gpg_error_t err;
size_t nread;
while (count)
{
err = ksba_reader_read (reader, buffer, count, &nread);
if (err)
return err;
buffer += nread;
count -= nread;
}
return 0;
}
/* Fetch the next certificate. Return 0 on success, GPG_ERR_EOF if no
(more) certificates are available or any other error
code. GPG_ERR_TRUNCATED may be returned to indicate that the result
has been truncated. */
gpg_error_t
fetch_next_cert_ldap (cert_fetch_context_t context,
unsigned char **value, size_t *valuelen)
{
gpg_error_t err;
unsigned char hdr[5];
char *p, *pend;
unsigned long n;
int okay = 0;
/* int is_cms = 0; */
*value = NULL;
*valuelen = 0;
err = 0;
while (!err)
{
err = read_buffer (context->reader, hdr, 5);
if (err)
break;
n = buf32_to_ulong (hdr+1);
if (*hdr == 'V' && okay)
{
#if 0 /* That code to extra a cert from a CMS object is not yet ready. */
if (is_cms)
{
/* The certificate needs to be parsed from CMS data. */
ksba_cms_t cms;
ksba_stop_reason_t stopreason;
int i;
err = ksba_cms_new (&cms);
if (err)
goto leave;
err = ksba_cms_set_reader_writer (cms, context->reader, NULL);
if (err)
{
log_error ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (err));
goto leave;
}
do
{
err = ksba_cms_parse (cms, &stopreason);
if (err)
{
log_error ("ksba_cms_parse failed: %s\n",
gpg_strerror (err));
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA)
log_error ("userSMIMECertificate is not "
"a certs-only message\n");
}
while (stopreason != KSBA_SR_READY);
for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
{
check_and_store (ctrl, stats, cert, 0);
ksba_cert_release (cert);
cert = NULL;
}
if (!i)
log_error ("no certificate found\n");
else
any = 1;
}
else
#endif /* End unfinished code to extract from a CMS object. */
{
*value = xtrymalloc (n);
if (!*value)
return gpg_error_from_errno (errno);
*valuelen = n;
err = read_buffer (context->reader, *value, n);
break; /* Ready or error. */
}
}
else if (!n && *hdr == 'A')
okay = 0;
else if (n)
{
if (n > context->tmpbufsize)
{
xfree (context->tmpbuf);
context->tmpbufsize = 0;
context->tmpbuf = xtrymalloc (n+1);
if (!context->tmpbuf)
return gpg_error_from_errno (errno);
context->tmpbufsize = n;
}
err = read_buffer (context->reader, context->tmpbuf, n);
if (err)
break;
if (*hdr == 'A')
{
p = context->tmpbuf;
p[n] = 0; /*(we allocated one extra byte for this.)*/
/* fixme: is_cms = 0; */
if ( (pend = strchr (p, ';')) )
*pend = 0; /* Strip off the extension. */
if (!ascii_strcasecmp (p, USERCERTIFICATE))
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
USERCERTIFICATE);
okay = 1;
}
else if (!ascii_strcasecmp (p, CACERTIFICATE))
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
CACERTIFICATE);
okay = 1;
}
else if (!ascii_strcasecmp (p, X509CACERT))
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'\n",
CACERTIFICATE);
okay = 1;
}
/* else if (!ascii_strcasecmp (p, USERSMIMECERTIFICATE)) */
/* { */
/* if (DBG_LOOKUP) */
/* log_debug ("fetch_next_cert_ldap: got attribute '%s'\n", */
/* USERSMIMECERTIFICATE); */
/* okay = 1; */
/* is_cms = 1; */
/* } */
else
{
if (DBG_LOOKUP)
log_debug ("fetch_next_cert_ldap: got attribute '%s'"
" - ignored\n", p);
okay = 0;
}
}
else if (*hdr == 'E')
{
p = context->tmpbuf;
p[n] = 0; /*(we allocated one extra byte for this.)*/
if (!strcmp (p, "truncated"))
{
context->truncated = 1;
log_info (_("ldap_search hit the size limit of"
" the server\n"));
}
}
}
}
if (err)
{
xfree (*value);
*value = NULL;
*valuelen = 0;
if (gpg_err_code (err) == GPG_ERR_EOF && context->truncated)
{
context->truncated = 0; /* So that the next call would return EOF. */
err = gpg_error (GPG_ERR_TRUNCATED);
}
}
return err;
}
void
end_cert_fetch_ldap (cert_fetch_context_t context)
{
if (context)
{
ksba_reader_t reader = context->reader;
xfree (context->tmpbuf);
xfree (context);
ldap_wrapper_release_context (reader);
ksba_reader_release (reader);
}
}
diff --git a/dirmngr/ldapserver.c b/dirmngr/ldapserver.c
index ed38c7101..8cd193f86 100644
--- a/dirmngr/ldapserver.c
+++ b/dirmngr/ldapserver.c
@@ -1,219 +1,219 @@
/* dirmngr.c - LDAP access
Copyright (C) 2008 g10 Code GmbH
This file is part of DirMngr.
DirMngr 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 2 of the License, or
(at your option) any later version.
DirMngr 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, write to the Free Software
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
02110-1301, USA. */
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#include "dirmngr.h"
#include "ldapserver.h"
/* Release the list of SERVERS. As usual it is okay to call this
function with SERVERS passed as NULL. */
void
ldapserver_list_free (ldap_server_t servers)
{
while (servers)
{
ldap_server_t tmp = servers->next;
xfree (servers->host);
xfree (servers->user);
if (servers->pass)
memset (servers->pass, 0, strlen (servers->pass));
xfree (servers->pass);
xfree (servers->base);
xfree (servers);
servers = tmp;
}
}
/* Parse a single LDAP server configuration line. Returns the server
* or NULL in case of errors. The configuration line is assumed to be
* colon separated with these fields:
*
* 1. field: Hostname
* 2. field: Portnumber
* 3. field: Username
* 4. field: Password
* 5. field: Base DN
* 6. field: Flags
*
* Flags are:
*
* starttls := Use STARTTLS with a default port of 389
- * ldaptls := Tunnel LDAP trough a TLS tunnel with default port 636
+ * ldaptls := Tunnel LDAP through a TLS tunnel with default port 636
* plain := Switch to plain unsecured LDAP.
* (The last of these 3 flags is the effective one)
* ntds := Use Active Directory authentication
* areconly := Use option LDAP_OPT_AREC_EXCLUSIVE
*
* FILENAME and LINENO are used for diagnostic purposes only.
*/
ldap_server_t
ldapserver_parse_one (const char *line,
const char *filename, unsigned int lineno)
{
char *p;
const char *s;
ldap_server_t server;
int fieldno;
int fail = 0;
int i;
char **fields = NULL;
server = xtrycalloc (1, sizeof *server);
if (!server)
{
fail = 1;
goto leave;
}
fields = strtokenize (line, ":");
if (!fields)
{
fail = 1;
goto leave;
}
for (fieldno=0; (p = fields[fieldno]); fieldno++)
{
switch (fieldno)
{
case 0:
server->host = xtrystrdup (p);
if (!server->host)
fail = 1;
break;
case 1:
if (*p)
server->port = atoi (p);
break;
case 2:
server->user = xtrystrdup (p);
if (!server->user)
fail = 1;
break;
case 3:
if (*p && !server->user)
{
if (filename)
log_error (_("%s:%u: password given without user\n"),
filename, lineno);
else
log_error ("ldap: password given without user ('%s')\n", line);
fail = 1;
}
else if (*p)
{
server->pass = xtrystrdup (p);
if (!server->pass)
fail = 1;
}
break;
case 4:
if (*p)
{
server->base = xtrystrdup (p);
if (!server->base)
fail = 1;;
}
break;
case 5:
{
char **flags = NULL;
flags = strtokenize (p, ",");
if (!flags)
{
log_error ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
fail = 1;
break;
}
for (i=0; (s = flags[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "starttls"))
{
server->starttls = 1;
server->ldap_over_tls = 0;
}
else if (!ascii_strcasecmp (s, "ldaptls"))
{
server->starttls = 0;
server->ldap_over_tls = 1;
}
else if (!ascii_strcasecmp (s, "plain"))
{
server->starttls = 0;
server->ldap_over_tls = 0;
}
else if (!ascii_strcasecmp (s, "ntds"))
{
server->ntds = 1;
}
else if (!ascii_strcasecmp (s, "areconly"))
{
server->areconly = 1;
}
else
{
if (filename)
log_info (_("%s:%u: ignoring unknown flag '%s'\n"),
filename, lineno, s);
else
log_info ("ldap: unknown flag '%s' ignored in (%s)\n",
s, line);
}
}
xfree (flags);
}
break;
default:
/* (We silently ignore extra fields.) */
break;
}
}
leave:
if (fail)
{
if (filename)
log_info (_("%s:%u: skipping this line\n"), filename, lineno);
else
log_info ("ldap: error in server spec ('%s')\n", line);
ldapserver_list_free (server);
server = NULL;
}
xfree (fields);
return server;
}
diff --git a/dirmngr/ocsp.c b/dirmngr/ocsp.c
index ad7ed962a..7de8b10e4 100644
--- a/dirmngr/ocsp.c
+++ b/dirmngr/ocsp.c
@@ -1,977 +1,977 @@
/* ocsp.c - OCSP management
* Copyright (C) 2004, 2007 g10 Code GmbH
*
* This file is part of DirMngr.
*
* DirMngr 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 2 of the License, or
* (at your option) any later version.
*
* DirMngr 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include "dirmngr.h"
#include "misc.h"
#include "http.h"
#include "validate.h"
#include "certcache.h"
#include "ocsp.h"
-/* The maximum size we allow as a response from an OCSP reponder. */
+/* The maximum size we allow as a response from an OCSP responder. */
#define MAX_RESPONSE_SIZE 65536
static const char oidstr_ocsp[] = "1.3.6.1.5.5.7.48.1";
/* Telesec attribute used to implement a positive confirmation.
CertHash ::= SEQUENCE {
HashAlgorithm AlgorithmIdentifier,
certificateHash OCTET STRING }
*/
/* static const char oidstr_certHash[] = "1.3.36.8.3.13"; */
/* Read from FP and return a newly allocated buffer in R_BUFFER with the
entire data read from FP. */
static gpg_error_t
read_response (estream_t fp, unsigned char **r_buffer, size_t *r_buflen)
{
gpg_error_t err;
unsigned char *buffer;
size_t bufsize, nbytes;
*r_buffer = NULL;
*r_buflen = 0;
bufsize = 4096;
buffer = xtrymalloc (bufsize);
if (!buffer)
return gpg_error_from_errno (errno);
nbytes = 0;
for (;;)
{
unsigned char *tmp;
size_t nread = 0;
assert (nbytes < bufsize);
nread = es_fread (buffer+nbytes, 1, bufsize-nbytes, fp);
if (nread < bufsize-nbytes && es_ferror (fp))
{
err = gpg_error_from_errno (errno);
log_error (_("error reading from responder: %s\n"),
strerror (errno));
xfree (buffer);
return err;
}
if ( !(nread == bufsize-nbytes && !es_feof (fp)))
{ /* Response successfully received. */
nbytes += nread;
*r_buffer = buffer;
*r_buflen = nbytes;
return 0;
}
nbytes += nread;
/* Need to enlarge the buffer. */
if (bufsize >= MAX_RESPONSE_SIZE)
{
log_error (_("response from server too large; limit is %d bytes\n"),
MAX_RESPONSE_SIZE);
xfree (buffer);
return gpg_error (GPG_ERR_TOO_LARGE);
}
bufsize += 4096;
tmp = xtryrealloc (buffer, bufsize);
if (!tmp)
{
err = gpg_error_from_errno (errno);
xfree (buffer);
return err;
}
buffer = tmp;
}
}
/* Construct an OCSP request, send it to the configured OCSP responder
and parse the response. On success the OCSP context may be used to
further process the response. The signature value and the
production date are returned at R_SIGVAL and R_PRODUCED_AT; they
may be NULL or an empty string if not available. A new hash
context is returned at R_MD. */
static gpg_error_t
do_ocsp_request (ctrl_t ctrl, ksba_ocsp_t ocsp,
const char *url, ksba_cert_t cert, ksba_cert_t issuer_cert,
ksba_sexp_t *r_sigval, ksba_isotime_t r_produced_at,
gcry_md_hd_t *r_md)
{
gpg_error_t err;
unsigned char *request, *response;
size_t requestlen, responselen;
http_t http;
ksba_ocsp_response_status_t response_status;
const char *t;
int redirects_left = 2;
char *free_this = NULL;
(void)ctrl;
*r_sigval = NULL;
*r_produced_at = 0;
*r_md = NULL;
if (dirmngr_use_tor ())
{
/* For now we do not allow OCSP via Tor due to possible privacy
concerns. Needs further research. */
const char *msg = _("OCSP request not possible due to Tor mode");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
log_error ("%s", msg);
dirmngr_status_printf (ctrl, "NOTE", "no_ocsp_due_to_tor %u %s", err,msg);
return err;
}
if (opt.disable_http)
{
log_error (_("OCSP request not possible due to disabled HTTP\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
err = ksba_ocsp_add_target (ocsp, cert, issuer_cert);
if (err)
{
log_error (_("error setting OCSP target: %s\n"), gpg_strerror (err));
return err;
}
{
size_t n;
unsigned char nonce[32];
n = ksba_ocsp_set_nonce (ocsp, NULL, 0);
if (n > sizeof nonce)
n = sizeof nonce;
gcry_create_nonce (nonce, n);
ksba_ocsp_set_nonce (ocsp, nonce, n);
}
err = ksba_ocsp_build_request (ocsp, &request, &requestlen);
if (err)
{
log_error (_("error building OCSP request: %s\n"), gpg_strerror (err));
return err;
}
once_more:
err = http_open (ctrl, &http, HTTP_REQ_POST, url, NULL, NULL,
((opt.honor_http_proxy? HTTP_FLAG_TRY_PROXY:0)
| (dirmngr_use_tor ()? HTTP_FLAG_FORCE_TOR:0)
| (opt.disable_ipv4? HTTP_FLAG_IGNORE_IPv4 : 0)
| (opt.disable_ipv6? HTTP_FLAG_IGNORE_IPv6 : 0)),
ctrl->http_proxy, NULL, NULL, NULL);
if (err)
{
log_error (_("error connecting to '%s': %s\n"), url, gpg_strerror (err));
xfree (free_this);
return err;
}
es_fprintf (http_get_write_ptr (http),
"Content-Type: application/ocsp-request\r\n"
"Content-Length: %lu\r\n",
(unsigned long)requestlen );
http_start_data (http);
if (es_fwrite (request, requestlen, 1, http_get_write_ptr (http)) != 1)
{
err = gpg_error_from_errno (errno);
log_error ("error sending request to '%s': %s\n", url, strerror (errno));
http_close (http, 0);
xfree (request);
xfree (free_this);
return err;
}
xfree (request);
request = NULL;
err = http_wait_response (http);
if (err || http_get_status_code (http) != 200)
{
if (err)
log_error (_("error reading HTTP response for '%s': %s\n"),
url, gpg_strerror (err));
else
{
switch (http_get_status_code (http))
{
case 301:
case 302:
{
const char *s = http_get_header (http, "Location", 0);
log_info (_("URL '%s' redirected to '%s' (%u)\n"),
url, s?s:"[none]", http_get_status_code (http));
if (s && *s && redirects_left-- )
{
xfree (free_this); url = NULL;
free_this = xtrystrdup (s);
if (!free_this)
err = gpg_error_from_errno (errno);
else
{
url = free_this;
http_close (http, 0);
goto once_more;
}
}
else
err = gpg_error (GPG_ERR_NO_DATA);
log_error (_("too many redirections\n"));
}
break;
case 413: /* Payload too large */
err = gpg_error (GPG_ERR_TOO_LARGE);
break;
default:
log_error (_("error accessing '%s': http status %u\n"),
url, http_get_status_code (http));
err = gpg_error (GPG_ERR_NO_DATA);
break;
}
}
http_close (http, 0);
xfree (free_this);
return err;
}
err = read_response (http_get_read_ptr (http), &response, &responselen);
http_close (http, 0);
if (err)
{
log_error (_("error reading HTTP response for '%s': %s\n"),
url, gpg_strerror (err));
xfree (free_this);
return err;
}
/* log_printhex (response, responselen, "ocsp response"); */
err = ksba_ocsp_parse_response (ocsp, response, responselen,
&response_status);
if (err)
{
log_error (_("error parsing OCSP response for '%s': %s\n"),
url, gpg_strerror (err));
xfree (response);
xfree (free_this);
return err;
}
switch (response_status)
{
case KSBA_OCSP_RSPSTATUS_SUCCESS: t = "success"; break;
case KSBA_OCSP_RSPSTATUS_MALFORMED: t = "malformed"; break;
case KSBA_OCSP_RSPSTATUS_INTERNAL: t = "internal error"; break;
case KSBA_OCSP_RSPSTATUS_TRYLATER: t = "try later"; break;
case KSBA_OCSP_RSPSTATUS_SIGREQUIRED: t = "must sign request"; break;
case KSBA_OCSP_RSPSTATUS_UNAUTHORIZED: t = "unauthorized"; break;
case KSBA_OCSP_RSPSTATUS_REPLAYED: t = "replay detected"; break;
case KSBA_OCSP_RSPSTATUS_OTHER: t = "other (unknown)"; break;
case KSBA_OCSP_RSPSTATUS_NONE: t = "no status"; break;
default: t = "[unknown status]"; break;
}
if (response_status == KSBA_OCSP_RSPSTATUS_SUCCESS)
{
int hash_algo;
if (opt.verbose)
log_info (_("OCSP responder at '%s' status: %s\n"), url, t);
/* Get the signature value now because we can call this function
* only once. */
*r_sigval = ksba_ocsp_get_sig_val (ocsp, r_produced_at);
hash_algo = hash_algo_from_sigval (*r_sigval);
if (!hash_algo)
{
if (opt.verbose)
log_info ("ocsp: using SHA-256 as fallback hash algo.\n");
hash_algo = GCRY_MD_SHA256;
}
err = gcry_md_open (r_md, hash_algo, 0);
if (err)
{
log_error (_("failed to establish a hashing context for OCSP: %s\n"),
gpg_strerror (err));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (*r_md, "ocsp");
err = ksba_ocsp_hash_response (ocsp, response, responselen,
HASH_FNC, *r_md);
if (err)
log_error (_("hashing the OCSP response for '%s' failed: %s\n"),
url, gpg_strerror (err));
}
else
{
log_error (_("OCSP responder at '%s' status: %s\n"), url, t);
err = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (response);
xfree (free_this);
if (err)
{
xfree (*r_sigval);
*r_sigval = NULL;
*r_produced_at = 0;
gcry_md_close (*r_md);
*r_md = NULL;
}
return err;
}
/* Validate that CERT is indeed valid to sign an OCSP response. If
SIGNER_FPR_LIST is not NULL we simply check that CERT matches one
of the fingerprints in this list. */
static gpg_error_t
validate_responder_cert (ctrl_t ctrl, ksba_cert_t cert,
fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
char *fpr;
if (signer_fpr_list)
{
fpr = get_fingerprint_hexstring (cert);
for (; signer_fpr_list && strcmp (signer_fpr_list->hexfpr, fpr);
signer_fpr_list = signer_fpr_list->next)
;
if (signer_fpr_list)
err = 0;
else
{
log_error (_("not signed by a default OCSP signer's certificate"));
err = gpg_error (GPG_ERR_BAD_CA_CERT);
}
xfree (fpr);
}
else
{
/* We avoid duplicating the entire certificate validation code
from gpgsm here. Because we have no way calling back to the
client and letting it compute the validity, we use the ugly
hack of telling the client that the response will only be
valid if the certificate given in this status message is
valid.
Note, that in theory we could simply ask the client via an
inquire to validate a certificate but this might involve
calling DirMngr again recursively - we can't do that as of now
(neither DirMngr nor gpgsm have the ability for concurrent
access to DirMngr. */
/* FIXME: We should cache this certificate locally, so that the next
call to dirmngr won't need to look it up - if this works at
all. */
fpr = get_fingerprint_hexstring (cert);
dirmngr_status (ctrl, "ONLY_VALID_IF_CERT_VALID", fpr, NULL);
xfree (fpr);
err = 0;
}
return err;
}
/* Helper for check_signature. MD is the finalized hash context. */
static gpg_error_t
check_signature_core (ctrl_t ctrl, ksba_cert_t cert, gcry_sexp_t s_sig,
gcry_md_hd_t md, fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
gcry_sexp_t s_pkey = NULL;
gcry_sexp_t s_hash = NULL;
const char *s;
int mdalgo, mdlen;
/* Get the public key as a gcrypt s-expression. */
{
ksba_sexp_t pk = ksba_cert_get_public_key (cert);
if (!pk)
err = gpg_error (GPG_ERR_INV_OBJ);
else
{
err = canon_sexp_to_gcry (pk, &s_pkey);
xfree (pk);
}
if (err)
goto leave;
}
mdalgo = gcry_md_get_algo (md);
mdlen = gcry_md_get_algo_dlen (mdalgo);
if (pk_algo_from_sexp (s_pkey) == GCRY_PK_ECC)
{
unsigned int qbits0, qbits;
qbits0 = gcry_pk_get_nbits (s_pkey);
qbits = qbits0 == 521? 512 : qbits0;
if ((qbits%8))
{
log_error ("ECDSA requires the hash length to be a"
" multiple of 8 bits\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
/* Don't allow any Q smaller than 160 bits. */
if (qbits < 160)
{
log_error (_("%s key uses an unsafe (%u bit) hash\n"),
"ECDSA", qbits0);
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
/* Check if we're too short. */
if (mdlen < qbits/8)
{
log_error (_("a %u bit hash is not valid for a %u bit %s key\n"),
(unsigned int)mdlen*8,
qbits0,
"ECDSA");
if (mdlen < 20)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
}
/* Truncate. */
if (mdlen > qbits/8)
mdlen = qbits/8;
err = gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))",
(int)mdlen, gcry_md_read (md, mdalgo));
}
else if (mdalgo && (s = gcry_md_algo_name (mdalgo)) && strlen (s) < 16)
{
/* Assume RSA */
char hashalgostr[16+1];
int i;
for (i=0; s[i]; i++)
hashalgostr[i] = ascii_tolower (s[i]);
hashalgostr[i] = 0;
err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
hashalgostr,
(int)mdlen,
gcry_md_read (md, mdalgo));
}
else
err = gpg_error (GPG_ERR_DIGEST_ALGO);
if (err)
{
log_error (_("creating S-expression failed: %s\n"), gcry_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
{
gcry_log_debugsxp ("sig ", s_sig);
gcry_log_debugsxp ("hash", s_hash);
}
err = gcry_pk_verify (s_sig, s_hash, s_pkey);
if (err)
goto leave;
err = validate_responder_cert (ctrl, cert, signer_fpr_list);
leave:
gcry_sexp_release (s_hash);
gcry_sexp_release (s_pkey);
return err;
}
/* Check the signature of an OCSP response. OCSP is the context,
S_SIG the signature value and MD the handle of the hash we used for
the response. This function automagically finds the correct public
- key. If SIGNER_FPR_LIST is not NULL, the default OCSP reponder has been
+ key. If SIGNER_FPR_LIST is not NULL, the default OCSP responder has been
used and thus the certificate is one of those identified by
the fingerprints. */
static gpg_error_t
check_signature (ctrl_t ctrl,
ksba_ocsp_t ocsp, gcry_sexp_t s_sig, gcry_md_hd_t md,
fingerprint_list_t signer_fpr_list)
{
gpg_error_t err;
int cert_idx;
ksba_cert_t cert;
/* Create a suitable S-expression with the hash value of our response. */
gcry_md_final (md);
/* Get rid of old OCSP specific certificate references. */
release_ctrl_ocsp_certs (ctrl);
if (signer_fpr_list && !signer_fpr_list->next)
{
/* There is exactly one signer fingerprint given. Thus we use
the default OCSP responder's certificate and instantly know
the certificate to use. */
cert = get_cert_byhexfpr (signer_fpr_list->hexfpr);
if (!cert)
cert = get_cert_local (ctrl, signer_fpr_list->hexfpr);
if (cert)
{
err = check_signature_core (ctrl, cert, s_sig, md,
signer_fpr_list);
ksba_cert_release (cert);
cert = NULL;
if (!err)
{
return 0; /* Successfully verified the signature. */
}
}
}
else
{
char *name;
ksba_sexp_t keyid;
/* Put all certificates included in the response into the cache
and setup a list of those certificate which will later be
preferred used when locating certificates. */
for (cert_idx=0; (cert = ksba_ocsp_get_cert (ocsp, cert_idx));
cert_idx++)
{
cert_ref_t cref;
/* dump_cert ("from ocsp response", cert); */
cref = xtrymalloc (sizeof *cref);
if (!cref)
{
err = gpg_error_from_syserror ();
log_error (_("allocating list item failed: %s\n"),
gpg_strerror (err));
}
else if (!cache_cert_silent (cert, &cref->fpr))
{
cref->next = ctrl->ocsp_certs;
ctrl->ocsp_certs = cref;
}
else
xfree (cref);
}
/* Get the certificate by means of the responder ID. */
err = ksba_ocsp_get_responder_id (ocsp, &name, &keyid);
if (err)
{
log_error (_("error getting responder ID: %s\n"),
gcry_strerror (err));
return err;
}
cert = find_cert_bysubject (ctrl, name, keyid);
if (!cert)
{
log_error ("responder certificate ");
if (name)
log_printf ("'/%s' ", name);
if (keyid)
{
log_printf ("{");
dump_serial (keyid);
log_printf ("} ");
}
log_printf ("not found\n");
}
if (cert)
{
err = check_signature_core (ctrl, cert, s_sig, md, signer_fpr_list);
ksba_cert_release (cert);
if (!err)
{
ksba_free (name);
ksba_free (keyid);
return 0; /* Successfully verified the signature. */
}
log_error ("responder certificate ");
if (name)
log_printf ("'/%s' ", name);
if (keyid)
{
log_printf ("{");
dump_serial (keyid);
log_printf ("} ");
}
log_printf ("did not verify: %s\n", gpg_strerror (err));
}
ksba_free (name);
ksba_free (keyid);
}
log_error (_("no suitable certificate found to verify the OCSP response\n"));
return gpg_error (GPG_ERR_NO_PUBKEY);
}
/* Check whether the certificate either given by fingerprint CERT_FPR
or directly through the CERT object is valid by running an OCSP
transaction. With FORCE_DEFAULT_RESPONDER set only the configured
default responder is used. If R_REVOKED_AT or R_REASON are not
- NULL and the certificat has been revoked the revocation time and
+ NULL and the certificate has been revoked the revocation time and
the reasons are stored there. */
gpg_error_t
ocsp_isvalid (ctrl_t ctrl, ksba_cert_t cert, const char *cert_fpr,
int force_default_responder, ksba_isotime_t r_revoked_at,
const char **r_reason)
{
gpg_error_t err;
ksba_ocsp_t ocsp = NULL;
ksba_cert_t issuer_cert = NULL;
ksba_sexp_t sigval = NULL;
gcry_sexp_t s_sig = NULL;
ksba_isotime_t current_time;
ksba_isotime_t this_update, next_update, revocation_time, produced_at;
ksba_isotime_t tmp_time;
ksba_status_t status;
ksba_crl_reason_t reason;
char *url_buffer = NULL;
const char *url;
gcry_md_hd_t md = NULL;
int i, idx;
char *oid;
ksba_name_t name;
fingerprint_list_t default_signer = NULL;
const char *sreason;
if (r_revoked_at)
*r_revoked_at = 0;
if (r_reason)
*r_reason = NULL;
/* Get the certificate. */
if (cert)
{
ksba_cert_ref (cert);
err = find_issuing_cert (ctrl, cert, &issuer_cert);
if (err)
{
log_error (_("issuer certificate not found: %s\n"),
gpg_strerror (err));
goto leave;
}
}
else
{
cert = get_cert_local (ctrl, cert_fpr);
if (!cert)
{
log_error (_("caller did not return the target certificate\n"));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer_cert = get_issuing_cert_local (ctrl, NULL);
if (!issuer_cert)
{
log_error (_("caller did not return the issuing certificate\n"));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
/* Create an OCSP instance. */
err = ksba_ocsp_new (&ocsp);
if (err)
{
log_error (_("failed to allocate OCSP context: %s\n"),
gpg_strerror (err));
goto leave;
}
/* Figure out the OCSP responder to use.
- 1. Try to get the reponder from the certificate.
+ 1. Try to get the responder from the certificate.
We do only take http and https style URIs into account.
2. If this fails use the default responder, if any.
*/
url = NULL;
for (idx=0; !url && !opt.ignore_ocsp_service_url && !force_default_responder
&& !(err=ksba_cert_get_authority_info_access (cert, idx,
&oid, &name)); idx++)
{
if ( !strcmp (oid, oidstr_ocsp) )
{
for (i=0; !url && ksba_name_enum (name, i); i++)
{
char *p = ksba_name_get_uri (name, i);
if (p && (!ascii_strncasecmp (p, "http:", 5)
|| !ascii_strncasecmp (p, "https:", 6)))
url = url_buffer = p;
else
xfree (p);
}
}
ksba_name_release (name);
ksba_free (oid);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err));
goto leave;
}
if (!url)
{
if (!opt.ocsp_responder || !*opt.ocsp_responder)
{
log_info (_("no default OCSP responder defined\n"));
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
if (!opt.ocsp_signer)
{
log_info (_("no default OCSP signer defined\n"));
err = gpg_error (GPG_ERR_CONFIGURATION);
goto leave;
}
url = opt.ocsp_responder;
default_signer = opt.ocsp_signer;
if (opt.verbose)
log_info (_("using default OCSP responder '%s'\n"), url);
}
else
{
if (opt.verbose)
log_info (_("using OCSP responder '%s'\n"), url);
}
/* Ask the OCSP responder. */
err = do_ocsp_request (ctrl, ocsp, url, cert, issuer_cert,
&sigval, produced_at, &md);
if (err)
goto leave;
/* It is sometimes useful to know the responder ID. */
if (opt.verbose)
{
char *resp_name;
ksba_sexp_t resp_keyid;
err = ksba_ocsp_get_responder_id (ocsp, &resp_name, &resp_keyid);
if (err)
log_info (_("error getting responder ID: %s\n"), gpg_strerror (err));
else
{
log_info ("responder id: ");
if (resp_name)
log_printf ("'/%s' ", resp_name);
if (resp_keyid)
{
log_printf ("{");
dump_serial (resp_keyid);
log_printf ("} ");
}
log_printf ("\n");
}
ksba_free (resp_name);
ksba_free (resp_keyid);
err = 0;
}
/* We got a useful answer, check that the answer has a valid signature. */
if (!sigval || !*produced_at || !md)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
if ( (err = canon_sexp_to_gcry (sigval, &s_sig)) )
goto leave;
xfree (sigval);
sigval = NULL;
err = check_signature (ctrl, ocsp, s_sig, md, default_signer);
if (err)
goto leave;
/* We only support one certificate per request. Check that the
answer matches the right certificate. */
err = ksba_ocsp_get_status (ocsp, cert,
&status, this_update, next_update,
revocation_time, &reason);
if (err)
{
log_error (_("error getting OCSP status for target certificate: %s\n"),
gpg_strerror (err));
goto leave;
}
/* In case the certificate has been revoked, we better invalidate
our cached validation status. */
if (status == KSBA_STATUS_REVOKED)
{
time_t validated_at = 0; /* That is: No cached validation available. */
err = ksba_cert_set_user_data (cert, "validated_at",
&validated_at, sizeof (validated_at));
if (err)
{
log_error ("set_user_data(validated_at) failed: %s\n",
gpg_strerror (err));
err = 0; /* The certificate is anyway revoked, and that is a
more important message than the failure of our
cache. */
}
switch (reason)
{
case KSBA_CRLREASON_UNSPECIFIED:
sreason = "unspecified"; break;
case KSBA_CRLREASON_KEY_COMPROMISE:
sreason = "key compromise"; break;
case KSBA_CRLREASON_CA_COMPROMISE:
sreason = "CA compromise"; break;
case KSBA_CRLREASON_AFFILIATION_CHANGED:
sreason = "affiliation changed"; break;
case KSBA_CRLREASON_SUPERSEDED:
sreason = "superseded"; break;
case KSBA_CRLREASON_CESSATION_OF_OPERATION:
sreason = "cessation of operation"; break;
case KSBA_CRLREASON_CERTIFICATE_HOLD:
sreason = "certificate on hold"; break;
case KSBA_CRLREASON_REMOVE_FROM_CRL:
sreason = "removed from CRL"; break;
case KSBA_CRLREASON_PRIVILEGE_WITHDRAWN:
sreason = "privilege withdrawn"; break;
case KSBA_CRLREASON_AA_COMPROMISE:
sreason = "AA compromise"; break;
case KSBA_CRLREASON_OTHER:
sreason = "other"; break;
default: sreason = "?"; break;
}
}
else
sreason = "";
if (opt.verbose)
{
log_info (_("certificate status is: %s (this=%s next=%s)\n"),
status == KSBA_STATUS_GOOD? _("good"):
status == KSBA_STATUS_REVOKED? _("revoked"):
status == KSBA_STATUS_UNKNOWN? _("unknown"):
status == KSBA_STATUS_NONE? _("none"): "?",
this_update, next_update);
if (status == KSBA_STATUS_REVOKED)
log_info (_("certificate has been revoked at: %s due to: %s\n"),
revocation_time, sreason);
}
if (status == KSBA_STATUS_REVOKED)
{
err = gpg_error (GPG_ERR_CERT_REVOKED);
if (r_revoked_at)
gnupg_copy_time (r_revoked_at, revocation_time);
if (r_reason)
*r_reason = sreason;
}
else if (status == KSBA_STATUS_UNKNOWN)
err = gpg_error (GPG_ERR_NO_DATA);
else if (status != KSBA_STATUS_GOOD)
err = gpg_error (GPG_ERR_GENERAL);
/* Allow for some clock skew. */
gnupg_get_isotime (current_time);
add_seconds_to_isotime (current_time, opt.ocsp_max_clock_skew);
if (strcmp (this_update, current_time) > 0 )
{
log_error (_("OCSP responder returned a status in the future\n"));
log_info ("used now: %s this_update: %s\n", current_time, this_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
/* Check that THIS_UPDATE is not too far back in the past. */
gnupg_copy_time (tmp_time, this_update);
add_seconds_to_isotime (tmp_time,
opt.ocsp_max_period+opt.ocsp_max_clock_skew);
if (!*tmp_time || strcmp (tmp_time, current_time) < 0 )
{
log_error (_("OCSP responder returned a non-current status\n"));
log_info ("used now: %s this_update: %s\n",
current_time, this_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
/* Check that we are not beyond NEXT_UPDATE (plus some extra time). */
if (*next_update)
{
gnupg_copy_time (tmp_time, next_update);
add_seconds_to_isotime (tmp_time,
opt.ocsp_current_period+opt.ocsp_max_clock_skew);
if (!*tmp_time && strcmp (tmp_time, current_time) < 0 )
{
log_error (_("OCSP responder returned an too old status\n"));
log_info ("used now: %s next_update: %s\n",
current_time, next_update);
if (!err)
err = gpg_error (GPG_ERR_TIME_CONFLICT);
}
}
leave:
gcry_md_close (md);
gcry_sexp_release (s_sig);
xfree (sigval);
ksba_cert_release (issuer_cert);
ksba_cert_release (cert);
ksba_ocsp_release (ocsp);
xfree (url_buffer);
return err;
}
/* Release the list of OCSP certificates hold in the CTRL object. */
void
release_ctrl_ocsp_certs (ctrl_t ctrl)
{
while (ctrl->ocsp_certs)
{
cert_ref_t tmp = ctrl->ocsp_certs->next;
xfree (ctrl->ocsp_certs);
ctrl->ocsp_certs = tmp;
}
}
diff --git a/dirmngr/server.c b/dirmngr/server.c
index 32c85d07b..710317e5e 100644
--- a/dirmngr/server.c
+++ b/dirmngr/server.c
@@ -1,3405 +1,3405 @@
/* server.c - LDAP and Keyserver access server
* Copyright (C) 2002 Klarälvdalens Datakonsult AB
* Copyright (C) 2003, 2004, 2005, 2007, 2008, 2009, 2011, 2015 g10 Code GmbH
* Copyright (C) 2014, 2015, 2016 Werner Koch
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# include <winsock2.h>
# include <sddl.h>
#endif
#include "dirmngr.h"
#include <assuan.h>
#include "crlcache.h"
#include "crlfetch.h"
#if USE_LDAP
# include "ldapserver.h"
#endif
#include "ocsp.h"
#include "certcache.h"
#include "validate.h"
#include "misc.h"
#if USE_LDAP
# include "ldap-wrapper.h"
#endif
#include "ks-action.h"
#include "ks-engine.h"
#if USE_LDAP
# include "ldap-parse-uri.h"
#endif
#include "dns-stuff.h"
#include "../common/mbox-util.h"
#include "../common/zb32.h"
#include "../common/server-help.h"
/* To avoid DoS attacks we limit the size of a certificate to
something reasonable. The DoS was actually only an issue back when
Dirmngr was a system service and not a user service. */
#define MAX_CERT_LENGTH (16*1024)
/* The limit for the CERTLIST inquiry. We allow for up to 20
* certificates but also take PEM encoding into account. */
#define MAX_CERTLIST_LENGTH ((MAX_CERT_LENGTH * 20 * 4)/3)
/* The same goes for OpenPGP keyblocks, but here we need to allow for
much longer blocks; a 200k keyblock is not too unusual for keys
with a lot of signatures (e.g. 0x5b0358a2). 9C31503C6D866396 even
has 770 KiB as of 2015-08-23. To avoid adding a runtime option we
now use 20MiB which should really be enough. Well, a key with
several pictures could be larger (the parser as a 18MiB limit for
attribute packets) but it won't be nice to the keyservers to send
them such large blobs. */
#define MAX_KEYBLOCK_LENGTH (20*1024*1024)
#define PARM_ERROR(t) assuan_set_error (ctx, \
gpg_error (GPG_ERR_ASS_PARAMETER), (t))
#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \
/**/: gpg_error (e))
/* Control structure per connection. */
struct server_local_s
{
/* Data used to associate an Assuan context with local server data */
assuan_context_t assuan_ctx;
/* The session id (a counter). */
unsigned int session_id;
/* Per-session LDAP servers. */
ldap_server_t ldapservers;
/* Per-session list of keyservers. */
uri_item_t keyservers;
/* If this flag is set to true this dirmngr process will be
terminated after the end of this session. */
int stopme;
/* State variable private to is_tor_running. */
int tor_state;
/* If the first both flags are set the assuan logging of data lines
* is suppressed. The count variable is used to show the number of
* non-logged bytes. */
size_t inhibit_data_logging_count;
unsigned int inhibit_data_logging : 1;
unsigned int inhibit_data_logging_now : 1;
};
/* Cookie definition for assuan data line output. */
static gpgrt_ssize_t data_line_cookie_write (void *cookie,
const void *buffer, size_t size);
static int data_line_cookie_close (void *cookie);
static es_cookie_io_functions_t data_line_cookie_functions =
{
NULL,
data_line_cookie_write,
NULL,
data_line_cookie_close
};
/* Local prototypes */
static const char *task_check_wkd_support (ctrl_t ctrl, const char *domain);
/* Accessor for the local ldapservers variable. */
ldap_server_t
get_ldapservers_from_ctrl (ctrl_t ctrl)
{
if (ctrl && ctrl->server_local)
return ctrl->server_local->ldapservers;
else
return NULL;
}
/* Release an uri_item_t list. */
void
release_uri_item_list (uri_item_t list)
{
while (list)
{
uri_item_t tmp = list->next;
http_release_parsed_uri (list->parsed_uri);
xfree (list);
list = tmp;
}
}
/* Release all configured keyserver info from CTRL. */
void
release_ctrl_keyservers (ctrl_t ctrl)
{
if (! ctrl->server_local)
return;
release_uri_item_list (ctrl->server_local->keyservers);
ctrl->server_local->keyservers = NULL;
}
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* This is a wrapper around assuan_send_data which makes debugging the
output in verbose mode easier. */
static gpg_error_t
data_line_write (assuan_context_t ctx, const void *buffer_arg, size_t size)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
const char *buffer = buffer_arg;
gpg_error_t err;
/* If we do not want logging, enable it here. */
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
ctrl->server_local->inhibit_data_logging_now = 1;
if (opt.verbose && buffer && size)
{
/* Ease reading of output by sending a physical line at each LF. */
const char *p;
size_t n, nbytes;
nbytes = size;
do
{
p = memchr (buffer, '\n', nbytes);
n = p ? (p - buffer) + 1 : nbytes;
err = assuan_send_data (ctx, buffer, n);
if (err)
{
gpg_err_set_errno (EIO);
goto leave;
}
buffer += n;
nbytes -= n;
if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
{
gpg_err_set_errno (EIO);
goto leave;
}
}
while (nbytes);
}
else
{
err = assuan_send_data (ctx, buffer, size);
if (err)
{
gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
goto leave;
}
}
leave:
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
{
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count += size;
}
return err;
}
/* A write handler used by es_fopencookie to write assuan data
lines. */
static gpgrt_ssize_t
data_line_cookie_write (void *cookie, const void *buffer, size_t size)
{
assuan_context_t ctx = cookie;
if (data_line_write (ctx, buffer, size))
return -1;
return (gpgrt_ssize_t)size;
}
static int
data_line_cookie_close (void *cookie)
{
assuan_context_t ctx = cookie;
if (DBG_IPC)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (ctrl && ctrl->server_local
&& ctrl->server_local->inhibit_data_logging
&& ctrl->server_local->inhibit_data_logging_count)
log_debug ("(%zu bytes sent via D lines not shown)\n",
ctrl->server_local->inhibit_data_logging_count);
}
if (assuan_send_data (ctx, NULL, 0))
{
gpg_err_set_errno (EIO);
return -1;
}
return 0;
}
/* Copy the % and + escaped string S into the buffer D and replace the
escape sequences. Note, that it is sufficient to allocate the
target string D as long as the source string S, i.e.: strlen(s)+1.
Note further that if S contains an escaped binary Nul the resulting
string D will contain the 0 as well as all other characters but it
will be impossible to know whether this is the original EOS or a
copied Nul. */
static void
strcpy_escaped_plus (char *d, const unsigned char *s)
{
while (*s)
{
if (*s == '%' && s[1] && s[2])
{
s++;
*d++ = xtoi_2 ( s);
s += 2;
}
else if (*s == '+')
*d++ = ' ', s++;
else
*d++ = *s++;
}
*d = 0;
}
/* This function returns true if a Tor server is running. The status
* is cached for the current connection. */
static int
is_tor_running (ctrl_t ctrl)
{
/* Check whether we can connect to the proxy. */
if (!ctrl || !ctrl->server_local)
return 0; /* Ooops. */
if (!ctrl->server_local->tor_state)
{
assuan_fd_t sock;
sock = assuan_sock_connect_byname (NULL, 0, 0, NULL, ASSUAN_SOCK_TOR);
if (sock == ASSUAN_INVALID_FD)
ctrl->server_local->tor_state = -1; /* Not running. */
else
{
assuan_sock_close (sock);
ctrl->server_local->tor_state = 1; /* Running. */
}
}
return (ctrl->server_local->tor_state > 0);
}
/* Return an error if the assuan context does not belong to the owner
of the process or to root. On error FAILTEXT is set as Assuan
error string. */
static gpg_error_t
check_owner_permission (assuan_context_t ctx, const char *failtext)
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows the dirmngr is always run under the control of the
user. */
(void)ctx;
(void)failtext;
#else
gpg_err_code_t ec;
assuan_peercred_t cred;
ec = gpg_err_code (assuan_get_peercred (ctx, &cred));
if (!ec && cred->uid && cred->uid != getuid ())
ec = GPG_ERR_EPERM;
if (ec)
return set_error (ec, failtext);
#endif
return 0;
}
/* Common code for get_cert_local and get_issuer_cert_local. */
static ksba_cert_t
do_get_cert_local (ctrl_t ctrl, const char *name, const char *command)
{
unsigned char *value;
size_t valuelen;
int rc;
char *buf;
ksba_cert_t cert;
buf = name? strconcat (command, " ", name, NULL) : xtrystrdup (command);
if (!buf)
rc = gpg_error_from_syserror ();
else
{
rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
&value, &valuelen, MAX_CERT_LENGTH);
xfree (buf);
}
if (rc)
{
log_error (_("assuan_inquire(%s) failed: %s\n"),
command, gpg_strerror (rc));
return NULL;
}
if (!valuelen)
{
xfree (value);
return NULL;
}
rc = ksba_cert_new (&cert);
if (!rc)
{
rc = ksba_cert_init_from_mem (cert, value, valuelen);
if (rc)
{
ksba_cert_release (cert);
cert = NULL;
}
}
xfree (value);
return cert;
}
/* Ask back to return a certificate for NAME, given as a regular gpgsm
* certificate identifier (e.g. fingerprint or one of the other
* methods). Alternatively, NULL may be used for NAME to return the
* current target certificate. Either return the certificate in a
* KSBA object or NULL if it is not available. */
ksba_cert_t
get_cert_local (ctrl_t ctrl, const char *name)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
{
if (opt.debug)
log_debug ("get_cert_local called w/o context\n");
return NULL;
}
return do_get_cert_local (ctrl, name, "SENDCERT");
}
/* Ask back to return the issuing certificate for NAME, given as a
* regular gpgsm certificate identifier (e.g. fingerprint or one
* of the other methods). Alternatively, NULL may be used for NAME to
* return the current target certificate. Either return the certificate
* in a KSBA object or NULL if it is not available. */
ksba_cert_t
get_issuing_cert_local (ctrl_t ctrl, const char *name)
{
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
{
if (opt.debug)
log_debug ("get_issuing_cert_local called w/o context\n");
return NULL;
}
return do_get_cert_local (ctrl, name, "SENDISSUERCERT");
}
/* Ask back to return a certificate with subject NAME and a
* subjectKeyIdentifier of KEYID. */
ksba_cert_t
get_cert_local_ski (ctrl_t ctrl, const char *name, ksba_sexp_t keyid)
{
unsigned char *value;
size_t valuelen;
int rc;
char *buf;
ksba_cert_t cert;
char *hexkeyid;
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx)
{
if (opt.debug)
log_debug ("get_cert_local_ski called w/o context\n");
return NULL;
}
if (!name || !keyid)
{
log_debug ("get_cert_local_ski called with insufficient arguments\n");
return NULL;
}
hexkeyid = serial_hex (keyid);
if (!hexkeyid)
{
log_debug ("serial_hex() failed\n");
return NULL;
}
buf = strconcat ("SENDCERT_SKI ", hexkeyid, " /", name, NULL);
if (!buf)
{
log_error ("can't allocate enough memory: %s\n", strerror (errno));
xfree (hexkeyid);
return NULL;
}
xfree (hexkeyid);
rc = assuan_inquire (ctrl->server_local->assuan_ctx, buf,
&value, &valuelen, MAX_CERT_LENGTH);
xfree (buf);
if (rc)
{
log_error (_("assuan_inquire(%s) failed: %s\n"), "SENDCERT_SKI",
gpg_strerror (rc));
return NULL;
}
if (!valuelen)
{
xfree (value);
return NULL;
}
rc = ksba_cert_new (&cert);
if (!rc)
{
rc = ksba_cert_init_from_mem (cert, value, valuelen);
if (rc)
{
ksba_cert_release (cert);
cert = NULL;
}
}
xfree (value);
return cert;
}
/* Ask the client via an inquiry to check the istrusted status of the
certificate specified by the hexified fingerprint HEXFPR. Returns
0 if the certificate is trusted by the client or an error code. */
gpg_error_t
get_istrusted_from_client (ctrl_t ctrl, const char *hexfpr)
{
unsigned char *value;
size_t valuelen;
int rc;
char request[100];
if (!ctrl || !ctrl->server_local || !ctrl->server_local->assuan_ctx
|| !hexfpr)
return gpg_error (GPG_ERR_INV_ARG);
snprintf (request, sizeof request, "ISTRUSTED %s", hexfpr);
rc = assuan_inquire (ctrl->server_local->assuan_ctx, request,
&value, &valuelen, 100);
if (rc)
{
log_error (_("assuan_inquire(%s) failed: %s\n"),
request, gpg_strerror (rc));
return rc;
}
/* The expected data is: "1" or "1 cruft" (not a C-string). */
if (valuelen && *value == '1' && (valuelen == 1 || spacep (value+1)))
rc = 0;
else
rc = gpg_error (GPG_ERR_NOT_TRUSTED);
xfree (value);
return rc;
}
/* Ask the client to return the certificate associated with the
current command. This is sometimes needed because the client usually
sends us just the cert ID, assuming that the request can be
satisfied from the cache, where the cert ID is used as key. */
static int
inquire_cert_and_load_crl (assuan_context_t ctx)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char *value = NULL;
size_t valuelen;
ksba_cert_t cert = NULL;
err = assuan_inquire( ctx, "SENDCERT", &value, &valuelen, 0);
if (err)
return err;
/* { */
/* FILE *fp = fopen ("foo.der", "r"); */
/* value = xmalloc (2000); */
/* valuelen = fread (value, 1, 2000, fp); */
/* fclose (fp); */
/* } */
if (!valuelen) /* No data returned; return a comprehensible error. */
return gpg_error (GPG_ERR_MISSING_CERT);
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, value, valuelen);
if(err)
goto leave;
xfree (value); value = NULL;
err = crl_cache_reload_crl (ctrl, cert);
leave:
ksba_cert_release (cert);
xfree (value);
return err;
}
/* Handle OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "force-crl-refresh"))
{
int i = *value? atoi (value) : 0;
ctrl->force_crl_refresh = i;
}
else if (!strcmp (key, "audit-events"))
{
int i = *value? atoi (value) : 0;
ctrl->audit_events = i;
}
else if (!strcmp (key, "http-proxy"))
{
xfree (ctrl->http_proxy);
if (!*value || !strcmp (value, "none"))
ctrl->http_proxy = NULL;
else if (!(ctrl->http_proxy = xtrystrdup (value)))
err = gpg_error_from_syserror ();
}
else if (!strcmp (key, "honor-keyserver-url-used"))
{
/* Return an error if we are running in Tor mode. */
if (dirmngr_use_tor ())
err = gpg_error (GPG_ERR_FORBIDDEN);
}
else if (!strcmp (key, "http-crl"))
{
int i = *value? atoi (value) : 0;
ctrl->http_no_crl = !i;
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
static const char hlp_dns_cert[] =
"DNS_CERT <subtype> <name>\n"
"DNS_CERT --pka <user_id>\n"
"DNS_CERT --dane <user_id>\n"
"\n"
"Return the CERT record for <name>. <subtype> is one of\n"
" * Return the first record of any supported subtype\n"
" PGP Return the first record of subtype PGP (3)\n"
" IPGP Return the first record of subtype IPGP (6)\n"
"If the content of a certificate is available (PGP) it is returned\n"
"by data lines. Fingerprints and URLs are returned via status lines.\n"
"In --pka mode the fingerprint and if available an URL is returned.\n"
"In --dane mode the key is returned from RR type 61";
static gpg_error_t
cmd_dns_cert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int pka_mode, dane_mode;
char *mbox = NULL;
char *namebuf = NULL;
char *encodedhash = NULL;
const char *name;
int certtype;
char *p;
void *key = NULL;
size_t keylen;
unsigned char *fpr = NULL;
size_t fprlen;
char *url = NULL;
pka_mode = has_option (line, "--pka");
dane_mode = has_option (line, "--dane");
line = skip_options (line);
if (pka_mode && dane_mode)
{
err = PARM_ERROR ("either --pka or --dane may be given");
goto leave;
}
if (pka_mode || dane_mode)
; /* No need to parse here - we do this later. */
else
{
p = strchr (line, ' ');
if (!p)
{
err = PARM_ERROR ("missing arguments");
goto leave;
}
*p++ = 0;
if (!strcmp (line, "*"))
certtype = DNS_CERTTYPE_ANY;
else if (!strcmp (line, "IPGP"))
certtype = DNS_CERTTYPE_IPGP;
else if (!strcmp (line, "PGP"))
certtype = DNS_CERTTYPE_PGP;
else
{
err = PARM_ERROR ("unknown subtype");
goto leave;
}
while (spacep (p))
p++;
line = p;
if (!*line)
{
err = PARM_ERROR ("name missing");
goto leave;
}
}
if (pka_mode || dane_mode)
{
char *domain; /* Points to mbox. */
char hashbuf[32]; /* For SHA-1 and SHA-256. */
/* We lowercase ascii characters but the DANE I-D does not allow
this. FIXME: Check after the release of the RFC whether to
change this. */
mbox = mailbox_from_userid (line, 0);
if (!mbox || !(domain = strchr (mbox, '@')))
{
err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id");
goto leave;
}
*domain++ = 0;
if (pka_mode)
{
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, mbox, strlen (mbox));
encodedhash = zb32_encode (hashbuf, 8*20);
if (!encodedhash)
{
err = gpg_error_from_syserror ();
goto leave;
}
namebuf = strconcat (encodedhash, "._pka.", domain, NULL);
if (!namebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
name = namebuf;
certtype = DNS_CERTTYPE_IPGP;
}
else
{
/* Note: The hash is truncated to 28 bytes and we lowercase
the result only for aesthetic reasons. */
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox));
encodedhash = bin2hex (hashbuf, 28, NULL);
if (!encodedhash)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (encodedhash);
namebuf = strconcat (encodedhash, "._openpgpkey.", domain, NULL);
if (!namebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
name = namebuf;
certtype = DNS_CERTTYPE_RR61;
}
}
else
name = line;
err = get_dns_cert (ctrl, name, certtype, &key, &keylen, &fpr, &fprlen, &url);
if (err)
goto leave;
if (key)
{
err = data_line_write (ctx, key, keylen);
if (err)
goto leave;
}
if (fpr)
{
char *tmpstr;
tmpstr = bin2hex (fpr, fprlen, NULL);
if (!tmpstr)
err = gpg_error_from_syserror ();
else
{
err = assuan_write_status (ctx, "FPR", tmpstr);
xfree (tmpstr);
}
if (err)
goto leave;
}
if (url)
{
err = assuan_write_status (ctx, "URL", url);
if (err)
goto leave;
}
leave:
xfree (key);
xfree (fpr);
xfree (url);
xfree (mbox);
xfree (namebuf);
xfree (encodedhash);
return leave_cmd (ctx, err);
}
/* Core of cmd_wkd_get and task_check_wkd_support. If CTX is NULL
* this function will not write anything to the assuan output. */
static gpg_error_t
proc_wkd_get (ctrl_t ctrl, assuan_context_t ctx, char *line)
{
gpg_error_t err = 0;
char *mbox = NULL;
char *domainbuf = NULL;
char *domain; /* Points to mbox or domainbuf. This is used to
* connect to the host. */
char *domain_orig;/* Points to mbox. This is the used for the
* query; i.e. the domain part of the
* addrspec. */
char sha1buf[20];
char *uri = NULL;
char *encodedhash = NULL;
int opt_submission_addr;
int opt_policy_flags;
int is_wkd_query; /* True if this is a real WKD query. */
int no_log = 0;
char portstr[20] = { 0 };
int subdomain_mode = 0;
opt_submission_addr = has_option (line, "--submission-address");
opt_policy_flags = has_option (line, "--policy-flags");
if (has_option (line, "--quick"))
ctrl->timeout = opt.connect_quick_timeout;
line = skip_options (line);
is_wkd_query = !(opt_policy_flags || opt_submission_addr);
mbox = mailbox_from_userid (line, 0);
if (!mbox || !(domain = strchr (mbox, '@')))
{
err = set_error (GPG_ERR_INV_USER_ID, "no mailbox in user id");
goto leave;
}
*domain++ = 0;
domain_orig = domain;
/* Let's check whether we already know that the domain does not
* support WKD. */
if (is_wkd_query)
{
if (domaininfo_is_wkd_not_supported (domain_orig))
{
err = gpg_error (GPG_ERR_NO_DATA);
dirmngr_status_printf (ctrl, "NOTE", "wkd_cached_result %u", err);
goto leave;
}
}
/* First try the new "openpgp" subdomain. We check that the domain
* is valid because it is later used as an unescaped filename part
* of the URI. */
if (is_valid_domain_name (domain_orig))
{
dns_addrinfo_t aibuf;
domainbuf = strconcat ( "openpgpkey.", domain_orig, NULL);
if (!domainbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: We should put a cache into dns-stuff because the same
* query (with a different port and socket type, though) will be
* done later by http function. */
err = resolve_dns_name (ctrl, domainbuf, 0, 0, 0, &aibuf, NULL);
if (err)
{
err = 0;
xfree (domainbuf);
domainbuf = NULL;
}
else /* Got a subdomain. */
{
free_dns_addrinfo (aibuf);
subdomain_mode = 1;
domain = domainbuf;
}
}
/* Check for SRV records unless we have a subdomain. */
if (!subdomain_mode)
{
struct srventry *srvs;
unsigned int srvscount;
size_t domainlen, targetlen;
int i;
err = get_dns_srv (ctrl, domain, "openpgpkey", NULL, &srvs, &srvscount);
if (err)
{
- /* Ignore server failed becuase there are too many resolvers
+ /* Ignore server failed because there are too many resolvers
* which do not work as expected. */
if (gpg_err_code (err) == GPG_ERR_SERVER_FAILED)
err = 0; /*(srvcount is guaranteed to be 0)*/
else
goto leave;
}
/* Check for rogue DNS names. */
for (i = 0; i < srvscount; i++)
{
if (!is_valid_domain_name (srvs[i].target))
{
err = gpg_error (GPG_ERR_DNS_ADDRESS);
log_error ("rogue openpgpkey SRV record for '%s'\n", domain);
xfree (srvs);
goto leave;
}
}
/* Find the first target which also ends in DOMAIN or is equal
* to DOMAIN. */
domainlen = strlen (domain);
for (i = 0; i < srvscount; i++)
{
if (DBG_DNS)
log_debug ("srv: trying '%s:%hu'\n", srvs[i].target, srvs[i].port);
targetlen = strlen (srvs[i].target);
if ((targetlen > domainlen + 1
&& srvs[i].target[targetlen - domainlen - 1] == '.'
&& !ascii_strcasecmp (srvs[i].target + targetlen - domainlen,
domain))
|| (targetlen == domainlen
&& !ascii_strcasecmp (srvs[i].target, domain)))
{
/* found. */
domainbuf = xtrystrdup (srvs[i].target);
if (!domainbuf)
{
err = gpg_error_from_syserror ();
xfree (srvs);
goto leave;
}
domain = domainbuf;
if (srvs[i].port)
snprintf (portstr, sizeof portstr, ":%hu", srvs[i].port);
break;
}
}
xfree (srvs);
}
/* Prepare the hash of the local part. */
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1buf, mbox, strlen (mbox));
encodedhash = zb32_encode (sha1buf, 8*20);
if (!encodedhash)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt_submission_addr)
{
uri = strconcat ("https://",
domain,
portstr,
"/.well-known/openpgpkey/",
subdomain_mode? domain_orig : "",
subdomain_mode? "/" : "",
"submission-address",
NULL);
}
else if (opt_policy_flags)
{
uri = strconcat ("https://",
domain,
portstr,
"/.well-known/openpgpkey/",
subdomain_mode? domain_orig : "",
subdomain_mode? "/" : "",
"policy",
NULL);
}
else
{
char *escapedmbox;
escapedmbox = http_escape_string (mbox, "%;?&=+#");
if (escapedmbox)
{
uri = strconcat ("https://",
domain,
portstr,
"/.well-known/openpgpkey/",
subdomain_mode? domain_orig : "",
subdomain_mode? "/" : "",
"hu/",
encodedhash,
"?l=",
escapedmbox,
NULL);
xfree (escapedmbox);
no_log = 1;
if (uri)
{
err = dirmngr_status_printf (ctrl, "SOURCE", "https://%s%s",
domain, portstr);
if (err)
goto leave;
}
}
}
if (!uri)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Setup an output stream and perform the get. */
{
estream_t outfp;
outfp = ctx? es_fopencookie (ctx, "w", data_line_cookie_functions) : NULL;
if (!outfp && ctx)
err = set_error (GPG_ERR_ASS_GENERAL,
"error setting up a data stream");
else
{
if (ctrl->server_local)
{
if (no_log)
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
}
err = ks_action_fetch (ctrl, uri, outfp);
es_fclose (outfp);
if (ctrl->server_local)
ctrl->server_local->inhibit_data_logging = 0;
/* Register the result under the domain name of MBOX. */
switch (gpg_err_code (err))
{
case 0:
domaininfo_set_wkd_supported (domain_orig);
break;
case GPG_ERR_NO_NAME:
/* There is no such domain. */
domaininfo_set_no_name (domain_orig);
break;
case GPG_ERR_NO_DATA:
if (is_wkd_query && ctrl->server_local)
{
/* Mark that and schedule a check. */
domaininfo_set_wkd_not_found (domain_orig);
workqueue_add_task (task_check_wkd_support, domain_orig,
ctrl->server_local->session_id, 1);
}
else if (opt_policy_flags) /* No policy file - no support. */
domaininfo_set_wkd_not_supported (domain_orig);
break;
default:
/* Don't register other errors. */
break;
}
}
}
leave:
xfree (uri);
xfree (encodedhash);
xfree (mbox);
xfree (domainbuf);
return err;
}
static const char hlp_wkd_get[] =
"WKD_GET [--submission-address|--policy-flags] <user_id>\n"
"\n"
"Return the key or other info for <user_id>\n"
"from the Web Key Directory.";
static gpg_error_t
cmd_wkd_get (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
err = proc_wkd_get (ctrl, ctx, line);
return leave_cmd (ctx, err);
}
/* A task to check whether DOMAIN supports WKD. This is done by
* checking whether the policy flags file can be read. */
static const char *
task_check_wkd_support (ctrl_t ctrl, const char *domain)
{
char *string;
if (!ctrl || !domain)
return "check_wkd_support";
string = strconcat ("--policy-flags foo@", domain, NULL);
if (!string)
log_error ("%s: %s\n", __func__, gpg_strerror (gpg_error_from_syserror ()));
else
{
proc_wkd_get (ctrl, NULL, string);
xfree (string);
}
return NULL;
}
static const char hlp_ldapserver[] =
"LDAPSERVER [--clear] <data>\n"
"\n"
"Add a new LDAP server to the list of configured LDAP servers.\n"
"DATA is in the same format as expected in the configure file.\n"
"An optional prefix \"ldap:\" is allowed. With no args all\n"
"configured ldapservers are listed. Option --clear removes all\n"
"servers configured in this session.";
static gpg_error_t
cmd_ldapserver (assuan_context_t ctx, char *line)
{
#if USE_LDAP
ctrl_t ctrl = assuan_get_pointer (ctx);
ldap_server_t server;
ldap_server_t *last_next_p;
int clear_flag;
clear_flag = has_option (line, "--clear");
line = skip_options (line);
while (spacep (line))
line++;
if (clear_flag)
{
#if USE_LDAP
ldapserver_list_free (ctrl->server_local->ldapservers);
#endif /*USE_LDAP*/
ctrl->server_local->ldapservers = NULL;
}
if (!*line && clear_flag)
return leave_cmd (ctx, 0);
if (!*line)
{
/* List all ldapservers. */
struct ldapserver_iter ldapserver_iter;
char *tmpstr;
char portstr[20];
for (ldapserver_iter_begin (&ldapserver_iter, ctrl);
!ldapserver_iter_end_p (&ldapserver_iter);
ldapserver_iter_next (&ldapserver_iter))
{
server = ldapserver_iter.server;
if (server->port)
snprintf (portstr, sizeof portstr, "%d", server->port);
else
*portstr = 0;
tmpstr = xtryasprintf ("ldap:%s:%s:%s:%s:%s:%s%s:",
server->host? server->host : "",
portstr,
server->user? server->user : "",
server->pass? "*****": "",
server->base? server->base : "",
server->starttls ? "starttls" :
server->ldap_over_tls ? "ldaptls" : "none",
server->ntds ? ",ntds" : "");
if (!tmpstr)
return leave_cmd (ctx, gpg_error_from_syserror ());
dirmngr_status (ctrl, "LDAPSERVER", tmpstr, NULL);
xfree (tmpstr);
}
return leave_cmd (ctx, 0);
}
/* Skip an "ldap:" prefix unless it is a valid ldap url. */
if (!strncmp (line, "ldap:", 5) && !(line[5] == '/' && line[6] == '/'))
line += 5;
server = ldapserver_parse_one (line, NULL, 0);
if (! server)
return leave_cmd (ctx, gpg_error (GPG_ERR_INV_ARG));
last_next_p = &ctrl->server_local->ldapservers;
while (*last_next_p)
last_next_p = &(*last_next_p)->next;
*last_next_p = server;
return leave_cmd (ctx, 0);
#else
(void)line;
return leave_cmd (ctx, gpg_error (GPG_ERR_NOT_IMPLEMENTED));
#endif
}
static const char hlp_isvalid[] =
"ISVALID [--only-ocsp] [--force-default-responder]"
" <certificate_id> [<certificate_fpr>]\n"
"\n"
"This command checks whether the certificate identified by the\n"
"certificate_id is valid. This is done by consulting CRLs or\n"
"whatever has been configured. Note, that the returned error codes\n"
"are from gpg-error.h. The command may callback using the inquire\n"
"function. See the manual for details.\n"
"\n"
"The CERTIFICATE_ID is a hex encoded string consisting of two parts,\n"
"delimited by a single dot. The first part is the SHA-1 hash of the\n"
"issuer name and the second part the serial number.\n"
"\n"
"If an OCSP check is desired CERTIFICATE_FPR with the hex encoded\n"
"fingerprint of the certificate is required. In this case an OCSP\n"
"request is done before consulting the CRL.\n"
"\n"
"If the option --only-ocsp is given, no fallback to a CRL check will\n"
"be used.\n"
"\n"
"If the option --force-default-responder is given, only the default\n"
"OCSP responder will be used and any other methods of obtaining an\n"
"OCSP responder URL won't be used.";
static gpg_error_t
cmd_isvalid (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char *issuerhash, *serialno, *fpr;
gpg_error_t err;
int did_inquire = 0;
int ocsp_mode = 0;
int only_ocsp;
int force_default_responder;
only_ocsp = has_option (line, "--only-ocsp");
force_default_responder = has_option (line, "--force-default-responder");
line = skip_options (line);
/* We need to work on a copy of the line because that same Assuan
* context may be used for an inquiry. That is because Assuan
* reuses its line buffer. */
issuerhash = xstrdup (line);
serialno = strchr (issuerhash, '.');
if (!serialno)
{
xfree (issuerhash);
return leave_cmd (ctx, PARM_ERROR (_("serialno missing in cert ID")));
}
*serialno++ = 0;
if (strlen (issuerhash) != 40)
{
xfree (issuerhash);
return leave_cmd (ctx, PARM_ERROR ("cert ID is too short"));
}
fpr = strchr (serialno, ' ');
while (fpr && spacep (fpr))
fpr++;
if (fpr && *fpr)
{
char *endp = strchr (fpr, ' ');
if (endp)
*endp = 0;
if (strlen (fpr) != 40)
{
xfree (issuerhash);
return leave_cmd (ctx, PARM_ERROR ("fingerprint too short"));
}
ocsp_mode = 1;
}
again:
if (ocsp_mode)
{
gnupg_isotime_t revoked_at;
const char *reason;
/* Note, that we currently ignore the supplied fingerprint FPR;
* instead ocsp_isvalid does an inquire to ask for the cert.
* The fingerprint may eventually be used to lookup the
* certificate in a local cache. */
if (!opt.allow_ocsp)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
err = ocsp_isvalid (ctrl, NULL, NULL, force_default_responder,
revoked_at, &reason);
if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED)
dirmngr_status_printf (ctrl, "REVOCATIONINFO", "%s %s",
revoked_at, reason);
if (gpg_err_code (err) == GPG_ERR_CONFIGURATION
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR)
{
/* No default responder configured - fallback to CRL. */
if (!only_ocsp)
log_info ("falling back to CRL check\n");
ocsp_mode = 0;
goto again;
}
}
else if (only_ocsp)
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else if (opt.fake_crl && (err = fakecrl_isvalid (ctrl, issuerhash, serialno)))
{
/* We already got the error code. */
}
else
{
switch (crl_cache_isvalid (ctrl,
issuerhash, serialno,
ctrl->force_crl_refresh))
{
case CRL_CACHE_VALID:
err = 0;
break;
case CRL_CACHE_INVALID:
err = gpg_error (GPG_ERR_CERT_REVOKED);
break;
case CRL_CACHE_DONTKNOW:
if (did_inquire)
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else if (!(err = inquire_cert_and_load_crl (ctx)))
{
did_inquire = 1;
goto again;
}
break;
case CRL_CACHE_NOTTRUSTED:
err = gpg_error (GPG_ERR_NOT_TRUSTED);
break;
case CRL_CACHE_CANTUSE:
err = gpg_error (GPG_ERR_INV_CRL_OBJ);
break;
default:
log_fatal ("crl_cache_isvalid returned invalid code\n");
}
}
xfree (issuerhash);
return leave_cmd (ctx, err);
}
/* If the line contains a SHA-1 fingerprint as the first argument,
return the FPR buffer on success. The function checks that the
fingerprint consists of valid characters and prints and error
message if it does not and returns NULL. Fingerprints are
considered optional and thus no explicit error is returned. NULL is
also returned if there is no fingerprint at all available.
FPR must be a caller provided buffer of at least 20 bytes.
Note that colons within the fingerprint are allowed to separate 2
hex digits; this allows for easier cutting and pasting using the
usual fingerprint rendering.
*/
static unsigned char *
get_fingerprint_from_line (const char *line, unsigned char *fpr)
{
const char *s;
int i;
for (s=line, i=0; *s && *s != ' '; s++ )
{
if ( hexdigitp (s) && hexdigitp (s+1) )
{
if ( i >= 20 )
return NULL; /* Fingerprint too long. */
fpr[i++] = xtoi_2 (s);
s++;
}
else if ( *s != ':' )
return NULL; /* Invalid. */
}
if ( i != 20 )
return NULL; /* Fingerprint to short. */
return fpr;
}
static const char hlp_checkcrl[] =
"CHECKCRL [<fingerprint>]\n"
"\n"
"Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
"entire X.509 certificate blob) is valid or not by consulting the\n"
"CRL responsible for this certificate. If the fingerprint has not\n"
"been given or the certificate is not known, the function \n"
"inquires the certificate using an\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request (which should match FINGERPRINT) as a binary blob.\n"
"Processing then takes place without further interaction; in\n"
"particular dirmngr tries to locate other required certificate by\n"
"its own mechanism which includes a local certificate store as well\n"
"as a list of trusted root certificates.\n"
"\n"
"The return value is the usual gpg-error code or 0 for ducesss;\n"
"i.e. the certificate validity has been confirmed by a valid CRL.";
static gpg_error_t
cmd_checkcrl (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char fprbuffer[20], *fpr;
ksba_cert_t cert;
fpr = get_fingerprint_from_line (line, fprbuffer);
cert = fpr? get_cert_byfpr (fpr) : NULL;
if (!cert)
{
/* We do not have this certificate yet or the fingerprint has
not been given. Inquire it from the client. */
unsigned char *value = NULL;
size_t valuelen;
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
}
log_assert (cert);
err = crl_cache_cert_isvalid (ctrl, cert, ctrl->force_crl_refresh);
if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
{
err = crl_cache_reload_crl (ctrl, cert);
if (!err)
err = crl_cache_cert_isvalid (ctrl, cert, 0);
}
leave:
ksba_cert_release (cert);
return leave_cmd (ctx, err);
}
static const char hlp_checkocsp[] =
"CHECKOCSP [--force-default-responder] [<fingerprint>]\n"
"\n"
"Check whether the certificate with FINGERPRINT (SHA-1 hash of the\n"
"entire X.509 certificate blob) is valid or not by asking an OCSP\n"
"responder responsible for this certificate. The optional\n"
"fingerprint may be used for a quick check in case an OCSP check has\n"
"been done for this certificate recently (we always cache OCSP\n"
"responses for a couple of minutes). If the fingerprint has not been\n"
"given or there is no cached result, the function inquires the\n"
"certificate using an\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request (which should match FINGERPRINT) as a binary blob.\n"
"Processing then takes place without further interaction; in\n"
"particular dirmngr tries to locate other required certificates by\n"
"its own mechanism which includes a local certificate store as well\n"
"as a list of trusted root certificates.\n"
"\n"
"If the option --force-default-responder is given, only the default\n"
"OCSP responder will be used and any other methods of obtaining an\n"
"OCSP responder URL won't be used.\n"
"\n"
"The return value is the usual gpg-error code or 0 for ducesss;\n"
"i.e. the certificate validity has been confirmed by a valid CRL.";
static gpg_error_t
cmd_checkocsp (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char fprbuffer[20], *fpr;
ksba_cert_t cert;
int force_default_responder;
gnupg_isotime_t revoked_at;
const char *reason;
force_default_responder = has_option (line, "--force-default-responder");
line = skip_options (line);
fpr = get_fingerprint_from_line (line, fprbuffer);
cert = fpr? get_cert_byfpr (fpr) : NULL;
if (!cert)
{
/* We do not have this certificate yet or the fingerprint has
not been given. Inquire it from the client. */
unsigned char *value = NULL;
size_t valuelen;
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
}
log_assert (cert);
if (!opt.allow_ocsp)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
err = ocsp_isvalid (ctrl, cert, NULL, force_default_responder,
revoked_at, &reason);
if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED)
dirmngr_status_printf (ctrl, "REVOCATIONINFO", "%s %s",
revoked_at, reason);
leave:
ksba_cert_release (cert);
return leave_cmd (ctx, err);
}
static int
lookup_cert_by_url (assuan_context_t ctx, const char *url)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char *value = NULL;
size_t valuelen;
/* Fetch single certificate given it's URL. */
err = fetch_cert_by_url (ctrl, url, &value, &valuelen);
if (err)
{
log_error (_("fetch_cert_by_url failed: %s\n"), gpg_strerror (err));
goto leave;
}
/* Send the data, flush the buffer and then send an END. */
err = assuan_send_data (ctx, value, valuelen);
if (!err)
err = assuan_send_data (ctx, NULL, 0);
if (!err)
err = assuan_write_line (ctx, "END");
if (err)
{
log_error (_("error sending data: %s\n"), gpg_strerror (err));
goto leave;
}
leave:
return err;
}
/* Send the certificate, flush the buffer and then send an END. */
static gpg_error_t
return_one_cert (void *opaque, ksba_cert_t cert)
{
assuan_context_t ctx = opaque;
gpg_error_t err;
const unsigned char *der;
size_t derlen;
der = ksba_cert_get_image (cert, &derlen);
if (!der)
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
else
{
err = assuan_send_data (ctx, der, derlen);
if (!err)
err = assuan_send_data (ctx, NULL, 0);
if (!err)
err = assuan_write_line (ctx, "END");
}
if (err)
log_error (_("error sending data: %s\n"), gpg_strerror (err));
return err;
}
/* Lookup certificates from the internal cache or using the ldap
servers. */
static int
lookup_cert_by_pattern (assuan_context_t ctx, char *line,
int single, int cache_only)
{
gpg_error_t err = 0;
char *p;
strlist_t sl, list = NULL;
int truncated = 0, truncation_forced = 0;
int count = 0;
int local_count = 0;
#if USE_LDAP
ctrl_t ctrl = assuan_get_pointer (ctx);
unsigned char *value = NULL;
size_t valuelen;
struct ldapserver_iter ldapserver_iter;
cert_fetch_context_t fetch_context;
#endif /*USE_LDAP*/
int any_no_data = 0;
/* Break the line down into an STRLIST */
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
err = gpg_error_from_errno (errno);
goto leave;
}
memset (sl, 0, sizeof *sl);
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
/* First look through the internal cache. The certificates returned
here are not counted towards the truncation limit. */
if (single && !cache_only)
; /* Do not read from the local cache in this case. */
else
{
for (sl=list; sl; sl = sl->next)
{
err = get_certs_bypattern (sl->d, return_one_cert, ctx);
if (!err)
local_count++;
if (!err && single)
goto ready;
if (gpg_err_code (err) == GPG_ERR_NO_DATA
|| gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
err = 0;
if (cache_only)
any_no_data = 1;
}
else if (gpg_err_code (err) == GPG_ERR_INV_NAME && !cache_only)
{
/* No real fault because the internal pattern lookup
can't yet cope with all types of pattern. */
err = 0;
}
if (err)
goto ready;
}
}
/* Loop over all configured servers unless we want only the
certificates from the cache. */
#if USE_LDAP
for (ldapserver_iter_begin (&ldapserver_iter, ctrl);
!cache_only && !ldapserver_iter_end_p (&ldapserver_iter)
&& ldapserver_iter.server->host && !truncation_forced;
ldapserver_iter_next (&ldapserver_iter))
{
ldap_server_t ldapserver = ldapserver_iter.server;
if (DBG_LOOKUP)
log_debug ("cmd_lookup: trying %s:%d base=%s\n",
ldapserver->host, ldapserver->port,
ldapserver->base?ldapserver->base : "[default]");
/* Fetch certificates matching pattern */
err = start_cert_fetch (ctrl, &fetch_context, list, ldapserver);
if ( gpg_err_code (err) == GPG_ERR_NO_DATA )
{
if (DBG_LOOKUP)
log_debug ("cmd_lookup: no data\n");
err = 0;
any_no_data = 1;
continue;
}
if (err)
{
log_error (_("start_cert_fetch failed: %s\n"), gpg_strerror (err));
goto leave;
}
/* Fetch the certificates for this query. */
while (!truncation_forced)
{
xfree (value); value = NULL;
err = fetch_next_cert (fetch_context, &value, &valuelen);
if (gpg_err_code (err) == GPG_ERR_NO_DATA )
{
err = 0;
any_no_data = 1;
break; /* Ready. */
}
if (gpg_err_code (err) == GPG_ERR_TRUNCATED)
{
truncated = 1;
err = 0;
break; /* Ready. */
}
if (gpg_err_code (err) == GPG_ERR_EOF)
{
err = 0;
break; /* Ready. */
}
if (!err && !value)
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
if (err)
{
log_error (_("fetch_next_cert failed: %s\n"),
gpg_strerror (err));
end_cert_fetch (fetch_context);
goto leave;
}
if (DBG_LOOKUP)
log_debug ("cmd_lookup: returning one cert%s\n",
truncated? " (truncated)":"");
/* Send the data, flush the buffer and then send an END line
as a certificate delimiter. */
err = assuan_send_data (ctx, value, valuelen);
if (!err)
err = assuan_send_data (ctx, NULL, 0);
if (!err)
err = assuan_write_line (ctx, "END");
if (err)
{
log_error (_("error sending data: %s\n"), gpg_strerror (err));
end_cert_fetch (fetch_context);
goto leave;
}
if (++count >= opt.max_replies )
{
truncation_forced = 1;
log_info (_("max_replies %d exceeded\n"), opt.max_replies );
}
if (single)
break;
}
end_cert_fetch (fetch_context);
}
#endif /*USE_LDAP*/
ready:
if (truncated || truncation_forced)
{
char str[50];
sprintf (str, "%d", count);
assuan_write_status (ctx, "TRUNCATED", str);
}
if (!err && !count && !local_count && any_no_data)
err = gpg_error (GPG_ERR_NO_DATA);
leave:
free_strlist (list);
return err;
}
static const char hlp_lookup[] =
"LOOKUP [--url] [--single] [--cache-only] <pattern>\n"
"\n"
"Lookup certificates matching PATTERN. With --url the pattern is\n"
"expected to be one URL.\n"
"\n"
"If --url is not given: To allow for multiple patterns (which are ORed)\n"
"quoting is required: Spaces are translated to \"+\" or \"%20\";\n"
"obviously this requires that the usual escape quoting rules are applied.\n"
"\n"
"If --url is given no special escaping is required because URLs are\n"
"already escaped this way.\n"
"\n"
"If --single is given the first and only the first match will be\n"
"returned. If --cache-only is _not_ given, no local query will be\n"
"done.\n"
"\n"
"If --cache-only is given no external lookup is done so that only\n"
"certificates from the cache may get returned.";
static gpg_error_t
cmd_lookup (assuan_context_t ctx, char *line)
{
gpg_error_t err;
int lookup_url, single, cache_only;
lookup_url = has_leading_option (line, "--url");
single = has_leading_option (line, "--single");
cache_only = has_leading_option (line, "--cache-only");
line = skip_options (line);
if (lookup_url && cache_only)
err = gpg_error (GPG_ERR_NOT_FOUND);
else if (lookup_url && single)
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
else if (lookup_url)
err = lookup_cert_by_url (ctx, line);
else
err = lookup_cert_by_pattern (ctx, line, single, cache_only);
return leave_cmd (ctx, err);
}
static const char hlp_loadcrl[] =
"LOADCRL [--url] <filename|url>\n"
"\n"
"Load the CRL in the file with name FILENAME into our cache. Note\n"
"that FILENAME should be given with an absolute path because\n"
"Dirmngrs cwd is not known. With --url the CRL is directly loaded\n"
"from the given URL.\n"
"\n"
"This command is usually used by gpgsm using the invocation \"gpgsm\n"
"--call-dirmngr loadcrl <filename>\". A direct invocation of Dirmngr\n"
"is not useful because gpgsm might need to callback gpgsm to ask for\n"
"the CA's certificate.";
static gpg_error_t
cmd_loadcrl (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int use_url = has_leading_option (line, "--url");
line = skip_options (line);
if (use_url)
{
ksba_reader_t reader;
err = crl_fetch (ctrl, line, &reader);
if (err)
log_error (_("fetching CRL from '%s' failed: %s\n"),
line, gpg_strerror (err));
else
{
err = crl_cache_insert (ctrl, line, reader);
if (err)
log_error (_("processing CRL from '%s' failed: %s\n"),
line, gpg_strerror (err));
crl_close_reader (reader);
}
}
else
{
char *buf;
buf = xtrymalloc (strlen (line)+1);
if (!buf)
err = gpg_error_from_syserror ();
else
{
strcpy_escaped_plus (buf, line);
err = crl_cache_load (ctrl, buf);
xfree (buf);
}
}
return leave_cmd (ctx, err);
}
static const char hlp_listcrls[] =
"LISTCRLS\n"
"\n"
"List the content of all CRLs in a readable format. This command is\n"
"usually used by gpgsm using the invocation \"gpgsm --call-dirmngr\n"
"listcrls\". It may also be used directly using \"dirmngr\n"
"--list-crls\".";
static gpg_error_t
cmd_listcrls (assuan_context_t ctx, char *line)
{
gpg_error_t err;
estream_t fp;
(void)line;
fp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!fp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
err = crl_cache_list (fp);
es_fclose (fp);
}
return leave_cmd (ctx, err);
}
static const char hlp_cachecert[] =
"CACHECERT\n"
"\n"
"Put a certificate into the internal cache. This command might be\n"
"useful if a client knows in advance certificates required for a\n"
"test and wants to make sure they get added to the internal cache.\n"
"It is also helpful for debugging. To get the actual certificate,\n"
"this command immediately inquires it using\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request as a binary blob.";
static gpg_error_t
cmd_cachecert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
ksba_cert_t cert = NULL;
unsigned char *value = NULL;
size_t valuelen;
(void)line;
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
err = cache_cert (cert);
leave:
ksba_cert_release (cert);
return leave_cmd (ctx, err);
}
static const char hlp_validate[] =
"VALIDATE [--systrust] [--tls] [--no-crl]\n"
"\n"
"Validate a certificate using the certificate validation function\n"
"used internally by dirmngr. This command is only useful for\n"
"debugging. To get the actual certificate, this command immediately\n"
"inquires it using\n"
"\n"
" INQUIRE TARGETCERT\n"
"\n"
"and the caller is expected to return the certificate for the\n"
"request as a binary blob. The option --tls modifies this by asking\n"
"for list of certificates with\n"
"\n"
" INQUIRE CERTLIST\n"
"\n"
"Here the first certificate is the target certificate, the remaining\n"
"certificates are suggested intermediary certificates. All certificates\n"
"need to be PEM encoded.\n"
"\n"
"The option --systrust changes the behaviour to include the system\n"
"provided root certificates as trust anchors. The option --no-crl\n"
"skips CRL checks";
static gpg_error_t
cmd_validate (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
ksba_cert_t cert = NULL;
certlist_t certlist = NULL;
unsigned char *value = NULL;
size_t valuelen;
int systrust_mode, tls_mode, no_crl;
systrust_mode = has_option (line, "--systrust");
tls_mode = has_option (line, "--tls");
no_crl = has_option (line, "--no-crl");
line = skip_options (line);
if (tls_mode)
err = assuan_inquire (ctrl->server_local->assuan_ctx, "CERTLIST",
&value, &valuelen, MAX_CERTLIST_LENGTH);
else
err = assuan_inquire (ctrl->server_local->assuan_ctx, "TARGETCERT",
&value, &valuelen, MAX_CERT_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
err = gpg_error (GPG_ERR_MISSING_CERT);
else if (tls_mode)
{
estream_t fp;
fp = es_fopenmem_init (0, "rb", value, valuelen);
if (!fp)
err = gpg_error_from_syserror ();
else
{
err = read_certlist_from_stream (&certlist, fp);
es_fclose (fp);
if (!err && !certlist)
err = gpg_error (GPG_ERR_MISSING_CERT);
if (!err)
{
/* Extract the first certificate from the list. */
cert = certlist->cert;
ksba_cert_ref (cert);
}
}
}
else
{
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, value, valuelen);
}
xfree (value);
if(err)
goto leave;
if (!tls_mode)
{
/* If we have this certificate already in our cache, use the
* cached version for validation because this will take care of
* any cached results. We don't need to do this in tls mode
* because this has already been done for certificate in a
* certlist_t. */
unsigned char fpr[20];
ksba_cert_t tmpcert;
cert_compute_fpr (cert, fpr);
tmpcert = get_cert_byfpr (fpr);
if (tmpcert)
{
ksba_cert_release (cert);
cert = tmpcert;
}
}
/* Quick hack to make verification work by inserting the supplied
* certs into the cache. */
if (tls_mode && certlist)
{
certlist_t cl;
for (cl = certlist->next; cl; cl = cl->next)
cache_cert (cl->cert);
}
err = validate_cert_chain (ctrl, cert, NULL,
(VALIDATE_FLAG_TRUST_CONFIG
| (tls_mode ? VALIDATE_FLAG_TLS : 0)
| (systrust_mode ? VALIDATE_FLAG_TRUST_SYSTEM : 0)
| (no_crl ? VALIDATE_FLAG_NOCRLCHECK : 0)),
NULL);
leave:
ksba_cert_release (cert);
release_certlist (certlist);
return leave_cmd (ctx, err);
}
/* Parse an keyserver URI and store it in a new uri item which is
returned at R_ITEM. On error return an error code. */
static gpg_error_t
make_keyserver_item (const char *uri, uri_item_t *r_item)
{
/* We used to have DNS CNAME redirection from the URLs below to
* sks-keyserver. pools. The idea was to allow for a quick way to
* switch to a different set of pools. The problem with that
* approach is that TLS needs to verify the hostname and - because
* DNS is not secured - it can only check the user supplied hostname
* and not a hostname from a CNAME RR. Thus the final server all
* need to have certificates with the actual pool name as well as
* for keys.gnupg.net - that would render the advantage of
* keys.gnupg.net useless and so we better give up on this. Because
* the keys.gnupg.net URL are still in widespread use we do a static
* mapping here.
*/
if (!strcmp (uri, "hkps://keys.gnupg.net")
|| !strcmp (uri, "keys.gnupg.net"))
uri = "hkps://keyserver.ubuntu.com";
else if (!strcmp (uri, "https://keys.gnupg.net"))
uri = "hkps://keyserver.ubuntu.com";
else if (!strcmp (uri, "hkp://keys.gnupg.net"))
uri = "hkp://keyserver.ubuntu.com";
else if (!strcmp (uri, "http://keys.gnupg.net"))
uri = "hkp://keyserver.ubuntu.com:80";
else if (!strcmp (uri, "hkps://http-keys.gnupg.net")
|| !strcmp (uri, "http-keys.gnupg.net"))
uri = "hkps://keyserver.ubuntu.com";
else if (!strcmp (uri, "https://http-keys.gnupg.net"))
uri = "hkps://keyserver.ubuntu.com";
else if (!strcmp (uri, "hkp://http-keys.gnupg.net"))
uri = "hkp://keyserver.ubuntu.com";
else if (!strcmp (uri, "http://http-keys.gnupg.net"))
uri = "hkp://keyserver.ubuntu.com:80";
return ks_action_parse_uri (uri, r_item);
}
/* If no keyserver is stored in CTRL but a global keyserver has been
set, put that global keyserver into CTRL. We need use this
function to help migrate from the old gpg based keyserver
configuration to the new dirmngr based configuration. */
static gpg_error_t
ensure_keyserver (ctrl_t ctrl)
{
gpg_error_t err;
uri_item_t item;
uri_item_t onion_items = NULL;
uri_item_t plain_items = NULL;
uri_item_t ui;
strlist_t sl;
int none_seen = 1;
if (ctrl->server_local->keyservers)
return 0; /* Already set for this session. */
if (!opt.keyserver)
{
/* No global option set. Fall back to default: */
return make_keyserver_item (DIRMNGR_DEFAULT_KEYSERVER,
&ctrl->server_local->keyservers);
}
for (sl = opt.keyserver; sl; sl = sl->next)
{
/* Frontends like Kleopatra may prefix option values without a
* scheme with "hkps://". Thus we need to check that too.
* Nobody will be mad enough to call a machine "none". */
if (!strcmp (sl->d, "none") || !strcmp (sl->d, "hkp://none")
|| !strcmp (sl->d, "hkps://none"))
{
none_seen = 1;
continue;
}
err = make_keyserver_item (sl->d, &item);
if (err)
goto leave;
if (item->parsed_uri->onion)
{
item->next = onion_items;
onion_items = item;
}
else
{
item->next = plain_items;
plain_items = item;
}
}
if (none_seen && !plain_items && !onion_items)
{
err = gpg_error (GPG_ERR_NO_KEYSERVER);
goto leave;
}
/* Decide which to use. Note that the session has no keyservers
yet set. */
if (onion_items && !onion_items->next && plain_items && !plain_items->next)
{
/* If there is just one onion and one plain keyserver given, we take
only one depending on whether Tor is running or not. */
if (!dirmngr_never_use_tor_p () && is_tor_running (ctrl))
{
ctrl->server_local->keyservers = onion_items;
onion_items = NULL;
}
else
{
ctrl->server_local->keyservers = plain_items;
plain_items = NULL;
}
}
else if (dirmngr_never_use_tor_p () || !is_tor_running (ctrl))
{
/* Tor is not running. It does not make sense to add Onion
addresses. */
ctrl->server_local->keyservers = plain_items;
plain_items = NULL;
}
else
{
/* In all other cases add all keyservers. */
ctrl->server_local->keyservers = onion_items;
onion_items = NULL;
for (ui = ctrl->server_local->keyservers; ui && ui->next; ui = ui->next)
;
if (ui)
ui->next = plain_items;
else
ctrl->server_local->keyservers = plain_items;
plain_items = NULL;
}
leave:
release_uri_item_list (onion_items);
release_uri_item_list (plain_items);
return err;
}
static const char hlp_keyserver[] =
"KEYSERVER [<options>] [<uri>|<host>]\n"
"Options are:\n"
" --help\n"
" --clear Remove all configured keyservers\n"
" --resolve Resolve HKP host names and rotate\n"
" --hosttable Print table of known hosts and pools\n"
" --dead Mark <host> as dead\n"
" --alive Mark <host> as alive\n"
"\n"
"If called without arguments list all configured keyserver URLs.\n"
"If called with an URI add this as keyserver. Note that keyservers\n"
"are configured on a per-session base. A default keyserver may already be\n"
"present, thus the \"--clear\" option must be used to get full control.\n"
"If \"--clear\" and an URI are used together the clear command is\n"
"obviously executed first. A RESET command does not change the list\n"
"of configured keyservers.";
static gpg_error_t
cmd_keyserver (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int clear_flag, add_flag, help_flag, host_flag, resolve_flag;
int dead_flag, alive_flag;
uri_item_t item = NULL;
clear_flag = has_option (line, "--clear");
help_flag = has_option (line, "--help");
resolve_flag = has_option (line, "--resolve");
host_flag = has_option (line, "--hosttable");
dead_flag = has_option (line, "--dead");
alive_flag = has_option (line, "--alive");
line = skip_options (line);
add_flag = !!*line;
if (help_flag)
{
err = ks_action_help (ctrl, line);
goto leave;
}
if (resolve_flag)
{
err = ensure_keyserver (ctrl);
if (err)
{
assuan_set_error (ctx, err,
"Bad keyserver configuration in dirmngr.conf");
goto leave;
}
err = ks_action_resolve (ctrl, ctrl->server_local->keyservers);
if (err)
goto leave;
}
if (alive_flag && dead_flag)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no support for zombies");
goto leave;
}
if (dead_flag)
{
err = check_owner_permission (ctx, "no permission to use --dead");
if (err)
goto leave;
}
if (alive_flag || dead_flag)
{
if (!*line)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "name of host missing");
goto leave;
}
err = ks_hkp_mark_host (ctrl, line, alive_flag);
if (err)
goto leave;
}
if (host_flag)
{
err = ks_hkp_print_hosttable (ctrl);
if (err)
goto leave;
}
if (resolve_flag || host_flag || alive_flag || dead_flag)
goto leave;
if (add_flag)
{
if (!strcmp (line, "none") || !strcmp (line, "hkp://none")
|| !strcmp (line, "hkps://none"))
err = 0;
else
err = make_keyserver_item (line, &item);
if (err)
goto leave;
}
if (clear_flag)
release_ctrl_keyservers (ctrl);
if (add_flag && item)
{
item->next = ctrl->server_local->keyservers;
ctrl->server_local->keyservers = item;
}
if (!add_flag && !clear_flag && !help_flag)
{
/* List configured keyservers. However, we first add a global
keyserver. */
uri_item_t u;
err = ensure_keyserver (ctrl);
if (err)
{
assuan_set_error (ctx, err,
"Bad keyserver configuration in dirmngr.conf");
goto leave;
}
for (u=ctrl->server_local->keyservers; u; u = u->next)
dirmngr_status (ctrl, "KEYSERVER", u->uri, NULL);
}
err = 0;
leave:
return leave_cmd (ctx, err);
}
static const char hlp_ks_search[] =
"KS_SEARCH {<pattern>}\n"
"\n"
"Search the configured OpenPGP keyservers (see command KEYSERVER)\n"
"for keys matching PATTERN";
static gpg_error_t
cmd_ks_search (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
strlist_t list, sl;
char *p;
estream_t outfp;
if (has_option (line, "--quick"))
ctrl->timeout = opt.connect_quick_timeout;
line = skip_options (line);
/* Break the line down into an strlist. Each pattern is
percent-plus escaped. */
list = NULL;
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
err = gpg_error_from_syserror ();
goto leave;
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
err = ensure_keyserver (ctrl);
if (err)
goto leave;
/* Setup an output stream and perform the search. */
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
err = ks_action_search (ctrl, ctrl->server_local->keyservers,
list, outfp);
es_fclose (outfp);
}
leave:
free_strlist (list);
return leave_cmd (ctx, err);
}
static const char hlp_ks_get[] =
"KS_GET [--quick] [--newer=TIME] [--ldap] [--first|--next] {<pattern>}\n"
"\n"
"Get the keys matching PATTERN from the configured OpenPGP keyservers\n"
"(see command KEYSERVER). Each pattern should be a keyid, a fingerprint,\n"
"or an exact name indicated by the '=' prefix. Option --quick uses a\n"
"shorter timeout; --ldap will use only ldap servers. With --first only\n"
"the first item is returned; --next is used to return the next item\n"
"Option --newer works only with certain LDAP servers.";
static gpg_error_t
cmd_ks_get (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
strlist_t list = NULL;
strlist_t sl;
const char *s;
char *p;
estream_t outfp;
unsigned int flags = 0;
gnupg_isotime_t opt_newer;
*opt_newer = 0;
if (has_option (line, "--quick"))
ctrl->timeout = opt.connect_quick_timeout;
if (has_option (line, "--ldap"))
flags |= KS_GET_FLAG_ONLY_LDAP;
if (has_option (line, "--first"))
flags |= KS_GET_FLAG_FIRST;
if (has_option (line, "--next"))
flags |= KS_GET_FLAG_NEXT;
if ((s = option_value (line, "--newer"))
&& !string2isotime (opt_newer, s))
{
err = set_error (GPG_ERR_SYNTAX, "invalid time format");
goto leave;
}
line = skip_options (line);
/* Break the line into a strlist. Each pattern is by
definition percent-plus escaped. However we only support keyids
and fingerprints and thus the client has no need to apply the
escaping. */
for (p=line; *p; line = p)
{
while (*p && *p != ' ')
p++;
if (*p)
*p++ = 0;
if (*line)
{
sl = xtrymalloc (sizeof *sl + strlen (line));
if (!sl)
{
err = gpg_error_from_syserror ();
goto leave;
}
sl->flags = 0;
strcpy_escaped_plus (sl->d, line);
sl->next = list;
list = sl;
}
}
if ((flags & KS_GET_FLAG_FIRST) && !(flags & KS_GET_FLAG_ONLY_LDAP))
{
err = PARM_ERROR ("--first is only supported with --ldap");
goto leave;
}
if (list && list->next && (flags & KS_GET_FLAG_FIRST))
{
/* ks_action_get loops over the pattern and we can't easily keep
* this state. */
err = PARM_ERROR ("Only one pattern allowed with --first");
goto leave;
}
if (!list && (flags & KS_GET_FLAG_FIRST))
{
/* Need to add a dummy pattern if no pattern is given. */
if (!add_to_strlist_try (&list, ""))
{
err = gpg_error_from_syserror ();
goto leave;
}
}
if ((flags & KS_GET_FLAG_NEXT))
{
if (list || (flags & ~KS_GET_FLAG_NEXT))
{
err = PARM_ERROR ("No pattern or other options allowed with --next");
goto leave;
}
/* Add a dummy pattern. */
if (!add_to_strlist_try (&list, ""))
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = ensure_keyserver (ctrl);
if (err)
goto leave;
/* Setup an output stream and perform the get. */
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
err = ks_action_get (ctrl, ctrl->server_local->keyservers,
list, flags, opt_newer, outfp);
es_fclose (outfp);
ctrl->server_local->inhibit_data_logging = 0;
}
leave:
free_strlist (list);
return leave_cmd (ctx, err);
}
static const char hlp_ks_fetch[] =
"KS_FETCH <URL>\n"
"\n"
"Get the key(s) from URL.";
static gpg_error_t
cmd_ks_fetch (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
estream_t outfp;
if (has_option (line, "--quick"))
ctrl->timeout = opt.connect_quick_timeout;
line = skip_options (line);
err = ensure_keyserver (ctrl); /* FIXME: Why do we needs this here? */
if (err)
goto leave;
/* Setup an output stream and perform the get. */
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
else
{
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
err = ks_action_fetch (ctrl, line, outfp);
es_fclose (outfp);
ctrl->server_local->inhibit_data_logging = 0;
}
leave:
return leave_cmd (ctx, err);
}
static const char hlp_ks_put[] =
"KS_PUT\n"
"\n"
"Send a key to the configured OpenPGP keyservers. The actual key material\n"
"is then requested by Dirmngr using\n"
"\n"
" INQUIRE KEYBLOCK\n"
"\n"
"The client shall respond with a binary version of the keyblock (e.g.,\n"
"the output of `gpg --export KEYID'). For LDAP\n"
"keyservers Dirmngr may ask for meta information of the provided keyblock\n"
"using:\n"
"\n"
" INQUIRE KEYBLOCK_INFO\n"
"\n"
"The client shall respond with a colon delimited info lines (the output\n"
"of 'gpg --list-keys --with-colons KEYID').\n";
static gpg_error_t
cmd_ks_put (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned char *value = NULL;
size_t valuelen;
unsigned char *info = NULL;
size_t infolen;
/* No options for now. */
line = skip_options (line);
err = ensure_keyserver (ctrl);
if (err)
goto leave;
/* Ask for the key material. */
err = assuan_inquire (ctx, "KEYBLOCK",
&value, &valuelen, MAX_KEYBLOCK_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data returned; return a comprehensible error. */
{
err = gpg_error (GPG_ERR_MISSING_CERT);
goto leave;
}
/* Ask for the key meta data. */
err = assuan_inquire (ctx, "KEYBLOCK_INFO",
&info, &infolen, MAX_KEYBLOCK_LENGTH);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
/* Send the key. */
err = ks_action_put (ctrl, ctrl->server_local->keyservers,
value, valuelen, info, infolen);
leave:
xfree (info);
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_ad_query[] =
"AD_QUERY [--first|--next] [--] <filter> \n"
"\n"
"Query properties from a Windows Active Directory.\n"
"Options:\n"
"\n"
" --rootdse - Query the root using serverless binding,\n"
" --subst - Substitute variables in the filter\n"
" --attr=<attribs> - Comma delimited list of attributes\n"
" to return.\n"
" --help - List supported variables\n"
"\n"
"Extended filter syntax is allowed:\n"
" ^[<base>][&<scope>]&[<filter>]\n"
"Usual escaping rules apply. An ampersand in <base> must\n"
"doubled. <scope> may be \"base\", \"one\", or \"sub\"."
;
static gpg_error_t
cmd_ad_query (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
unsigned int flags = 0;
const char *filter;
estream_t outfp = NULL;
char *p;
char **opt_attr = NULL;
const char *s;
gnupg_isotime_t opt_newer;
int opt_help = 0;
*opt_newer = 0;
/* No options for now. */
if (has_option (line, "--first"))
flags |= KS_GET_FLAG_FIRST;
if (has_option (line, "--next"))
flags |= KS_GET_FLAG_NEXT;
if (has_option (line, "--rootdse"))
flags |= KS_GET_FLAG_ROOTDSE;
if (has_option (line, "--subst"))
flags |= KS_GET_FLAG_SUBST;
if (has_option (line, "--help"))
opt_help = 1;
if ((s = option_value (line, "--newer"))
&& !string2isotime (opt_newer, s))
{
err = set_error (GPG_ERR_SYNTAX, "invalid time format");
goto leave;
}
err = get_option_value (line, "--attr", &p);
if (err)
goto leave;
if (p)
{
opt_attr = strtokenize (p, ",");
if (!opt_attr)
{
err = gpg_error_from_syserror ();
xfree (p);
goto leave;
}
xfree (p);
}
line = skip_options (line);
filter = line;
if (opt_help)
{
#if USE_LDAP
ks_ldap_help_variables (ctrl);
#endif
err = 0;
goto leave;
}
if ((flags & KS_GET_FLAG_NEXT))
{
if (*filter || (flags & ~KS_GET_FLAG_NEXT))
{
err = PARM_ERROR ("No filter or other options allowed with --next");
goto leave;
}
}
/* Setup an output stream and perform the get. */
outfp = es_fopencookie (ctx, "w", data_line_cookie_functions);
if (!outfp)
{
err = set_error (GPG_ERR_ASS_GENERAL, "error setting up a data stream");
goto leave;
}
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
err = ks_action_query (ctrl,
(flags & KS_GET_FLAG_ROOTDSE)? NULL : "ldap:///",
flags, filter, opt_attr, opt_newer, outfp);
leave:
es_fclose (outfp);
xfree (opt_attr);
ctrl->server_local->inhibit_data_logging = 0;
return leave_cmd (ctx, err);
}
static const char hlp_loadswdb[] =
"LOADSWDB [--force]\n"
"\n"
"Load and verify the swdb.lst from the Net.";
static gpg_error_t
cmd_loadswdb (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
err = dirmngr_load_swdb (ctrl, has_option (line, "--force"));
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
"version - Return the version of the program\n"
"pid - Return the process id of the server\n"
"tor - Return OK if running in Tor mode\n"
"dnsinfo - Return info about the DNS resolver\n"
"socket_name - Return the name of the socket\n"
"session_id - Return the current session_id\n"
"workqueue - Inspect the work queue\n"
"stats - Print stats\n"
"getenv NAME - Return value of envvar NAME\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char numbuf[50];
if (!strcmp (line, "version"))
{
const char *s = VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = dirmngr_get_current_socket_name ();
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "session_id"))
{
snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id);
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "tor"))
{
int use_tor;
use_tor = dirmngr_use_tor ();
if (use_tor)
{
if (!is_tor_running (ctrl))
err = assuan_write_status (ctx, "NO_TOR", "Tor not running");
else
err = 0;
if (!err)
assuan_set_okay_line (ctx, use_tor == 1 ? "- Tor mode is enabled"
/**/ : "- Tor mode is enforced");
}
else
err = set_error (GPG_ERR_FALSE, "Tor mode is NOT enabled");
}
else if (!strcmp (line, "dnsinfo"))
{
if (standard_resolver_p ())
assuan_set_okay_line
(ctx, "- Forced use of System resolver (w/o Tor support)");
else
{
#ifdef USE_LIBDNS
assuan_set_okay_line (ctx, (recursive_resolver_p ()
? "- Libdns recursive resolver"
: "- Libdns stub resolver"));
#else
assuan_set_okay_line (ctx, "- System resolver (w/o Tor support)");
#endif
}
err = 0;
}
else if (!strcmp (line, "workqueue"))
{
workqueue_dump_queue (ctrl);
err = 0;
}
else if (!strcmp (line, "stats"))
{
cert_cache_print_stats (ctrl);
domaininfo_print_stats (ctrl);
err = 0;
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
{
err = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
goto leave;
}
err = assuan_send_data (ctx, s, strlen (s));
}
}
#ifdef HAVE_W32_SYSTEM
else if (!strcmp (line, "sid"))
{
PSID mysid;
char *sidstr;
mysid = w32_get_user_sid ();
if (!mysid)
{
err = set_error (GPG_ERR_NOT_FOUND, "Error getting my SID");
goto leave;
}
if (!ConvertSidToStringSid (mysid, &sidstr))
{
err = set_error (GPG_ERR_BUG, "Error converting SID to a string");
goto leave;
}
err = assuan_send_data (ctx, sidstr, strlen (sidstr));
LocalFree (sidstr);
}
#endif /*HAVE_W32_SYSTEM*/
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
leave:
return leave_cmd (ctx, err);
}
static const char hlp_killdirmngr[] =
"KILLDIRMNGR\n"
"\n"
"This command allows a user - given sufficient permissions -\n"
"to kill this dirmngr process.\n";
static gpg_error_t
cmd_killdirmngr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_reloaddirmngr[] =
"RELOADDIRMNGR\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloaddirmngr (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
dirmngr_sighup_action ();
return 0;
}
static const char hlp_flushcrls[] =
"FLUSHCRLS\n"
"\n"
"Remove all cached CRLs from memory and\n"
"the file system.";
static gpg_error_t
cmd_flushcrls (assuan_context_t ctx, char *line)
{
(void)line;
return leave_cmd (ctx, crl_cache_flush () ? GPG_ERR_GENERAL : 0);
}
/* Tell the assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "DNS_CERT", cmd_dns_cert, hlp_dns_cert },
{ "WKD_GET", cmd_wkd_get, hlp_wkd_get },
{ "LDAPSERVER", cmd_ldapserver, hlp_ldapserver },
{ "ISVALID", cmd_isvalid, hlp_isvalid },
{ "CHECKCRL", cmd_checkcrl, hlp_checkcrl },
{ "CHECKOCSP", cmd_checkocsp, hlp_checkocsp },
{ "LOOKUP", cmd_lookup, hlp_lookup },
{ "LOADCRL", cmd_loadcrl, hlp_loadcrl },
{ "LISTCRLS", cmd_listcrls, hlp_listcrls },
{ "CACHECERT", cmd_cachecert, hlp_cachecert },
{ "VALIDATE", cmd_validate, hlp_validate },
{ "KEYSERVER", cmd_keyserver, hlp_keyserver },
{ "KS_SEARCH", cmd_ks_search, hlp_ks_search },
{ "KS_GET", cmd_ks_get, hlp_ks_get },
{ "KS_FETCH", cmd_ks_fetch, hlp_ks_fetch },
{ "KS_PUT", cmd_ks_put, hlp_ks_put },
{ "AD_QUERY", cmd_ad_query, hlp_ad_query },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "LOADSWDB", cmd_loadswdb, hlp_loadswdb },
{ "KILLDIRMNGR",cmd_killdirmngr,hlp_killdirmngr },
{ "RELOADDIRMNGR",cmd_reloaddirmngr,hlp_reloaddirmngr },
{ "FLUSHCRLS", cmd_flushcrls, hlp_flushcrls },
{ NULL, NULL }
};
int i, j, rc;
for (i=j=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Note that we do not reset the list of configured keyservers. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
#if USE_LDAP
ldapserver_list_free (ctrl->server_local->ldapservers);
#endif /*USE_LDAP*/
ctrl->server_local->ldapservers = NULL;
return 0;
}
/* This function is called by our assuan log handler to test whether a
* log message shall really be printed. The function must return
* false to inhibit the logging of MSG. CAT gives the requested log
* category. MSG might be NULL. */
int
dirmngr_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)cat;
(void)msg;
if (!ctrl || !ctrl->server_local)
return 1; /* Can't decide - allow logging. */
if (!ctrl->server_local->inhibit_data_logging)
return 1; /* Not requested - allow logging. */
/* Disallow logging if *_now is true. */
return !ctrl->server_local->inhibit_data_logging_now;
}
/* Startup the server and run the main command loop. With FD = -1,
* use stdin/stdout. SESSION_ID is either 0 or a unique number
* identifying a session. */
void
start_command_handler (assuan_fd_t fd, unsigned int session_id)
{
static const char hello[] = "Dirmngr " VERSION " at your service";
static char *hello_line;
int rc;
assuan_context_t ctx;
ctrl_t ctrl;
ctrl = xtrycalloc (1, sizeof *ctrl);
if (ctrl)
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl || !ctrl->server_local)
{
log_error (_("can't allocate control structure: %s\n"),
strerror (errno));
xfree (ctrl);
return;
}
dirmngr_init_default_ctrl (ctrl);
rc = assuan_new (&ctx);
if (rc)
{
log_error (_("failed to allocate assuan context: %s\n"),
gpg_strerror (rc));
dirmngr_exit (2);
}
if (fd == ASSUAN_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, fd, ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
assuan_release (ctx);
log_error (_("failed to initialize the server: %s\n"),
gpg_strerror(rc));
dirmngr_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error (_("failed to the register commands with Assuan: %s\n"),
gpg_strerror(rc));
dirmngr_exit (2);
}
if (!hello_line)
{
hello_line = xtryasprintf
("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
opt.config_filename? opt.config_filename : "[none]",
hello);
}
ctrl->server_local->assuan_ctx = ctx;
assuan_set_pointer (ctx, ctrl);
assuan_set_hello_line (ctx, hello_line);
assuan_register_option_handler (ctx, option_handler);
assuan_register_reset_notify (ctx, reset_notify);
ctrl->server_local->session_id = session_id;
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
break;
if (rc)
{
log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
break;
}
#ifndef HAVE_W32_SYSTEM
if (opt.verbose)
{
assuan_peercred_t peercred;
if (!assuan_get_peercred (ctx, &peercred))
log_info ("connection from process %ld (%ld:%ld)\n",
(long)peercred->pid, (long)peercred->uid,
(long)peercred->gid);
}
#endif
rc = assuan_process (ctx);
if (rc)
{
log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
continue;
}
}
#if USE_LDAP
ldap_wrapper_connection_cleanup (ctrl);
ldapserver_list_free (ctrl->server_local->ldapservers);
#endif /*USE_LDAP*/
ctrl->server_local->ldapservers = NULL;
release_ctrl_keyservers (ctrl);
ctrl->server_local->assuan_ctx = NULL;
assuan_release (ctx);
if (ctrl->server_local->stopme)
dirmngr_exit (0);
if (ctrl->refcount)
log_error ("oops: connection control structure still referenced (%d)\n",
ctrl->refcount);
else
{
#if USE_LDAP
ks_ldap_free_state (ctrl->ks_get_state);
ctrl->ks_get_state = NULL;
#endif
release_ctrl_ocsp_certs (ctrl);
xfree (ctrl->server_local);
dirmngr_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
}
/* Send a status line back to the client. KEYWORD is the status
keyword, the optional string arguments are blank separated added to
the line, the last argument must be a NULL. */
gpg_error_t
dirmngr_status (ctrl_t ctrl, const char *keyword, ...)
{
gpg_error_t err = 0;
va_list arg_ptr;
assuan_context_t ctx;
va_start (arg_ptr, keyword);
if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx))
{
err = vprint_assuan_status_strings (ctx, keyword, arg_ptr);
}
va_end (arg_ptr);
return err;
}
/* Print a help status line. The function splits text at LFs. */
gpg_error_t
dirmngr_status_help (ctrl_t ctrl, const char *text)
{
gpg_error_t err = 0;
assuan_context_t ctx;
if (ctrl->server_local && (ctx = ctrl->server_local->assuan_ctx))
{
char buf[950], *p;
size_t n;
do
{
p = buf;
n = 0;
for ( ; *text && *text != '\n' && n < DIM (buf)-2; n++)
*p++ = *text++;
if (*text == '\n')
text++;
*p = 0;
err = assuan_write_status (ctx, "#", buf);
}
while (!err && *text);
}
return err;
}
/* Print a help status line using a printf like format. The function
* splits text at LFs. With CTRL being NULL, the function behaves
* like log_info. */
gpg_error_t
dirmngr_status_helpf (ctrl_t ctrl, const char *format, ...)
{
va_list arg_ptr;
gpg_error_t err;
char *buf;
va_start (arg_ptr, format);
if (ctrl)
{
buf = es_vbsprintf (format, arg_ptr);
err = buf? 0 : gpg_error_from_syserror ();
if (!err)
err = dirmngr_status_help (ctrl, buf);
es_free (buf);
}
else
{
log_logv (GPGRT_LOGLVL_INFO, format, arg_ptr);
err = 0;
}
va_end (arg_ptr);
return err;
}
/* This function is similar to print_assuan_status but takes a CTRL
* arg instead of an assuan context as first argument. */
gpg_error_t
dirmngr_status_printf (ctrl_t ctrl, const char *keyword,
const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx;
if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
return 0;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Send a tick progress indicator back. Fixme: This is only done for
the currently active channel. */
gpg_error_t
dirmngr_tick (ctrl_t ctrl)
{
static time_t next_tick = 0;
gpg_error_t err = 0;
time_t now = time (NULL);
if (!next_tick)
{
next_tick = now + 1;
}
else if ( now > next_tick )
{
if (ctrl)
{
err = dirmngr_status (ctrl, "PROGRESS", "tick", "? 0 0", NULL);
if (err)
{
/* Take this as in indication for a cancel request. */
err = gpg_error (GPG_ERR_CANCELED);
}
now = time (NULL);
}
next_tick = now + 1;
}
return err;
}
diff --git a/dirmngr/t-ldap-parse-uri.c b/dirmngr/t-ldap-parse-uri.c
index 984e1412f..e46bbee83 100644
--- a/dirmngr/t-ldap-parse-uri.c
+++ b/dirmngr/t-ldap-parse-uri.c
@@ -1,303 +1,303 @@
/* t-ldap-parse-uri.c - Regression tests for ldap-parse-uri.c.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include "ldap-parse-uri.h"
#include "t-support.h"
#define PGM "t-ldap-parse-uri"
static int verbose;
struct test_ldap_uri_p
{
const char *uri;
int result;
};
void
check_ldap_uri_p (int test_count, struct test_ldap_uri_p *test)
{
int result;
if (verbose)
fprintf (stderr, PGM ": checking '%s'\n", test->uri);
result = ldap_uri_p (test->uri);
if (result != test->result)
{
printf ("'%s' is %san LDAP schema, but ldap_uri_p says opposite.\n",
test->uri, test->result ? "" : "not ");
fail(1000 * test_count);
}
}
static void
test_ldap_uri_p (void)
{
struct test_ldap_uri_p tests[] = {
{ "ldap://foo", 1 },
{ "ldap://", 1 },
{ "ldap:", 1 },
{ "ldap", 0 },
{ "ldapfoobar", 0 },
{ "ldaps://foo", 1 },
{ "ldaps://", 1 },
{ "ldaps:", 1 },
{ "ldaps", 0 },
{ "ldapsfoobar", 0 },
{ "ldapi://foo", 1 },
{ "ldapi://", 1 },
{ "ldapi:", 1 },
{ "ldapi", 0 },
{ "ldapifoobar", 0 },
{ "LDAP://FOO", 1 },
{ "LDAP://", 1 },
{ "LDAP:", 1 },
{ "LDAP", 0 },
{ "LDAPFOOBAR", 0 }
};
int test_count;
for (test_count = 1;
test_count <= sizeof (tests) / sizeof (tests[0]);
test_count ++)
check_ldap_uri_p (test_count, &tests[test_count - 1]);
}
struct test_ldap_parse_uri
{
const char *uri;
const char *scheme;
const char *host;
const int port;
const int use_tls;
const char *path; /* basedn. */
const char *auth; /* binddn. */
const char *password; /* query[1]. */
};
static int
cmp (const char *a, const char *b)
{
if (! a)
a = "";
if (! b)
b = "";
return strcmp (a, b) == 0;
}
void
check_ldap_parse_uri (int test_count, struct test_ldap_parse_uri *test)
{
gpg_error_t err;
parsed_uri_t puri;
if (verbose)
fprintf (stderr, PGM ": parsing '%s'\n", test->uri);
err = ldap_parse_uri (&puri, test->uri);
if (err)
{
printf ("Parsing '%s' failed (%d).\n", test->uri, err);
fail (test_count * 1000 + 0);
}
if (! cmp(test->scheme, puri->scheme))
{
printf ("scheme mismatch: got '%s', expected '%s'.\n",
puri->scheme, test->scheme);
fail (test_count * 1000 + 1);
}
if (! cmp(test->host, puri->host))
{
printf ("host mismatch: got '%s', expected '%s'.\n",
puri->host, test->host);
fail (test_count * 1000 + 2);
}
if (test->port != puri->port)
{
printf ("port mismatch: got '%d', expected '%d'.\n",
puri->port, test->port);
fail (test_count * 1000 + 3);
}
if (test->use_tls != puri->use_tls)
{
printf ("use_tls mismatch: got '%d', expected '%d'.\n",
puri->use_tls, test->use_tls);
fail (test_count * 1000 + 4);
}
if (! cmp(test->path, puri->path))
{
printf ("path mismatch: got '%s', expected '%s'.\n",
puri->path, test->path);
fail (test_count * 1000 + 5);
}
if (! cmp(test->auth, puri->auth))
{
printf ("auth mismatch: got '%s', expected '%s'.\n",
puri->auth, test->auth);
fail (test_count * 1000 + 6);
}
if (! test->password && ! puri->query)
/* Ok. */
;
else if (test->password && ! puri->query)
{
printf ("password mismatch: got NULL, expected '%s'.\n",
test->auth);
fail (test_count * 1000 + 7);
}
else if (! test->password && puri->query)
{
printf ("password mismatch: got something, expected NULL.\n");
fail (test_count * 1000 + 8);
}
else if (! (test->password && puri->query
&& puri->query->name && puri->query->value
&& strcmp (puri->query->name, "password") == 0
&& cmp (puri->query->value, test->password)))
{
printf ("password mismatch: got '%s:%s', expected 'password:%s'.\n",
puri->query->name, puri->query->value,
test->password);
fail (test_count * 1000 + 9);
}
http_release_parsed_uri (puri);
}
static void
test_ldap_parse_uri (void)
{
struct test_ldap_parse_uri tests[] = {
{ "ldap://", "ldap", NULL, 389, 0, NULL, NULL, NULL },
{ "ldap://host", "ldap", "host", 389, 0, NULL, NULL, NULL },
{ "ldap://host:100", "ldap", "host", 100, 0, NULL, NULL, NULL },
{ "ldaps://host", "ldaps", "host", 636, 1, NULL, NULL, NULL },
{ "ldap://host/ou%3DPGP%20Keys%2Cdc%3DEXAMPLE%2Cdc%3DORG",
"ldap", "host", 389, 0, "ou=PGP Keys,dc=EXAMPLE,dc=ORG" },
{ "ldap://host/????bindname=uid%3Duser%2Cou%3DPGP%20Users%2Cdc%3DEXAMPLE%2Cdc%3DORG,password=foobar",
"ldap", "host", 389, 0, "",
"uid=user,ou=PGP Users,dc=EXAMPLE,dc=ORG", "foobar" }
};
int test_count;
for (test_count = 1;
test_count <= sizeof (tests) / sizeof (tests[0]);
test_count ++)
check_ldap_parse_uri (test_count, &tests[test_count - 1]);
}
struct test_ldap_escape_filter
{
const char *filter;
const char *result;
};
static void
check_ldap_escape_filter (int test_count, struct test_ldap_escape_filter *test)
{
char *result = ldap_escape_filter (test->filter);
if (strcmp (result, test->result) != 0)
{
printf ("Filter: '%s'. Escaped: '%s'. Expected: '%s'.\n",
test->filter, result, test->result);
fail (test_count * 1000);
}
xfree (result);
}
static void
test_ldap_escape_filter (void)
{
struct test_ldap_escape_filter tests[] = {
{ "foobar", "foobar" },
{ "", "" },
{ "(foo)", "%28foo%29" },
{ "* ( ) \\ /", "%2a %28 %29 %5c %2f" }
};
int test_count;
for (test_count = 1;
test_count <= sizeof (tests) / sizeof (tests[0]);
test_count ++)
check_ldap_escape_filter (test_count, &tests[test_count - 1]);
}
int
main (int argc, char **argv)
{
int last_argc = -1;
if (argc)
{ argc--; argv++; }
while (argc && last_argc != argc )
{
last_argc = argc;
if (!strcmp (*argv, "--"))
{
argc--; argv++;
break;
}
else if (!strcmp (*argv, "--help"))
{
fputs ("usage: " PGM "\n"
"Options:\n"
" --verbose print timings etc.\n",
stdout);
exit (0);
}
else if (!strcmp (*argv, "--verbose"))
{
verbose++;
argc--; argv++;
}
else if (!strncmp (*argv, "--", 2))
{
fprintf (stderr, PGM ": unknown option '%s'\n", *argv);
exit (1);
}
}
if (argc)
{
- fprintf (stderr, PGM ": no argumenst are expected\n");
+ fprintf (stderr, PGM ": no arguments are expected\n");
exit (1);
}
test_ldap_uri_p ();
test_ldap_parse_uri ();
test_ldap_escape_filter ();
return 0;
}
diff --git a/dirmngr/validate.c b/dirmngr/validate.c
index 02db3c270..94a468b38 100644
--- a/dirmngr/validate.c
+++ b/dirmngr/validate.c
@@ -1,1350 +1,1350 @@
/* validate.c - Validate a certificate chain.
* Copyright (C) 2001, 2003, 2004, 2008 Free Software Foundation, Inc.
* Copyright (C) 2004, 2006, 2008, 2017 g10 Code GmbH
*
* This file is part of DirMngr.
*
* DirMngr 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 2 of the License, or
* (at your option) any later version.
*
* DirMngr 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, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <assert.h>
#include <ctype.h>
#include "dirmngr.h"
#include "certcache.h"
#include "crlcache.h"
#include "validate.h"
#include "misc.h"
/* Mode parameters for cert_check_usage(). */
enum cert_usage_modes
{
CERT_USAGE_MODE_SIGN, /* Usable for encryption. */
CERT_USAGE_MODE_ENCR, /* Usable for signing. */
CERT_USAGE_MODE_VRFY, /* Usable for verification. */
CERT_USAGE_MODE_DECR, /* Usable for decryption. */
CERT_USAGE_MODE_CERT, /* Usable for cert signing. */
- CERT_USAGE_MODE_OCSP, /* Usable for OCSP respone signing. */
+ CERT_USAGE_MODE_OCSP, /* Usable for OCSP response signing. */
CERT_USAGE_MODE_CRL /* Usable for CRL signing. */
};
/* While running the validation function we need to keep track of the
certificates and the validation outcome of each. We use this type
for it. */
struct chain_item_s
{
struct chain_item_s *next;
ksba_cert_t cert; /* The certificate. */
unsigned char fpr[20]; /* Fingerprint of the certificate. */
int is_self_signed; /* This certificate is self-signed. */
- int is_valid; /* The certifiate is valid except for revocations. */
+ int is_valid; /* The certificate is valid except for revocations. */
};
typedef struct chain_item_s *chain_item_t;
/* A couple of constants with Object Identifiers. */
static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1";
static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2";
static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3";
static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4";
static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8";
static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9";
/* Prototypes. */
static gpg_error_t check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert);
/* Make sure that the values defined in the headers are correct. We
* can't use the preprocessor due to the use of enums. */
static void
check_header_constants (void)
{
log_assert (CERTTRUST_CLASS_SYSTEM == VALIDATE_FLAG_TRUST_SYSTEM);
log_assert (CERTTRUST_CLASS_CONFIG == VALIDATE_FLAG_TRUST_CONFIG);
log_assert (CERTTRUST_CLASS_HKP == VALIDATE_FLAG_TRUST_HKP);
log_assert (CERTTRUST_CLASS_HKPSPOOL == VALIDATE_FLAG_TRUST_HKPSPOOL);
#undef X
#define X (VALIDATE_FLAG_TRUST_SYSTEM | VALIDATE_FLAG_TRUST_CONFIG \
| VALIDATE_FLAG_TRUST_HKP | VALIDATE_FLAG_TRUST_HKPSPOOL)
#if ( X & VALIDATE_FLAG_MASK_TRUST ) != X
# error VALIDATE_FLAG_MASK_TRUST is bad
#endif
#if ( ~X & VALIDATE_FLAG_MASK_TRUST )
# error VALIDATE_FLAG_MASK_TRUST is bad
#endif
#undef X
}
/* Check whether CERT contains critical extensions we don't know
about. */
static gpg_error_t
unknown_criticals (ksba_cert_t cert)
{
static const char *known[] = {
"2.5.29.15", /* keyUsage */
"2.5.29.19", /* basic Constraints */
"2.5.29.32", /* certificatePolicies */
"2.5.29.37", /* extendedKeyUsage */
NULL
};
int i, idx, crit;
const char *oid;
int unsupported;
strlist_t sl;
gpg_error_t err, rc;
rc = 0;
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, &crit, NULL, NULL));idx++)
{
if (!crit)
continue;
for (i=0; known[i] && strcmp (known[i],oid); i++)
;
unsupported = !known[i];
/* If this critical extension is not supported, check the list
of to be ignored extensions to see whether we claim that it
is supported. */
if (unsupported && opt.ignored_cert_extensions)
{
for (sl=opt.ignored_cert_extensions;
sl && strcmp (sl->d, oid); sl = sl->next)
;
if (sl)
unsupported = 0;
}
if (unsupported)
{
log_error (_("critical certificate extension %s is not supported"),
oid);
rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
}
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
rc = err; /* Such an error takes precedence. */
return rc;
}
/* Basic check for supported policies. */
static gpg_error_t
check_cert_policy (ksba_cert_t cert)
{
static const char *allowed[] = {
"2.289.9.9",
NULL
};
gpg_error_t err;
int idx;
char *p, *haystack;
char *policies;
int any_critical;
err = ksba_cert_get_cert_policies (cert, &policies);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 0; /* No policy given. */
if (err)
return err;
- /* STRING is a line delimited list of certifiate policies as stored
+ /* STRING is a line delimited list of certificate policies as stored
in the certificate. The line itself is colon delimited where the
first field is the OID of the policy and the second field either
N or C for normal or critical extension */
if (opt.verbose > 1)
log_info ("certificate's policy list: %s\n", policies);
/* The check is very minimal but won't give false positives */
any_critical = !!strstr (policies, ":C");
/* See whether we find ALLOWED (which is an OID) in POLICIES */
for (idx=0; allowed[idx]; idx++)
{
for (haystack=policies; (p=strstr (haystack, allowed[idx]));
haystack = p+1)
{
if ( !(p == policies || p[-1] == '\n') )
continue; /* Does not match the begin of a line. */
if (p[strlen (allowed[idx])] != ':')
continue; /* The length does not match. */
/* Yep - it does match: Return okay. */
ksba_free (policies);
return 0;
}
}
if (!any_critical)
{
log_info (_("Note: non-critical certificate policy not allowed"));
err = 0;
}
else
{
log_info (_("certificate policy not allowed"));
err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
ksba_free (policies);
return err;
}
static gpg_error_t
allowed_ca (ksba_cert_t cert, int *chainlen)
{
gpg_error_t err;
int flag;
err = ksba_cert_is_ca (cert, &flag, chainlen);
if (err)
return err;
if (!flag)
{
if (!is_trusted_cert (cert, CERTTRUST_CLASS_CONFIG))
{
/* The German SigG Root CA's certificate does not flag
itself as a CA; thus we relax this requirement if we
trust a root CA. I think this is reasonable. Note, that
gpgsm implements a far stricter scheme here but also
features a "relax" flag in the trustlist.txt. */
if (chainlen)
*chainlen = 3; /* That is what the SigG implements. */
if (opt.verbose)
log_info (_("accepting root CA not marked as a CA"));
}
else
{
log_error (_("issuer certificate is not marked as a CA"));
return gpg_error (GPG_ERR_BAD_CA_CERT);
}
}
return 0;
}
/* Helper for validate_cert_chain. */
static gpg_error_t
check_revocations (ctrl_t ctrl, chain_item_t chain)
{
gpg_error_t err = 0;
int any_revoked = 0;
int any_no_crl = 0;
int any_crl_too_old = 0;
int any_not_trusted = 0;
chain_item_t ci;
log_assert (ctrl->check_revocations_nest_level >= 0);
log_assert (chain);
if (ctrl->check_revocations_nest_level > 10)
{
log_error (_("CRL checking too deeply nested\n"));
return gpg_error(GPG_ERR_BAD_CERT_CHAIN);
}
ctrl->check_revocations_nest_level++;
if (opt.verbose)
log_info ("[%d] start checking CRLs\n", ctrl->check_revocations_nest_level);
for (ci=chain; ci; ci = ci->next)
{
assert (ci->cert);
if (ci == chain)
{
/* It does not make sense to check the root certificate for
revocations. In almost all cases this will lead to a
catch-22 as the root certificate is the final trust
anchor for the certificates and the CRLs. We expect the
user to remove root certificates from the list of trusted
certificates in case they have been revoked. */
if (opt.verbose)
cert_log_name (_("not checking CRL for"), ci->cert);
continue;
}
if (opt.verbose)
cert_log_name (_("checking CRL for"), ci->cert);
err = crl_cache_cert_isvalid (ctrl, ci->cert, 0);
if (gpg_err_code (err) == GPG_ERR_NO_CRL_KNOWN)
{
err = crl_cache_reload_crl (ctrl, ci->cert);
if (!err)
err = crl_cache_cert_isvalid (ctrl, ci->cert, 0);
}
if (opt.verbose)
log_info ("[%d] result of checking this CRL: %s\n",
ctrl->check_revocations_nest_level, gpg_strerror (err));
switch (gpg_err_code (err))
{
case 0: err = 0; break;
case GPG_ERR_CERT_REVOKED: any_revoked = 1; err = 0; break;
case GPG_ERR_NO_CRL_KNOWN: any_no_crl = 1; err = 0; break;
case GPG_ERR_NOT_TRUSTED: any_not_trusted = 1; err = 0; break;
case GPG_ERR_CRL_TOO_OLD: any_crl_too_old = 1; err = 0; break;
default: break;
}
}
if (err)
;
else if (any_revoked)
err = gpg_error (GPG_ERR_CERT_REVOKED);
else if (any_no_crl)
err = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else if (any_not_trusted)
err = gpg_error (GPG_ERR_NOT_TRUSTED);
else if (any_crl_too_old)
err = gpg_error (GPG_ERR_CRL_TOO_OLD);
else
err = 0;
if (opt.verbose)
log_info ("[%d] result of checking all CRLs: %s\n",
ctrl->check_revocations_nest_level, gpg_strerror (err));
ctrl->check_revocations_nest_level--;
return err;
}
/* Check whether CERT is a root certificate. ISSUERDN and SUBJECTDN
are the DNs already extracted by the caller from CERT. Returns
True if this is the case. */
static int
is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn)
{
gpg_error_t err;
int result = 0;
ksba_sexp_t serialno;
ksba_sexp_t ak_keyid;
ksba_name_t ak_name;
ksba_sexp_t ak_sn;
const char *ak_name_str;
ksba_sexp_t subj_keyid = NULL;
if (!issuerdn || !subjectdn)
return 0; /* No. */
if (strcmp (issuerdn, subjectdn))
return 0; /* No. */
err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 1; /* Yes. Without a authorityKeyIdentifier this needs
to be the Root certificate (our trust anchor). */
log_error ("error getting authorityKeyIdentifier: %s\n",
gpg_strerror (err));
return 0; /* Well, it is broken anyway. Return No. */
}
serialno = ksba_cert_get_serial (cert);
if (!serialno)
{
log_error ("error getting serialno: %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether the auth name's matches the issuer name+sn. If
that is the case this is a root certificate. */
ak_name_str = ksba_name_enum (ak_name, 0);
if (ak_name_str
&& !strcmp (ak_name_str, issuerdn)
&& !cmp_simple_canon_sexp (ak_sn, serialno))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
/* Similar for the ak_keyid. */
if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid)
&& !cmp_simple_canon_sexp (ak_keyid, subj_keyid))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
leave:
ksba_free (subj_keyid);
ksba_free (ak_keyid);
ksba_name_release (ak_name);
ksba_free (ak_sn);
ksba_free (serialno);
return result;
}
/* Validate the certificate CHAIN up to the trust anchor. Optionally
return the closest expiration time in R_EXPTIME (this is useful for
caching issues). MODE is one of the VALIDATE_MODE_* constants.
Note that VALIDATE_MODE_OCSP is not used due to the removal of the
system service in 2.1.15. Instead only the callback to gpgsm to
validate a certificate is used.
If R_TRUST_ANCHOR is not NULL and the validation would fail only
because the root certificate is not trusted, the hexified
fingerprint of that root certificate is stored at R_TRUST_ANCHOR
and success is returned. The caller needs to free the value at
R_TRUST_ANCHOR; in all other cases NULL is stored there. */
gpg_error_t
validate_cert_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t r_exptime,
unsigned int flags, char **r_trust_anchor)
{
gpg_error_t err = 0;
int depth, maxdepth;
char *issuer = NULL;
char *subject = NULL;
ksba_cert_t subject_cert = NULL;
ksba_cert_t issuer_cert = NULL;
ksba_isotime_t current_time;
ksba_isotime_t exptime;
int any_expired = 0;
int any_no_policy_match = 0;
chain_item_t chain;
check_header_constants ();
if (r_exptime)
*r_exptime = 0;
*exptime = 0;
if (r_trust_anchor)
*r_trust_anchor = NULL;
if (DBG_X509)
dump_cert ("subject", cert);
/* May the target certificate be used for this purpose? */
if ((flags & VALIDATE_FLAG_OCSP) && (err = check_cert_use_ocsp (cert)))
return err;
if ((flags & VALIDATE_FLAG_CRL) && (err = check_cert_use_crl (cert)))
return err;
/* If we already validated the certificate not too long ago, we can
avoid the excessive computations and lookups unless the caller
asked for the expiration time. */
if (!r_exptime)
{
size_t buflen;
time_t validated_at;
err = ksba_cert_get_user_data (cert, "validated_at",
&validated_at, sizeof (validated_at),
&buflen);
if (err || buflen != sizeof (validated_at) || !validated_at)
err = 0; /* Not available or other error. */
else
{
/* If the validation is not older than 30 minutes we are ready. */
if (validated_at < gnupg_get_time () + (30*60))
{
if (opt.verbose)
log_info ("certificate is good (cached)\n");
/* Note, that we can't jump to leave here as this would
falsely updated the validation timestamp. */
return 0;
}
}
}
/* Get the current time. */
gnupg_get_isotime (current_time);
/* We walk up the chain until we find a trust anchor. */
subject_cert = cert;
maxdepth = 10; /* Sensible limit on the length of the chain. */
chain = NULL;
depth = 0;
for (;;)
{
/* Get the subject and issuer name from the current
certificate. */
ksba_free (issuer);
ksba_free (subject);
issuer = ksba_cert_get_issuer (subject_cert, 0);
subject = ksba_cert_get_subject (subject_cert, 0);
if (!issuer)
{
log_error (_("no issuer found in certificate\n"));
err = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
/* Handle the notBefore and notAfter timestamps. */
{
ksba_isotime_t not_before, not_after;
err = ksba_cert_get_validity (subject_cert, 0, not_before);
if (!err)
err = ksba_cert_get_validity (subject_cert, 1, not_after);
if (err)
{
log_error (_("certificate with invalid validity: %s"),
gpg_strerror (err));
err = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
/* Keep track of the nearest expiration time in EXPTIME. */
if (*not_after)
{
if (!*exptime)
gnupg_copy_time (exptime, not_after);
else if (strcmp (not_after, exptime) < 0 )
gnupg_copy_time (exptime, not_after);
}
/* Check whether the certificate is already valid. */
if (*not_before && strcmp (current_time, not_before) < 0 )
{
log_error (_("certificate not yet valid"));
log_info ("(valid from ");
dump_isotime (not_before);
log_printf (")\n");
err = gpg_error (GPG_ERR_CERT_TOO_YOUNG);
goto leave;
}
/* Now check whether the certificate has expired. */
if (*not_after && strcmp (current_time, not_after) > 0 )
{
log_error (_("certificate has expired"));
log_info ("(expired at ");
dump_isotime (not_after);
log_printf (")\n");
any_expired = 1;
}
}
/* Do we have any critical extensions in the certificate we
can't handle? */
err = unknown_criticals (subject_cert);
if (err)
goto leave; /* yes. */
/* Check that given policies are allowed. */
err = check_cert_policy (subject_cert);
if (gpg_err_code (err) == GPG_ERR_NO_POLICY_MATCH)
{
any_no_policy_match = 1;
err = 0;
}
else if (err)
goto leave;
/* Is this a self-signed certificate? */
if (is_root_cert (subject_cert, issuer, subject))
{
/* There is no need to check the signature of the trust anchor. */
/* if (check_cert_sig (subject_cert, subject_cert) ) */
/* { */
/* log_error (_("selfsigned certificate has a BAD signature")); */
/* err = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN */
/* : GPG_ERR_BAD_CERT); */
/* goto leave; */
/* } */
/* Is this certificate allowed to act as a CA. */
err = allowed_ca (subject_cert, NULL);
if (err)
goto leave; /* No. */
err = is_trusted_cert (subject_cert,
(flags & VALIDATE_FLAG_MASK_TRUST));
if (!err)
; /* Yes we trust this cert. */
else if (gpg_err_code (err) == GPG_ERR_NOT_TRUSTED)
{
char *fpr;
log_error (_("root certificate is not marked trusted"));
fpr = get_fingerprint_hexstring (subject_cert);
log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
dump_cert ("issuer", subject_cert);
if (r_trust_anchor)
{
/* Caller wants to do another trustiness check. */
*r_trust_anchor = fpr;
err = 0;
}
else
xfree (fpr);
}
else
{
log_error (_("checking trustworthiness of "
"root certificate failed: %s\n"),
gpg_strerror (err));
}
if (err)
goto leave;
/* Prepend the certificate to our list. */
{
chain_item_t ci;
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
{
err = gpg_error_from_errno (errno);
goto leave;
}
ksba_cert_ref (subject_cert);
ci->cert = subject_cert;
cert_compute_fpr (subject_cert, ci->fpr);
ci->next = chain;
chain = ci;
}
if (opt.verbose)
{
if (r_trust_anchor && *r_trust_anchor)
log_info ("root certificate is good but not trusted\n");
else
log_info ("root certificate is good and trusted\n");
}
break; /* Okay: a self-signed certificate is an end-point. */
}
/* To avoid loops, we use an arbitrary limit on the length of
the chain. */
depth++;
if (depth > maxdepth)
{
log_error (_("certificate chain too long\n"));
err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
/* Find the next cert up the tree. */
ksba_cert_release (issuer_cert); issuer_cert = NULL;
err = find_issuing_cert (ctrl, subject_cert, &issuer_cert);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
log_error (_("issuer certificate not found"));
log_info ("issuer certificate: #/");
dump_string (issuer);
log_printf ("\n");
}
else
log_error (_("issuer certificate not found: %s\n"),
gpg_strerror (err));
/* Use a better understandable error code. */
err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
/* try_another_cert: */
if (DBG_X509)
{
log_debug ("got issuer's certificate:\n");
dump_cert ("issuer", issuer_cert);
}
/* Now check the signature of the certificate. FIXME: we should
* delay this until later so that faked certificates can't be
* turned into a DoS easily. */
err = check_cert_sig (issuer_cert, subject_cert);
if (err)
{
log_error (_("certificate has a BAD signature"));
#if 0
if (gpg_err_code (err) == GPG_ERR_BAD_SIGNATURE)
{
/* We now try to find other issuer certificates which
might have been used. This is required because some
CAs are reusing the issuer and subject DN for new
root certificates without using a authorityKeyIdentifier. */
rc = find_up (kh, subject_cert, issuer, 1);
if (!rc)
{
ksba_cert_t tmp_cert;
rc = keydb_get_cert (kh, &tmp_cert);
if (rc || !compare_certs (issuer_cert, tmp_cert))
{
/* The find next did not work or returned an
identical certificate. We better stop here
to avoid infinite checks. */
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
ksba_cert_release (tmp_cert);
}
else
{
do_list (0, lm, fp, _("found another possible matching "
"CA certificate - trying again"));
ksba_cert_release (issuer_cert);
issuer_cert = tmp_cert;
goto try_another_cert;
}
}
}
#endif
/* Return a more descriptive error code than the one
* returned from the signature checking. */
err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
/* Check that the length of the chain is not longer than allowed
* by the CA. */
{
int chainlen;
err = allowed_ca (issuer_cert, &chainlen);
if (err)
goto leave;
if (chainlen >= 0 && (depth - 1) > chainlen)
{
log_error (_("certificate chain longer than allowed by CA (%d)"),
chainlen);
err = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
}
/* May that certificate be used for certification? */
err = check_cert_use_cert (issuer_cert);
if (err)
goto leave; /* No. */
/* Prepend the certificate to our list. */
{
chain_item_t ci;
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
{
err = gpg_error_from_errno (errno);
goto leave;
}
ksba_cert_ref (subject_cert);
ci->cert = subject_cert;
cert_compute_fpr (subject_cert, ci->fpr);
ci->next = chain;
chain = ci;
}
if (opt.verbose)
log_info (_("certificate is good\n"));
/* Now to the next level up. */
subject_cert = issuer_cert;
issuer_cert = NULL;
}
/* Even if we have no error here we need to check whether we
* encountered an error somewhere during the checks. Set the error
* code to the most critical one. */
if (!err)
{
if (any_expired)
err = gpg_error (GPG_ERR_CERT_EXPIRED);
else if (any_no_policy_match)
err = gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
if (!err && opt.verbose)
{
chain_item_t citem;
log_info (_("certificate chain is good\n"));
for (citem = chain; citem; citem = citem->next)
cert_log_name (" certificate", citem->cert);
}
/* Now check for revocations unless CRL checks are disabled or we
* are non-recursive CRL mode. */
if (!err
&& !(flags & VALIDATE_FLAG_NOCRLCHECK)
&& !((flags & VALIDATE_FLAG_CRL)
&& !(flags & VALIDATE_FLAG_RECURSIVE)))
{ /* Now that everything is fine, walk the chain and check each
* certificate for revocations.
*
* 1. item in the chain - The root certificate.
* 2. item - the CA below the root
* last item - the target certificate.
*
* Now for each certificate in the chain check whether it has
* been included in a CRL and thus be revoked. We don't do OCSP
* here because this does not seem to make much sense. This
* might become a recursive process and we should better cache
* our validity results to avoid double work. Far worse a
* catch-22 may happen for an improper setup hierarchy and we
* need a way to break up such a deadlock. */
err = check_revocations (ctrl, chain);
}
if (!err && opt.verbose)
{
if (r_trust_anchor && *r_trust_anchor)
log_info ("target certificate may be valid\n");
else
log_info ("target certificate is valid\n");
}
else if (err && opt.verbose)
log_info ("target certificate is NOT valid\n");
leave:
if (!err && !(r_trust_anchor && *r_trust_anchor))
{
/* With no error we can update the validation cache. We do this
* for all certificates in the chain. Note that we can't use
* the cache if the caller requested to check the trustiness of
* the root certificate himself. Adding such a feature would
* require us to also store the fingerprint of root
* certificate. */
chain_item_t citem;
time_t validated_at = gnupg_get_time ();
for (citem = chain; citem; citem = citem->next)
{
err = ksba_cert_set_user_data (citem->cert, "validated_at",
&validated_at, sizeof (validated_at));
if (err)
{
log_error ("set_user_data(validated_at) failed: %s\n",
gpg_strerror (err));
err = 0;
}
}
}
if (r_exptime)
gnupg_copy_time (r_exptime, exptime);
ksba_free (issuer);
ksba_free (subject);
ksba_cert_release (issuer_cert);
if (subject_cert != cert)
ksba_cert_release (subject_cert);
while (chain)
{
chain_item_t ci_next = chain->next;
if (chain->cert)
ksba_cert_release (chain->cert);
xfree (chain);
chain = ci_next;
}
if (err && r_trust_anchor && *r_trust_anchor)
{
xfree (*r_trust_anchor);
*r_trust_anchor = NULL;
}
return err;
}
/* Return the public key algorithm id from the S-expression PKEY.
FIXME: libgcrypt should provide such a function. Note that this
implementation uses the names as used by libksba. */
int
pk_algo_from_sexp (gcry_sexp_t pkey)
{
gcry_sexp_t l1, l2;
const char *name;
size_t n;
int algo;
l1 = gcry_sexp_find_token (pkey, "public-key", 0);
if (!l1)
return 0; /* Not found. */
l2 = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
name = gcry_sexp_nth_data (l2, 0, &n);
if (!name)
algo = 0; /* Not found. */
else if (n==3 && !memcmp (name, "rsa", 3))
algo = GCRY_PK_RSA;
else if (n==3 && !memcmp (name, "dsa", 3))
algo = GCRY_PK_DSA;
else if (n==3 && !memcmp (name, "ecc", 3))
algo = GCRY_PK_ECC;
else if (n==13 && !memcmp (name, "ambiguous-rsa", 13))
algo = GCRY_PK_RSA;
else
algo = 0;
gcry_sexp_release (l2);
return algo;
}
/* Return the hash algorithm's algo id from its name given in the
* non-null termnated string in (buffer,buflen). Returns 0 on failure
* or if the algo is not known. */
static int
hash_algo_from_buffer (const void *buffer, size_t buflen)
{
char *string;
int algo;
string = xtrymalloc (buflen + 1);
if (!string)
{
log_error (_("out of core\n"));
return 0;
}
memcpy (string, buffer, buflen);
string[buflen] = 0;
algo = gcry_md_map_name (string);
if (!algo)
log_error ("unknown digest algorithm '%s' used in certificate\n", string);
xfree (string);
return algo;
}
/* Return an unsigned integer from the non-null termnated string
* (buffer,buflen). Returns 0 on failure. */
static unsigned int
uint_from_buffer (const void *buffer, size_t buflen)
{
char *string;
unsigned int val;
string = xtrymalloc (buflen + 1);
if (!string)
{
log_error (_("out of core\n"));
return 0;
}
memcpy (string, buffer, buflen);
string[buflen] = 0;
val = strtoul (string, NULL, 10);
xfree (string);
return val;
}
/* Check the signature on CERT using the ISSUER_CERT. This function
* does only test the cryptographic signature and nothing else. It is
* assumed that the ISSUER_CERT is valid. */
static gpg_error_t
check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert)
{
gpg_error_t err;
const char *algoid;
gcry_md_hd_t md;
int algo;
ksba_sexp_t p;
size_t n;
gcry_sexp_t s_sig, s_pkey;
gcry_sexp_t s_hash = NULL;
const char *algo_name; /* hash algorithm name converted to lower case. */
int digestlen;
unsigned char *digest;
int use_pss = 0;
unsigned int saltlen = 0; /* (use is actually controlled by use_pss) */
/* Hash the target certificate using the algorithm from that certificate. */
algoid = ksba_cert_get_digest_algo (cert);
algo = gcry_md_map_name (algoid);
if (!algo && algoid && !strcmp (algoid, "1.2.840.113549.1.1.10"))
use_pss = 1;
else if (!algo)
{
log_error (_("unknown hash algorithm '%s'\n"), algoid? algoid:"?");
return gpg_error (GPG_ERR_GENERAL);
}
/* Get the signature value out of the target certificate. */
p = ksba_cert_get_sig_val (cert);
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
ksba_free (p);
return gpg_error (GPG_ERR_BUG);
}
err = gcry_sexp_sscan ( &s_sig, NULL, p, n);
ksba_free (p);
if (err)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
return err;
}
if (DBG_CRYPTO)
gcry_log_debugsxp ("sigval", s_sig);
if (use_pss)
{
/* Extract the hash algorithm and the salt length from the sigval. */
gcry_buffer_t ioarray[2] = { {0}, {0} };
err = gcry_sexp_extract_param (s_sig, "sig-val",
"&'hash-algo''salt-length'",
ioarray+0, ioarray+1, NULL);
if (err)
{
gcry_sexp_release (s_sig);
log_error ("extracting params from PSS failed: %s\n",
gpg_strerror (err));
return err;
}
algo = hash_algo_from_buffer (ioarray[0].data, ioarray[0].len);
saltlen = uint_from_buffer (ioarray[1].data, ioarray[1].len);
xfree (ioarray[0].data);
xfree (ioarray[1].data);
if (saltlen < 20)
{
log_error ("length of PSS salt too short\n");
gcry_sexp_release (s_sig);
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
if (!algo)
{
gcry_sexp_release (s_sig);
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
/* Add some restrictions; see ../sm/certcheck.c for details. */
switch (algo)
{
case GCRY_MD_SHA1:
case GCRY_MD_SHA256:
case GCRY_MD_SHA384:
case GCRY_MD_SHA512:
case GCRY_MD_SHA3_256:
case GCRY_MD_SHA3_384:
case GCRY_MD_SHA3_512:
break;
default:
log_error ("PSS hash algorithm '%s' rejected\n",
gcry_md_algo_name (algo));
gcry_sexp_release (s_sig);
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
if (gcry_md_get_algo_dlen (algo) != saltlen)
{
log_error ("PSS hash algorithm '%s' rejected due to salt length %u\n",
gcry_md_algo_name (algo), saltlen);
gcry_sexp_release (s_sig);
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
}
algo_name = hash_algo_to_string (algo);
err = gcry_md_open (&md, algo, 0);
if (err)
{
log_error ("md_open failed: %s\n", gpg_strerror (err));
gcry_sexp_release (s_sig);
return err;
}
if (DBG_HASHING)
gcry_md_debug (md, "hash.cert");
err = ksba_cert_hash (cert, 1, HASH_FNC, md);
if (err)
{
log_error ("ksba_cert_hash failed: %s\n", gpg_strerror (err));
gcry_md_close (md);
gcry_sexp_release (s_sig);
return err;
}
gcry_md_final (md);
/* Get the public key from the issuer certificate. */
p = ksba_cert_get_public_key (issuer_cert);
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
log_error ("libksba did not return a proper S-Exp\n");
gcry_md_close (md);
ksba_free (p);
gcry_sexp_release (s_sig);
return gpg_error (GPG_ERR_BUG);
}
err = gcry_sexp_sscan ( &s_pkey, NULL, p, n);
ksba_free (p);
if (err)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (err));
gcry_md_close (md);
gcry_sexp_release (s_sig);
return err;
}
/* Prepare the values for signature verification. At this point we
* have these values:
*
* S_PKEY - S-expression with the issuer's public key.
* S_SIG - Signature value as given in the certificate.
* MD - Finalized hash context with hash of the certificate.
* ALGO_NAME - Lowercase hash algorithm name
* SALTLEN - Salt length for rsaPSS.
*/
digestlen = gcry_md_get_algo_dlen (algo);
digest = gcry_md_read (md, algo);
if (use_pss)
{
err = gcry_sexp_build (&s_hash, NULL,
"(data (flags pss)"
"(hash %s %b)"
"(salt-length %u))",
algo_name,
(int)digestlen,
digest,
saltlen);
}
else if (pk_algo_from_sexp (s_pkey) == GCRY_PK_ECC)
{
unsigned int qbits0, qbits;
qbits0 = gcry_pk_get_nbits (s_pkey);
qbits = qbits0 == 521? 512 : qbits0;
if ((qbits%8))
{
log_error ("ECDSA requires the hash length to be a"
" multiple of 8 bits\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
/* Don't allow any Q smaller than 160 bits. */
if (qbits < 160)
{
log_error (_("%s key uses an unsafe (%u bit) hash\n"),
"ECDSA", qbits0);
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
/* Check if we're too short. */
if (digestlen < qbits/8)
{
log_error (_("a %u bit hash is not valid for a %u bit %s key\n"),
(unsigned int)digestlen*8,
qbits0,
"ECDSA");
if (digestlen < 20)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
}
/* Truncate. */
if (digestlen > qbits/8)
digestlen = qbits/8;
err = gcry_sexp_build (&s_hash, NULL, "(data(flags raw)(value %b))",
(int)digestlen, digest);
}
else /* Not DSA - we assume RSA */
{
err = gcry_sexp_build (&s_hash, NULL, "(data(flags pkcs1)(hash %s %b))",
algo_name, (int)digestlen, digest);
}
if (!err)
err = gcry_pk_verify (s_sig, s_hash, s_pkey);
if (DBG_X509)
log_debug ("%s: gcry_pk_verify: %s\n", __func__, gpg_strerror (err));
leave:
gcry_md_close (md);
gcry_sexp_release (s_sig);
gcry_sexp_release (s_hash);
gcry_sexp_release (s_pkey);
return err;
}
/* Return 0 if CERT is usable for MODE. */
static gpg_error_t
check_cert_usage (ksba_cert_t cert, enum cert_usage_modes mode)
{
gpg_error_t err;
unsigned int use;
char *extkeyusages;
int have_ocsp_signing = 0;
err = ksba_cert_get_ext_key_usages (cert, &extkeyusages);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0; /* No policy given. */
if (!err)
{
unsigned int extusemask = ~0; /* Allow all. */
if (extkeyusages)
{
char *p, *pend;
int any_critical = 0;
extusemask = 0;
p = extkeyusages;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
/* Only care about critical flagged usages. */
if ( *pend == 'C' )
{
any_critical = 1;
if ( !strcmp (p, oid_kp_serverAuth))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_KEY_ENCIPHERMENT
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_clientAuth))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_codeSigning))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE);
else if ( !strcmp (p, oid_kp_emailProtection))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_NON_REPUDIATION
| KSBA_KEYUSAGE_KEY_ENCIPHERMENT
| KSBA_KEYUSAGE_KEY_AGREEMENT);
else if ( !strcmp (p, oid_kp_timeStamping))
extusemask |= (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_NON_REPUDIATION);
}
/* This is a hack to cope with OCSP. Note that we do
not yet fully comply with the requirements and that
the entire CRL/OCSP checking thing should undergo a
thorough review and probably redesign. */
if ( !strcmp (p, oid_kp_ocspSigning))
have_ocsp_signing = 1;
if ((p = strchr (pend, '\n')))
p++;
}
ksba_free (extkeyusages);
extkeyusages = NULL;
if (!any_critical)
extusemask = ~0; /* Reset to the don't care mask. */
}
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
err = 0;
if (opt.verbose && (mode == CERT_USAGE_MODE_SIGN
|| mode == CERT_USAGE_MODE_ENCR))
log_info (_("no key usage specified - assuming all usages\n"));
use = ~0;
}
/* Apply extKeyUsage. */
use &= extusemask;
}
if (err)
{
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (err));
ksba_free (extkeyusages);
return err;
}
switch (mode)
{
case CERT_USAGE_MODE_SIGN:
case CERT_USAGE_MODE_VRFY:
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE
| KSBA_KEYUSAGE_NON_REPUDIATION)))
return 0;
log_info (mode == CERT_USAGE_MODE_VRFY
? _("certificate should not have been used for signing\n")
: _("certificate is not usable for signing\n"));
break;
case CERT_USAGE_MODE_ENCR:
case CERT_USAGE_MODE_DECR:
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT
| KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
return 0;
log_info (mode == CERT_USAGE_MODE_DECR
? _("certificate should not have been used for encryption\n")
: _("certificate is not usable for encryption\n"));
break;
case CERT_USAGE_MODE_CERT:
if ((use & (KSBA_KEYUSAGE_KEY_CERT_SIGN)))
return 0;
log_info (_("certificate should not have "
"been used for certification\n"));
break;
case CERT_USAGE_MODE_OCSP:
if (use != ~0
&& (have_ocsp_signing
|| (use & (KSBA_KEYUSAGE_KEY_CERT_SIGN
|KSBA_KEYUSAGE_CRL_SIGN))))
return 0;
log_info (_("certificate should not have "
"been used for OCSP response signing\n"));
break;
case CERT_USAGE_MODE_CRL:
if ((use & (KSBA_KEYUSAGE_CRL_SIGN)))
return 0;
log_info (_("certificate should not have "
"been used for CRL signing\n"));
break;
}
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
/* Return 0 if the certificate CERT is usable for certification. */
gpg_error_t
check_cert_use_cert (ksba_cert_t cert)
{
return check_cert_usage (cert, CERT_USAGE_MODE_CERT);
}
/* Return 0 if the certificate CERT is usable for signing OCSP
responses. */
gpg_error_t
check_cert_use_ocsp (ksba_cert_t cert)
{
return check_cert_usage (cert, CERT_USAGE_MODE_OCSP);
}
/* Return 0 if the certificate CERT is usable for signing CRLs. */
gpg_error_t
check_cert_use_crl (ksba_cert_t cert)
{
return check_cert_usage (cert, CERT_USAGE_MODE_CRL);
}
diff --git a/doc/DETAILS b/doc/DETAILS
index a278d045f..c689046be 100644
--- a/doc/DETAILS
+++ b/doc/DETAILS
@@ -1,1854 +1,1854 @@
# doc/DETAILS -*- org -*-
#+TITLE: GnuPG Details
# Globally disable superscripts and subscripts:
#+OPTIONS: ^:{}
#+STARTUP: showall
# Note: This file uses org-mode; it should be easy to read as plain
# text but be aware of some markup peculiarities: Verbatim code is
# enclosed in #+begin-example, #+end-example blocks or marked by a
# colon as the first non-white-space character, words bracketed with
# equal signs indicate a monospace font, and the usual /italics/,
# *bold*, and _underline_ conventions are recognized.
This is the DETAILS file for GnuPG which specifies some internals and
parts of the external API for GPG and GPGSM.
* Format of the colon listings
The format is a based on colon separated record, each recods starts
with a tag string and extends to the end of the line. Here is an
example:
#+begin_example
$ gpg --with-colons --list-keys \
--with-fingerprint --with-fingerprint wk@gnupg.org
pub:f:1024:17:6C7EE1B8621CC013:899817715:1055898235::m:::scESC:
fpr:::::::::ECAF7590EB3443B5C7CF3ACB6C7EE1B8621CC013:
uid:f::::::::Werner Koch <wk@g10code.com>:
uid:f::::::::Werner Koch <wk@gnupg.org>:
sub:f:1536:16:06AD222CADF6A6E1:919537416:1036177416:::::e:
fpr:::::::::CF8BCC4B18DE08FCD8A1615906AD222CADF6A6E1:
sub:r:1536:20:5CE086B5B5A18FF4:899817788:1025961788:::::esc:
fpr:::::::::AB059359A3B81F410FCFF97F5CE086B5B5A18FF4:
#+end_example
Note that new version of GnuPG or the use of certain options may add
new fields to the output. Parsers should not assume a limit on the
number of fields per line. Some fields are not yet used or only used
with certain record types; parsers should ignore fields they are not
aware of. New versions of GnuPG or the use of certain options may add
new types of records as well. Parsers should ignore any record whose
type they do not recognize for forward-compatibility.
The double =--with-fingerprint= prints the fingerprint for the subkeys
too. Old versions of gpg used a slightly different format and required
the use of the option =--fixed-list-mode= to conform to the format
described here.
** Description of the fields
*** Field 1 - Type of record
- pub :: Public key
- crt :: X.509 certificate
- crs :: X.509 certificate and private key available
- sub :: Subkey (secondary key)
- sec :: Secret key
- ssb :: Secret subkey (secondary key)
- uid :: User id
- uat :: User attribute (same as user id except for field 10).
- sig :: Signature
- rev :: Revocation signature
- rvs :: Revocation signature (standalone) [since 2.2.9]
- fpr :: Fingerprint (fingerprint is in field 10)
- fp2 :: SHA-256 fingerprint (fingerprint is in field 10)
- pkd :: Public key data [*]
- grp :: Keygrip
- rvk :: Revocation key
- tfs :: TOFU statistics [*]
- tru :: Trust database information [*]
- spk :: Signature subpacket [*]
- cfg :: Configuration data [*]
Records marked with an asterisk are described at [[*Special%20field%20formats][*Special fields]].
*** Field 2 - Validity
This is a letter describing the computed validity of a key.
Currently this is a single letter, but be prepared that additional
information may follow in some future versions. Note that GnuPG <
2.1 does not set this field for secret key listings.
- o :: Unknown (this key is new to the system)
- i :: The key is invalid (e.g. due to a missing self-signature)
- d :: The key has been disabled
(deprecated - use the 'D' in field 12 instead)
- r :: The key has been revoked
- e :: The key has expired
- - :: Unknown validity (i.e. no value assigned)
- q :: Undefined validity. '-' and 'q' may safely be treated as
the same value for most purposes
- n :: The key is not valid
- m :: The key is marginal valid.
- f :: The key is fully valid
- u :: The key is ultimately valid. This often means that the
secret key is available, but any key may be marked as
ultimately valid.
- w :: The key has a well known private part.
- s :: The key has special validity. This means that it might be
self-signed and expected to be used in the STEED system.
If the validity information is given for a UID or UAT record, it
describes the validity calculated based on this user ID. If given
for a key record it describes the validity taken from the best
rated user ID.
For X.509 certificates a 'u' is used for a trusted root
certificate (i.e. for the trust anchor) and an 'f' for all other
valid certificates.
In "sig" records, this field may have one of these values as first
character:
- ! :: Signature is good.
- - :: Signature is bad.
- ? :: No public key to verify signature or public key is not usable.
- % :: Other error verifying a signature
More values may be added later. The field may also be empty if
gpg has been invoked in a non-checking mode (--list-sigs) or in a
fast checking mode. Since 2.2.7 '?' will also be printed by the
command --list-sigs if the key is not in the local keyring.
*** Field 3 - Key length
The length of key in bits.
*** Field 4 - Public key algorithm
The values here are those from the OpenPGP specs or if they are
greater than 255 the algorithm ids as used by Libgcrypt.
*** Field 5 - KeyID
This is the 64 bit keyid as specified by OpenPGP and the last 64
- bit of the SHA-1 fingerprint of an X.509 certifciate.
+ bit of the SHA-1 fingerprint of an X.509 certificate.
*** Field 6 - Creation date
The creation date of the key is given in UTC. For UID and UAT
records, this is used for the self-signature date. Note that the
date is usually printed in seconds since epoch, however, we are
migrating to an ISO 8601 format (e.g. "19660205T091500"). This is
currently only relevant for X.509. A simple way to detect the new
format is to scan for the 'T'. Note that old versions of gpg
without using the =--fixed-list-mode= option used a "yyyy-mm-tt"
format.
*** Field 7 - Expiration date
Key or UID/UAT expiration date or empty if it does not expire.
*** Field 8 - Certificate S/N, UID hash, trust signature info
Used for serial number in crt records. For UID and UAT records,
this is a hash of the user ID contents used to represent that
exact user ID. For trust signatures, this is the trust depth
separated by the trust value by a space.
*** Field 9 - Ownertrust
This is only used on primary keys. This is a single letter, but
be prepared that additional information may follow in future
versions. For trust signatures with a regular expression, this is
the regular expression value, quoted as in field 10.
*** Field 10 - User-ID
The value is quoted like a C string to avoid control characters
(the colon is quoted =\x3a=). For a "pub" record this field is
not used on --fixed-list-mode. A "uat" record puts the attribute
subpacket count here, a space, and then the total attribute
subpacket size. In gpgsm the issuer name comes here. The FPR and
FP2 records store the fingerprints here. The fingerprint of a
revocation key is also stored here. A "grp" records puts the
keygrip here; for combined algorithms the keygrips are delimited
by comma.
*** Field 11 - Signature class
Signature class as per RFC-4880. This is a 2 digit hexnumber
followed by either the letter 'x' for an exportable signature or
the letter 'l' for a local-only signature. The class byte of an
revocation key is also given here, by a 2 digit hexnumber and
optionally followed by the letter 's' for the "sensitive"
flag. This field is not used for X.509.
"rev" and "rvs" may be followed by a comma and a 2 digit hexnumber
with the revocation reason.
*** Field 12 - Key capabilities
The defined capabilities are:
- e :: Encrypt
- s :: Sign
- c :: Certify
- a :: Authentication
- r :: Restricted encryption (subkey only use)
- t :: Timestamping
- g :: Group key
- ? :: Unknown capability
A key may have any combination of them in any order. In addition
to these letters, the primary key has uppercase versions of the
letters to denote the _usable_ capabilities of the entire key, and
a potential letter 'D' to indicate a disabled key.
*** Field 13 - Issuer certificate fingerprint or other info
Used in FPR records for S/MIME keys to store the fingerprint of
the issuer certificate. This is useful to build the certificate
path based on certificates stored in the local key database it is
only filled if the issuer certificate is available. The root has
been reached if this is the same string as the fingerprint. The
advantage of using this value is that it is guaranteed to have
been built by the same lookup algorithm as gpgsm uses.
For "uid" records this field lists the preferences in the same way
gpg's --edit-key menu does.
For "sig", "rev" and "rvs" records, this is the fingerprint of the
key that issued the signature. Note that this may only be filled
if the signature verified correctly. Note also that for various
technical reasons, this fingerprint is only available if
--no-sig-cache is used. Since 2.2.7 this field will also be set
if the key is missing but the signature carries an issuer
fingerprint as meta data.
*** Field 14 - Flag field
Flag field used in the --edit-key menu output
*** Field 15 - S/N of a token
Used in sec/ssb to print the serial number of a token (internal
protect mode 1002) or a '#' if that key is a simple stub (internal
protect mode 1001). If the option --with-secret is used and a
secret key is available for the public key, a '+' indicates this.
*** Field 16 - Hash algorithm
For sig records, this is the used hash algorithm. For example:
2 = SHA-1, 8 = SHA-256.
*** Field 17 - Curve name
For pub, sub, sec, ssb, crt, and crs records this field is used
for the ECC curve name. For combined algorithms the first and the
second algorithm name, delimited by an underscore are put here.
*** Field 18 - Compliance flags
Space separated list of asserted compliance modes and
screening result for this key.
Valid values are:
- 8 :: The key is compliant with RFC4880bis
- 23 :: The key is compliant with compliance mode "de-vs".
- 6001 :: Screening hit on the ROCA vulnerability.
*** Field 19 - Last update
The timestamp of the last update of a key or user ID. The update
time of a key is defined a lookup of the key via its unique
identifier (fingerprint); the field is empty if not known. The
update time of a user ID is defined by a lookup of the key using a
trusted mapping from mail address to key.
*** Field 20 - Origin
The origin of the key or the user ID. This is an integer
optionally followed by a space and an URL. This goes along with
the previous field. The URL is quoted in C style.
*** Field 21 - Comment
This is currently only used in "rev" and "rvs" records to carry
the the comment field of the recocation reason. The value is
quoted in C style.
** Special fields
*** PKD - Public key data
If field 1 has the tag "pkd", a listing looks like this:
#+begin_example
pkd:0:1024:B665B1435F4C2 .... FF26ABB:
! ! !-- the value
! !------ for information number of bits in the value
!--------- index (eg. DSA goes from 0 to 3: p,q,g,y)
#+end_example
*** TFS - TOFU statistics
This field may follows a UID record to convey information about
the TOFU database. The information is similar to a TOFU_STATS
status line.
- Field 2 :: tfs record version (must be 1)
- Field 3 :: validity - A number with validity code.
- Field 4 :: signcount - The number of signatures seen.
- Field 5 :: encrcount - The number of encryptions done.
- Field 6 :: policy - A string with the policy
- Field 7 :: signture-first-seen - a timestamp or 0 if not known.
- Field 8 :: signature-most-recent-seen - a timestamp or 0 if not known.
- Field 9 :: encryption-first-done - a timestamp or 0 if not known.
- Field 10 :: encryption-most-recent-done - a timestamp or 0 if not known.
*** TRU - Trust database information
Example for a "tru" trust base record:
#+begin_example
tru:o:0:1166697654:1:3:1:5
#+end_example
- Field 2 :: Reason for staleness of trust. If this field is
empty, then the trustdb is not stale. This field may
have multiple flags in it:
- o :: Trustdb is old
- t :: Trustdb was built with a different trust model
than the one we are using now.
- Field 3 :: Trust model
- 0 :: Classic trust model, as used in PGP 2.x.
- 1 :: PGP trust model, as used in PGP 6 and later.
This is the same as the classic trust model,
except for the addition of trust signatures.
GnuPG before version 1.4 used the classic trust model
by default. GnuPG 1.4 and later uses the PGP trust
model by default.
- Field 4 :: Date trustdb was created in seconds since Epoch.
- Field 5 :: Date trustdb will expire in seconds since Epoch.
- Field 6 :: Number of marginally trusted users to introduce a new
key signer (gpg's option --marginals-needed).
- Field 7 :: Number of completely trusted users to introduce a new
key signer. (gpg's option --completes-needed)
- Field 8 :: Maximum depth of a certification chain. (gpg's option
--max-cert-depth)
*** SPK - Signature subpacket records
- Field 2 :: Subpacket number as per RFC-4880 and later.
- Field 3 :: Flags in hex. Currently the only two bits assigned
are 1, to indicate that the subpacket came from the
hashed part of the signature, and 2, to indicate the
subpacket was marked critical.
- Field 4 :: Length of the subpacket. Note that this is the
length of the subpacket, and not the length of field
5 below. Due to the need for %-encoding, the length
of field 5 may be up to 3x this value.
- Field 5 :: The subpacket data. Printable ASCII is shown as
ASCII, but other values are rendered as %XX where XX
is the hex value for the byte.
*** CFG - Configuration data
--list-config outputs information about the GnuPG configuration
for the benefit of frontends or other programs that call GnuPG.
There are several list-config items, all colon delimited like the
rest of the --with-colons output. The first field is always "cfg"
to indicate configuration information. The second field is one of
(with examples):
- version :: The third field contains the version of GnuPG.
: cfg:version:1.3.5
- pubkey :: The third field contains the public key algorithms
this version of GnuPG supports, separated by
semicolons. The algorithm numbers are as specified in
RFC-4880. Note that in contrast to the --status-fd
interface these are _not_ the Libgcrypt identifiers.
Using =pubkeyname= prints names instead of numbers.
: cfg:pubkey:1;2;3;16;17
- cipher :: The third field contains the symmetric ciphers this
version of GnuPG supports, separated by semicolons.
The cipher numbers are as specified in RFC-4880.
Using =ciphername= prints names instead of numbers.
: cfg:cipher:2;3;4;7;8;9;10
- digest :: The third field contains the digest (hash) algorithms
this version of GnuPG supports, separated by
semicolons. The digest numbers are as specified in
RFC-4880. Using =digestname= prints names instead of
numbers.
: cfg:digest:1;2;3;8;9;10
- compress :: The third field contains the compression algorithms
this version of GnuPG supports, separated by
semicolons. The algorithm numbers are as specified
in RFC-4880.
: cfg:compress:0;1;2;3
- group :: The third field contains the name of the group, and the
fourth field contains the values that the group expands
to, separated by semicolons.
For example, a group of:
: group mynames = paige 0x12345678 joe patti
would result in:
: cfg:group:mynames:patti;joe;0x12345678;paige
- curve :: The third field contains the curve names this version
of GnuPG supports, separated by semicolons. Using
=curveoid= prints OIDs instead of numbers.
: cfg:curve:ed25519;nistp256;nistp384;nistp521
* Format of the --status-fd output
Every line is prefixed with "[GNUPG:] ", followed by a keyword with
the type of the status line and some arguments depending on the type
(maybe none); an application should always be willing to ignore
unknown keywords that may be emitted by future versions of GnuPG.
Also, new versions of GnuPG may add arguments to existing keywords.
Any additional arguments should be ignored for forward-compatibility.
** General status codes
*** NEWSIG [<signers_uid>]
Is issued right before a signature verification starts. This is
useful to define a context for parsing ERROR status messages.
If SIGNERS_UID is given and is not "-" this is the percent-escaped
value of the OpenPGP Signer's User ID signature sub-packet.
*** GOODSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good. For each signature only one
of the codes GOODSIG, BADSIG, EXPSIG, EXPKEYSIG, REVKEYSIG or
ERRSIG will be emitted. In the past they were used as a marker
for a new signature; new code should use the NEWSIG status
instead. The username is the primary one encoded in UTF-8 and %XX
escaped. The fingerprint may be used instead of the long keyid if
it is available. This is the case with CMS and might eventually
also be available for OpenPGP.
*** EXPSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good, but the signature is
expired. The username is the primary one encoded in UTF-8 and %XX
escaped. The fingerprint may be used instead of the long keyid if
it is available. This is the case with CMS and might eventually
also be available for OpenPGP.
*** EXPKEYSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good, but the signature was made
by an expired key. The username is the primary one encoded in
UTF-8 and %XX escaped. The fingerprint may be used instead of the
long keyid if it is available. This is the case with CMS and
might eventually also be available for OpenPGP.
*** REVKEYSIG <long_keyid_or_fpr> <username>
The signature with the keyid is good, but the signature was made
by a revoked key. The username is the primary one encoded in UTF-8
and %XX escaped. The fingerprint may be used instead of the long
keyid if it is available. This is the case with CMS and might
eventually also beñ available for OpenPGP.
*** BADSIG <long_keyid_or_fpr> <username>
The signature with the keyid has not been verified okay. The
username is the primary one encoded in UTF-8 and %XX escaped. The
fingerprint may be used instead of the long keyid if it is
available. This is the case with CMS and might eventually also be
available for OpenPGP.
*** ERRSIG <keyid> <pkalgo> <hashalgo> <sig_class> <time> <rc> <fpr>
It was not possible to check the signature. This may be caused by
a missing public key or an unsupported algorithm. A RC of 4
indicates unknown algorithm, a 9 indicates a missing public
key. The other fields give more information about this signature.
sig_class is a 2 byte hex-value. The fingerprint may be used
instead of the long_keyid_or_fpr if it is available. This is the
case with gpgsm and might eventually also be available for
OpenPGP. The ERRSIG line has FPR filed which is only available
since 2.2.7; that FPR may either be missing or - if the signature
has no fingerprint as meta data.
Note, that TIME may either be the number of seconds since Epoch or
an ISO 8601 string. The latter can be detected by the presence of
the letter 'T'.
*** VALIDSIG <args>
The args are:
- <fingerprint_in_hex>
- <sig_creation_date>
- <sig-timestamp>
- <expire-timestamp>
- <sig-version>
- <reserved>
- <pubkey-algo>
- <hash-algo>
- <sig-class>
- [ <primary-key-fpr> ]
This status indicates that the signature is cryptographically
valid. This is similar to GOODSIG, EXPSIG, EXPKEYSIG, or REVKEYSIG
(depending on the date and the state of the signature and signing
key) but has the fingerprint as the argument. Multiple status
lines (VALIDSIG and the other appropriate *SIG status) are emitted
for a valid signature. All arguments here are on one long line.
sig-timestamp is the signature creation time in seconds after the
epoch. expire-timestamp is the signature expiration time in
seconds after the epoch (zero means "does not
expire"). sig-version, pubkey-algo, hash-algo, and sig-class (a
2-byte hex value) are all straight from the signature packet.
PRIMARY-KEY-FPR is the fingerprint of the primary key or identical
to the first argument. This is useful to get back to the primary
key without running gpg again for this purpose.
The primary-key-fpr parameter is used for OpenPGP and not
available for CMS signatures. The sig-version as well as the sig
class is not defined for CMS and currently set to 0 and 00.
Note, that *-TIMESTAMP may either be a number of seconds since
Epoch or an ISO 8601 string which can be detected by the presence
of the letter 'T'.
*** ASSERT_SIGNER <fingerprint>
This is emitted for the matching <fingerprint> when option
--assert-signer is used. The fingerprint is printed with
uppercase hex digits.
*** ASSERT_PUBKEY_ALGO <fingerprint> <state> <algostr>
This is emitted when option --assert-pubkey-algo is used and the
signing algorithms is accepted according to that list if state is
1 or denied if state is 0. The fingerprint is printed with
uppercase hex digits.
*** SIG_ID <radix64_string> <sig_creation_date> <sig-timestamp>
This is emitted only for signatures of class 0 or 1 which have
been verified okay. The string is a signature id and may be used
in applications to detect replay attacks of signed messages. Note
that only DLP algorithms give unique ids - others may yield
duplicated ones when they have been created in the same second.
Note, that SIG-TIMESTAMP may either be a number of seconds since
Epoch or an ISO 8601 string which can be detected by the presence
of the letter 'T'.
*** ENC_TO <long_keyid> <keytype> <keylength>
The message is encrypted to this LONG_KEYID. KEYTYPE is the
numerical value of the public key algorithm or 0 if it is not
known, KEYLENGTH is the length of the key or 0 if it is not known
(which is currently always the case). Gpg prints this line
always; Gpgsm only if it knows the certificate.
*** BEGIN_DECRYPTION
Mark the start of the actual decryption process. This is also
emitted when in --list-only mode.
*** END_DECRYPTION
Mark the end of the actual decryption process. This is also
emitted when in --list-only mode.
*** DECRYPTION_KEY <fpr> <fpr2> <otrust>
This line is emitted when a public key decryption succeeded in
providing a session key. <fpr> is the hexified fingerprint of the
actual key used for decryption. <fpr2> is the fingerprint of the
primary key. <otrust> is the letter with the ownertrust; this is
in general a 'u' which stands for ultimately trusted.
*** DECRYPTION_INFO <mdc_method> <sym_algo> [<aead_algo>]
Print information about the symmetric encryption algorithm and the
MDC method. This will be emitted even if the decryption fails.
For an AEAD algorithm AEAD_ALGO is not 0. GPGSM currently does
not print such a status.
*** DECRYPTION_FAILED
The symmetric decryption failed - one reason could be a wrong
passphrase for a symmetrical encrypted message.
*** DECRYPTION_OKAY
The decryption process succeeded. This means, that either the
correct secret key has been used or the correct passphrase for a
symmetric encrypted message was given. The program itself may
return an errorcode because it may not be possible to verify a
signature for some reasons.
*** SESSION_KEY <algo>:<hexdigits>
The session key used to decrypt the message. This message will
only be emitted if the option --show-session-key is used. The
format is suitable to be passed as value for the option
--override-session-key. It is not an indication that the
decryption will or has succeeded.
*** BEGIN_ENCRYPTION <mdc_method> <sym_algo> [<aead_algo>]
Mark the start of the actual encryption process.
MDC_METHOD shall be 0 if an AEAD_ALGO is not 0. Users should
however ignore MDC_METHOD if AEAD_ALGO is not 0.
*** END_ENCRYPTION
Mark the end of the actual encryption process.
*** FILE_START <what> <filename>
Start processing a file <filename>. <what> indicates the performed
operation:
- 1 :: verify
- 2 :: encrypt
- 3 :: decrypt
*** FILE_DONE
Marks the end of a file processing which has been started
by FILE_START.
*** BEGIN_SIGNING
Mark the start of the actual signing process. This may be used as
an indication that all requested secret keys are ready for use.
*** ALREADY_SIGNED <long-keyid>
Warning: This is experimental and might be removed at any time.
*** SIG_CREATED <type> <pk_algo> <hash_algo> <class> <timestamp> <keyfpr>
A signature has been created using these parameters.
Values for type <type> are:
- D :: detached
- C :: cleartext
- S :: standard
(only the first character should be checked)
<class> are 2 hex digits with the OpenPGP signature class.
Note, that TIMESTAMP may either be a number of seconds since Epoch
or an ISO 8601 string which can be detected by the presence of the
letter 'T'.
*** NOTATION_
There are actually three related status codes to convey notation
data:
- NOTATION_NAME <name>
- NOTATION_FLAGS <critical> <human_readable>
- NOTATION_DATA <string>
<name> and <string> are %XX escaped. The data may be split among
several NOTATION_DATA lines. NOTATION_FLAGS is emitted after
NOTATION_NAME and gives the critical and human readable flags;
the flag values are either 0 or 1.
*** POLICY_URL <string>
Note that URL in <string> is %XX escaped.
*** PLAINTEXT <format> <timestamp> <filename>
This indicates the format of the plaintext that is about to be
written. The format is a 1 byte hex code that shows the format of
the plaintext: 62 ('b') is binary data, 74 ('t') is text data with
no character set specified, and 75 ('u') is text data encoded in
the UTF-8 character set. The timestamp is in seconds since the
epoch. If a filename is available it gets printed as the third
argument, percent-escaped as usual.
*** PLAINTEXT_LENGTH <length>
This indicates the length of the plaintext that is about to be
written. Note that if the plaintext packet has partial length
encoding it is not possible to know the length ahead of time. In
that case, this status tag does not appear. The length is only
exact for binary formats; other formats ('t', 'u') may do post
processing like line ending conversion so that the actual number
of bytes written may be differ.
*** ATTRIBUTE <arguments>
The list or arguments are:
- <fpr>
- <octets>
- <type>
- <index>
- <count>
- <timestamp>
- <expiredate>
- <flags>
This is one long line issued for each attribute subpacket when an
attribute packet is seen during key listing. <fpr> is the
fingerprint of the key. <octets> is the length of the attribute
subpacket. <type> is the attribute type (e.g. 1 for an image).
<index> and <count> indicate that this is the N-th indexed
subpacket of count total subpackets in this attribute packet.
<timestamp> and <expiredate> are from the self-signature on the
attribute packet. If the attribute packet does not have a valid
self-signature, then the timestamp is 0. <flags> are a bitwise OR
of:
- 0x01 :: this attribute packet is a primary uid
- 0x02 :: this attribute packet is revoked
- 0x04 :: this attribute packet is expired
*** SIG_SUBPACKET <type> <flags> <len> <data>
This indicates that a signature subpacket was seen. The format is
the same as the "spk" record above.
*** ENCRYPTION_COMPLIANCE_MODE <flags>
Indicates that the current encryption operation was in compliance
with the given set of modes for all recipients. "flags" is a
space separated list of numerical flags, see "Field 18 -
Compliance flags" above.
*** DECRYPTION_COMPLIANCE_MODE <flags>
Indicates that the current decryption operation is in compliance
with the given set of modes. "flags" is a space separated list of
numerical flags, see "Field 18 - Compliance flags" above.
*** VERIFICATION_COMPLIANCE_MODE <flags>
Indicates that the current signature verification operation is in
compliance with the given set of modes. "flags" is a space
separated list of numerical flags, see "Field 18 - Compliance
flags" above.
** Key related
*** INV_RECP, INV_SGNR
The two similar status codes:
- INV_RECP <reason> <requested_recipient>
- INV_SGNR <reason> <requested_sender>
are issued for each unusable recipient/sender. The reasons codes
currently in use are:
- 0 :: No specific reason given
- 1 :: Not Found
- 2 :: Ambiguous specification
- 3 :: Wrong key usage
- 4 :: Key revoked
- 5 :: Key expired
- 6 :: No CRL known
- 7 :: CRL too old
- 8 :: Policy mismatch
- 9 :: Not a secret key
- 10 :: Key not trusted
- 11 :: Missing certificate
- 12 :: Missing issuer certificate
- 13 :: Key disabled
- 14 :: Syntax error in specification
If no specific reason was given a previously emitted status code
KEY_CONSIDERED may be used to analyzed the problem.
Note that for historical reasons the INV_RECP status is also used
for gpgsm's SIGNER command where it relates to signer's of course.
Newer GnuPG versions are using INV_SGNR; applications should
ignore the INV_RECP during the sender's command processing once
they have seen an INV_SGNR. Different codes are used so that they
can be distinguish while doing an encrypt+sign operation.
*** NO_RECP <reserved>
Issued if no recipients are usable.
*** NO_SGNR <reserved>
Issued if no senders are usable.
*** KEY_CONSIDERED <fpr> <flags>
Issued to explain the lookup of a key. FPR is the hexified
fingerprint of the primary key. The bit values for FLAGS are:
- 1 :: The key has not been selected.
- 2 :: All subkeys of the key are expired or have been revoked.
*** KEYEXPIRED <expire-timestamp>
The key has expired. expire-timestamp is the expiration time in
seconds since Epoch. This status line is not very useful because
it will also be emitted for expired subkeys even if this subkey is
not used. To check whether a key used to sign a message has
expired, the EXPKEYSIG status line is to be used.
Note, that the TIMESTAMP may either be a number of seconds since
Epoch or an ISO 8601 string which can be detected by the presence
of the letter 'T'.
*** KEYREVOKED
The used key has been revoked by its owner. No arguments yet.
*** NO_PUBKEY <long keyid>
The public key is not available. Note the arg should in general
not be used because it is better to take it from the ERRSIG
status line which is printed right before this one.
*** NO_SECKEY <long keyid>
The secret key is not available
*** KEY_CREATED <type> <fingerprint> [<handle>]
A key has been created. Values for <type> are:
- B :: primary and subkey
- P :: primary
- S :: subkey
The fingerprint is one of the primary key for type B and P and the
one of the subkey for S. Handle is an arbitrary non-whitespace
string used to match key parameters from batch key creation run.
*** KEY_NOT_CREATED [<handle>]
The key from batch run has not been created due to errors.
*** TRUST_
These are several similar status codes:
#+begin_src
- TRUST_UNDEFINED <error_token> [<validation_model> [<mbox>]]
- TRUST_NEVER <error_token> [<validation_model> [<mbox>]]
- TRUST_MARGINAL 0 [<validation_model> [<mbox>]]
- TRUST_FULLY 0 [<validation_model> [<mbox>]]
- TRUST_ULTIMATE 0 [<validation_model> [<mbox>]]
#+end_src
For good signatures one of these status lines are emitted to
indicate the validity of the key used to create the signature.
<error_token> values other that a literal zero are currently only
emitted by gpgsm.
VALIDATION_MODEL describes the algorithm used to check the
validity of the key. The defaults are the standard Web of Trust
model for gpg and the standard X.509 model for gpgsm. The
defined values are
- classic :: The classic PGP WoT model.
- pgp :: The standard PGP WoT.
- external :: The external PGP trust model.
- tofu :: The GPG Trust-On-First-Use model.
- tofu+pgp :: Ditto but combined with mopdel "pgp".
- always :: The Always trust model.
- direct :: The Direct Trust model.
- shell :: The Standard X.509 model.
- chain :: The Chain model.
- steed :: The STEED model.
- unknown :: An unknown trust model.
Note that the term =TRUST_= in the status names is used for
historic reasons; we now speak of validity.
MBOX is the UTF-8 encoded and percent escaped addr-spec of the
User ID used to compute the validity of a signature. If this is
not known the validity is computed on the key with no specific
User ID. Note that MBOX is always the addr-spec of the User ID;
for User IDs without a proper addr-spec a dash is used to
distinguish this from the case that no User ID at all is known.
The MBOX is either taken from the Signer's User ID signature
sub-packet or from the addr-spec passed to gpg using the --sender
option. If both are available and they don't match
TRUST_UNDEFINED along with an error code is emitted. MBOX is not
used by gpgsm.
*** TOFU_USER <fingerprint_in_hex> <mbox>
This status identifies the key and the userid for all following
Tofu information. The fingerprint is the fingerprint of the
primary key and the mbox is in general the addr-spec part of the
userid encoded in UTF-8 and percent escaped. The fingerprint is
identical for all TOFU_USER lines up to a NEWSIG line.
*** TOFU_STATS <MANY_ARGS>
Statistics for the current user id.
The <MANY_ARGS> are the usual space delimited arguments. Here we
have too many of them to fit on one printed line and thus they are
given on 3 printed lines:
: <summary> <sign-count> <encryption-count>
: [<policy> [<tm1> <tm2> <tm3> <tm4>
: [<validity> [<sign-days> <encrypt-days>]]]]
Values for SUMMARY are:
- 0 :: attention, an interaction with the user is required (conflict)
- 1 :: key with no verification/encryption history
- 2 :: key with little history
- 3 :: key with enough history for basic trust
- 4 :: key with a lot of history
Values for POLICY are:
- none :: No Policy set
- auto :: Policy is "auto"
- good :: Policy is "good"
- bad :: Policy is "bad"
- ask :: Policy is "ask"
- unknown :: Policy is "unknown" (TOFU information does not
contribute to the key's validity)
TM1 is the time the first message was verified. TM2 is the time
the most recent message was verified. TM3 is the time the first
message was encrypted. TM4 is the most recent encryption. All may
either be seconds since Epoch or an ISO time string
(yyyymmddThhmmss).
VALIDITY is the same as SUMMARY with the exception that VALIDITY
doesn't reflect whether the key needs attention. That is it never
takes on value 0. Instead, if there is a conflict, VALIDITY still
reflects the key's validity (values: 1-4).
SUMMARY values use the euclidean distance (m = sqrt(a² + b²)) rather
then the sum of the magnitudes (m = a + b) to ensure a balance between
verified signatures and encrypted messages.
Values are calculated based on the number of days where a key was used
for verifying a signature or to encrypt to it.
The ranges for the values are:
- 1 :: signature_days + encryption_days == 0
- 2 :: 1 <= sqrt(signature_days² + encryption_days²) < 8
- 3 :: 8 <= sqrt(signature_days² + encryption_days²) < 42
- 4 :: sqrt(signature_days² + encryption_days²) >= 42
SIGN-COUNT and ENCRYPTION-COUNT are the number of messages that we
have seen that have been signed by this key / encryption to this
key.
SIGN-DAYS and ENCRYPTION-DAYS are similar, but the number of days
(in UTC) on which we have seen messages signed by this key /
encrypted to this key.
*** TOFU_STATS_SHORT <long_string>
Information about the TOFU binding for the signature.
Example: "15 signatures verified. 10 messages encrypted"
*** TOFU_STATS_LONG <long_string>
Information about the TOFU binding for the signature in verbose
format. The LONG_STRING is percent escaped.
Example: 'Verified 9 messages signed by "Werner Koch
(dist sig)" in the past 3 minutes, 40 seconds. The most
recent message was verified 4 seconds ago.'
*** PKA_TRUST_
This is one of:
- PKA_TRUST_GOOD <addr-spec>
- PKA_TRUST_BAD <addr-spec>
Depending on the outcome of the PKA check one of the above status
codes is emitted in addition to a =TRUST_*= status.
** Remote control
*** GET_BOOL, GET_LINE, GET_HIDDEN, GOT_IT
These status line are used with --command-fd for interactive
control of the process.
*** USERID_HINT <long main keyid> <string>
Give a hint about the user ID for a certain keyID.
*** NEED_PASSPHRASE <long keyid> <long main keyid> <keytype> <keylength>
Issued whenever a passphrase is needed. KEYTYPE is the numerical
value of the public key algorithm or 0 if this is not applicable,
KEYLENGTH is the length of the key or 0 if it is not known (this
is currently always the case).
*** NEED_PASSPHRASE_SYM <cipher_algo> <s2k_mode> <s2k_hash>
Issued whenever a passphrase for symmetric encryption is needed.
*** NEED_PASSPHRASE_PIN <card_type> <chvno> [<serialno>]
Issued whenever a PIN is requested to unlock a card.
*** MISSING_PASSPHRASE
No passphrase was supplied. An application which encounters this
message may want to stop parsing immediately because the next
message will probably be a BAD_PASSPHRASE. However, if the
application is a wrapper around the key edit menu functionality it
might not make sense to stop parsing but simply ignoring the
following BAD_PASSPHRASE.
*** BAD_PASSPHRASE <long keyid>
The supplied passphrase was wrong or not given. In the latter
case you may have seen a MISSING_PASSPHRASE.
*** GOOD_PASSPHRASE
The supplied passphrase was good and the secret key material
is therefore usable.
** Import/Export
*** IMPORT_CHECK <long keyid> <fingerprint> <user ID>
This status is emitted in interactive mode right before
the "import.okay" prompt.
*** IMPORTED <long keyid> <username>
The keyid and name of the signature just imported
*** IMPORT_OK <reason> [<fingerprint>]
The key with the primary key's FINGERPRINT has been imported.
REASON flags are:
- 0 :: Not actually changed
- 1 :: Entirely new key.
- 2 :: New user IDs
- 4 :: New signatures
- 8 :: New subkeys
- 16 :: Contains private key.
The flags may be ORed.
*** IMPORT_PROBLEM <reason> [<fingerprint>]
Issued for each import failure. Reason codes are:
- 0 :: No specific reason given.
- 1 :: Invalid Certificate.
- 2 :: Issuer Certificate missing.
- 3 :: Certificate Chain too long.
- 4 :: Error storing certificate.
*** IMPORT_RES <args>
Final statistics on import process (this is one long line). The
args are a list of unsigned numbers separated by white space:
- <count>
- <no_user_id>
- <imported>
- always 0 (formerly used for the number of RSA keys)
- <unchanged>
- <n_uids>
- <n_subk>
- <n_sigs>
- <n_revoc>
- <sec_read>
- <sec_imported>
- <sec_dups>
- <skipped_new_keys>
- <not_imported>
- <skipped_v3_keys>
*** EXPORTED <fingerprint>
The key with <fingerprint> has been exported. The fingerprint is
the fingerprint of the primary key even if the primary key has
been replaced by a stub key during secret key export.
*** EXPORT_RES <args>
Final statistics on export process (this is one long line). The
args are a list of unsigned numbers separated by white space:
- <count>
- <secret_count>
- <exported>
** Smartcard related
*** CARDCTRL <what> [<serialno>]
This is used to control smartcard operations. Defined values for
WHAT are:
- 1 :: Request insertion of a card. Serialnumber may be given
to request a specific card. Used by gpg 1.4 w/o
scdaemon
- 2 :: Request removal of a card. Used by gpg 1.4 w/o scdaemon.
- 3 :: Card with serialnumber detected
- 4 :: No card available
- 5 :: No card reader available
- 6 :: No card support available
- 7 :: Card is in termination state
*** SC_OP_FAILURE [<code>]
An operation on a smartcard definitely failed. Currently there is
no indication of the actual error code, but application should be
prepared to later accept more arguments. Defined values for
<code> are:
- 0 :: unspecified error (identically to a missing CODE)
- 1 :: canceled
- 2 :: bad PIN
*** SC_OP_SUCCESS
A smart card operation succeeded. This status is only printed for
certain operation and is mostly useful to check whether a PIN
change really worked.
** Miscellaneous status codes
*** NODATA <what>
No data has been found. Codes for WHAT are:
- 1 :: No armored data.
- 2 :: Expected a packet but did not found one.
- 3 :: Invalid packet found, this may indicate a non OpenPGP
message.
- 4 :: Signature expected but not found
You may see more than one of these status lines.
*** UNEXPECTED <what>
Unexpected data has been encountered. Codes for WHAT are:
- 0 :: Not further specified
- 1 :: Corrupted message structure
*** TRUNCATED <maxno>
The output was truncated to MAXNO items. This status code is
issued for certain external requests.
*** ERROR <error location> <error code> [<more>]
This is a generic error status message, it might be followed by
error location specific data. <error code> and <error_location>
should not contain spaces. The error code is a either a string
commencing with a letter or such a string prefixed with a
numerical error code and an underscore; e.g.: "151011327_EOF".
Some of the error locations are:
- decryption.early_plaintext :: The OpenPGP message contains more
than one plaintext.
- genkey :: Problem generating a key. The error code further
describes the problem.
- get_passphrase :: Problem getting the passphrase from the
gpg-agent.
- keyedit.passwd :: Changing the password failed.
- nomdc_with_legacy_cipher :: The message was not MDC protected.
- Use the command line to lern about a workaround.
+ Use the command line to learn about a workaround.
- random-compliance :: The random number generator or the used
version of Libgcrypt do not fulfill the requirements of the
current compliance setting. The error code is often
GPG_ERR_FORBIDDEN.
- set_expire :: Changing the expiration time failed.
*** WARNING <location> <error code> [<text>]
This is a generic warning status message, it might be followed by
error location specific data. <location> and <error code> may not
contain spaces. The <location> may be used to indicate a class of
warnings. The error code is a either a string commencing with a
letter or such a string prefixed with a numerical error code and
an underscore; e.g.: "151011327_EOF".
*** NOTE <location> <error code> [<text>]
This is a generic info status message the same syntax as for
WARNING messages is used.
*** SUCCESS [<location>]
Positive confirmation that an operation succeeded. It is used
similar to ISO-C's EXIT_SUCCESS. <location> is optional but if
given should not contain spaces. Used only with a few commands.
*** FAILURE <location> <error_code>
This is the counterpart to SUCCESS and used to indicate a program
failure. It is used similar to ISO-C's EXIT_FAILURE but allows
conveying more information, in particular a gpg-error error code.
That numerical error code may optionally have a suffix made of an
underscore and a string with an error symbol like "151011327_EOF".
A dash may be used instead of <location>.
*** BADARMOR
The ASCII armor is corrupted. No arguments yet.
*** DELETE_PROBLEM <reason_code>
Deleting a key failed. Reason codes are:
- 1 :: No such key
- 2 :: Must delete secret key first
- 3 :: Ambiguous specification
- 4 :: Key is stored on a smartcard.
*** PROGRESS <what> <char> <cur> <total> [<units>]
Used by the primegen and public key functions to indicate
progress. <char> is the character displayed with no --status-fd
enabled, with the linefeed replaced by an 'X'. <cur> is the
current amount done and <total> is amount to be done; a <total> of
0 indicates that the total amount is not known. Both are
non-negative integers. The condition
: TOTAL && CUR == TOTAL
may be used to detect the end of an operation.
Well known values for <what> are:
- pk_dsa :: DSA key generation
- pk_elg :: Elgamal key generation
- primegen :: Prime generation
- need_entropy :: Waiting for new entropy in the RNG
- tick :: Generic tick without any special meaning - useful
for letting clients know that the server is still
working.
- starting_agent :: A gpg-agent was started because it is not
running as a daemon.
- learncard :: Send by the agent and gpgsm while learing
the data of a smartcard.
- card_busy :: A smartcard is still working
- scd_locked :: Waiting for other clients to unlock the
scdaemon
- gpgtar :: Here <char> has a special meaning: 's'
indicates total size and 'c' file count. A
<total> of zero indicates that gpgtar is in the
scanning phase. A positive <total> is used in
the writing phase.
When <what> refers to a file path, it may be truncated.
<units> is sometimes used to describe the units for <current> and
<total>. For example "B", "KiB", or "MiB".
*** BACKUP_KEY_CREATED <fingerprint> <fname>
- A backup of a key identified by <fingerprint> has been writte to
+ A backup of a key identified by <fingerprint> has been written to
the file <fname>; <fname> is percent-escaped.
*** MOUNTPOINT <name>
<name> is a percent-plus escaped filename describing the
mountpoint for the current operation (e.g. used by "g13 --mount").
This may either be the specified mountpoint or one randomly
chosen by g13.
*** PINENTRY_LAUNCHED <pid>[:<extra>]
This status line is emitted by gpg to notify a client that a
Pinentry has been launched. <pid> is the PID of the Pinentry. It
may be used to display a hint to the user but can't be used to
synchronize with Pinentry. Note that there is also an Assuan
inquiry line with the same name used internally or, if enabled,
send to the client instead of this status line. Such an inquiry
may be used to sync with Pinentry
*** GPGTAR_EXTRACT <tot> <skp> <bad> <sus> <sym> <hrd> <oth>
This status line is emitted after gpgtar has extracted files.
- tot :: Total number of files extracted and stored
- skp :: Total number of files skipped during extraction
- bad :: Number of files skipped due to a bad file name
- sus :: Number of files skipped due to a suspicious file name
- sym :: Number of symlinks not restored
- hrd :: Number of hard links not restored
- oth :: Number of files not extracted due to other reasons.
** Obsolete status codes
*** SIGEXPIRED
Removed on 2011-02-04. This is deprecated in favor of KEYEXPIRED.
*** RSA_OR_IDEA
Obsolete. This status message used to be emitted for requests to
use the IDEA or RSA algorithms. It has been dropped from GnuPG
2.1 after the respective patents expired.
*** SHM_INFO, SHM_GET, SHM_GET_BOOL, SHM_GET_HIDDEN
These were used for the ancient shared memory based co-processing.
*** BEGIN_STREAM, END_STREAM
Used to issued by the experimental pipemode.
*** GOODMDC
This is not anymore needed. Checking the DECRYPTION_OKAY status is
sufficient.
*** BADMDC
This is not anymore needed.
** Inter-component codes
Status codes are also used between the components of the GnuPG
system via the Assuan S lines. Some of them are documented here:
*** PUBKEY_INFO <n> <ubid> <flags> <uidno> <pkno>
The type of the public key in the following D-lines or
communicated via a pipe. <n> is the value of =enum pubkey_types=
and <ubid> the Unique Blob ID (UBID) which is the fingerprint of
the primary key truncated to 20 octets and formatted in hex. Note
that the keyboxd SEARCH command can be used to lookup the public
key using the <ubid> prefixed with a caret (^).
<flags> is a string extra information about the blob. The first
byte is either '-' for standard key or 'e' for an ephemeral key.
The second byte is either '-' or 'r' for a known revoked key.
<uidno> and <pkno> are the ordinal numbers for the the user id or
public key which matches the search criteria. A value of 0 means
not known.
*** KEYPAIRINFO <grip> <keyref> [<usage>] [<keytime>] [<algostr>]
This status is emitted by scdaemon and gpg-agent to convey brief
information about keypairs stored on tokens. <grip> is the
hexified keygrip of the key or, if no key is stored, an "X".
<keyref> is the ID of a card's key; for example "OPENPGP.2" for
the second key slot of an OpenPGP card. <usage> is optional and
returns technically possible key usages, this is a string of
single letters describing the usage ('c' for certify, 'e' for
encryption, 's' for signing, 'a' for authentication). A '-' can be
used to tell that usage flags are not conveyed. <keytime> is used
by OpenPGP cards for the stored key creation time. A '-' means no
info available. The format is the usual ISO string or a number
with the seconds since Epoch. <algostr> is the algorithm or curve
this key uses (e.g. "rsa2048") or a "-" if not known.
*** CERTINFO <certtype> <certref> [<label>]
- This status is emitted for X.509 certifcates.
+ This status is emitted for X.509 certificates.
CERTTYPE is a number indicating the type of the certificate:
0 := Unknown
100 := Regular X.509 cert
101 := Trusted X.509 cert
102 := Useful X.509 cert
110 := Root CA cert in a special format (e.g. DINSIG)
111 := Root CA cert as standard X509 cert
CERTREF identifies the certificate uniquely on the card and may be
used to match it with a key's KEYREF. LABEL is an optional human
- readable decription of the certificate; it won't have any space in
+ readable description of the certificate; it won't have any space in
it and is percent encoded.
*** MANUFACTURER <n> [<string>]
This status returns the Manufactorer ID as the unsigned number N.
For OpenPGP this is well defined; for other cards this is 0. The
name of the manufacturer is also given as <string>; spaces are not
escaped. For PKCS#15 cards <string> is TokenInfo.manufactorerID;
a string in brackets describing GnuPG's own card product name may
be appended to <string>.
*** KEY-STATUS <keyref> <status>
This is the response from scdaemon on GETATTR KEY-STATUS for
OpenPGP cards. <keyref> is the usual keyref (e.g. OPENPGP.1 or
OPENPGP.129) and <status> is an integer describing the status of
the key: 0 = key is not present, 1 = key generated on card, 2 =
key imported. See section 4.4.3.8 of the OpenPGP Smart Card
Application V3.4.
*** KEY-ATTR-INFO <keyref> <string>
This is the response from scdaemon on GETATTR KEY-ATTR-INFO for
OpenPGP cards. <keyref> is the usual keyref (e.g. OPENPGP.1 or
- OPENPGP.129) and <string> is the algoritm or curve name, which
+ OPENPGP.129) and <string> is the algorithm or curve name, which
is available for the key.
*** KEY-TIME <n> <timestamp>
This is a response from scdaemon on GETATTR KEY-TIME. A keyref N
of 1 gives the timestamp for the standard OpenPGP signing key, 2
for the encryption key, and 3 for an authentication key. Note
that a KEYPAIRINFO status lines carries the same information and
should be preferred.
*** KEY-LABEL <keyref> <label>
This returns the human readbable label for the keys given by
KEYREF. LABEL won't have any space in it and is percent encoded.
- This info shall only be used for dispaly purposes.
+ This info shall only be used for display purposes.
* Format of the --attribute-fd output
When --attribute-fd is set, during key listings (--list-keys,
--list-secret-keys) GnuPG dumps each attribute packet to the file
descriptor specified. --attribute-fd is intended for use with
--status-fd as part of the required information is carried on the
ATTRIBUTE status tag (see above).
The contents of the attribute data is specified by RFC 4880. For
convenience, here is the Photo ID format, as it is currently the
only attribute defined:
- Byte 0-1 :: The length of the image header. Due to a historical
accident (i.e. oops!) back in the NAI PGP days, this
is a little-endian number. Currently 16 (0x10 0x00).
- Byte 2 :: The image header version. Currently 0x01.
- Byte 3 :: Encoding format. 0x01 == JPEG.
- Byte 4-15 :: Reserved, and currently unused.
All other data after this header is raw image (JPEG) data.
* Layout of the TrustDB
The TrustDB is built from fixed length records, where the first byte
describes the record type. All numeric values are stored in network
byte order. The length of each record is 40 bytes. The first
record of the DB is always of type 1 and this is the only record of
this type.
The record types: directory(2), key(3), uid(4), pref(5), sigrec(6),
and shadow directory(8) are not anymore used by version 2 of the
TrustDB.
** Record type 0
Unused record or deleted, can be reused for any purpose. Such
records should in general not exist because deleted records are of
type 254 and kept in a linked list.
** Version info (RECTYPE_VER, 1)
Version information for this TrustDB. This is always the first
record of the DB and the only one of this type.
- 1 u8 :: Record type (value: 1).
- 3 byte :: Magic value ("gpg")
- 1 u8 :: TrustDB version (value: 2).
- 1 u8 :: =marginals=. How many marginal trusted keys are required.
- 1 u8 :: =completes=. How many completely trusted keys are
required.
- 1 u8 :: =max_cert_depth=. How deep is the WoT evaluated. Along
with =marginals= and =completes=, this value is used to
check whether the cached validity value from a [FIXME
dir] record can be used.
- 1 u8 :: =trust_model=
- 1 u8 :: =min_cert_level=
- 2 byte :: Not used
- 1 u32 :: =created=. Timestamp of trustdb creation.
- 1 u32 :: =nextcheck=. Timestamp of last modification which may
affect the validity of keys in the trustdb. This value
is checked against the validity timestamp in the dir
records.
- 1 u32 :: =reserved=. Not used.
- 1 u32 :: =reserved2=. Not used.
- 1 u32 :: =firstfree=. Number of the record with the head record
of the RECTYPE_FREE linked list.
- 1 u32 :: =reserved3=. Not used.
- 1 u32 :: =trusthashtbl=. Record number of the trusthashtable.
** Hash table (RECTYPE_HTBL, 10)
Due to the fact that we use fingerprints to lookup keys, we can
implement quick access by some simple hash methods, and avoid the
overhead of gdbm. A property of fingerprints is that they can be
used directly as hash values. What we use is a dynamic multilevel
architecture, which combines hash tables, record lists, and linked
lists.
This record is a hash table of 256 entries with the property that
all these records are stored consecutively to make one big
table. The hash value is simple the 1st, 2nd, ... byte of the
fingerprint (depending on the indirection level).
- 1 u8 :: Record type (value: 10).
- 1 u8 :: Reserved
- n u32 :: =recnum=. A table with the hash table items fitting into
this record. =n= depends on the record length:
$n=(reclen-2)/4$ which yields 9 for oure current record
length of 40 bytes.
The total number of hash table records to form the table is:
$m=(256+n-1)/n$. This is 29 for our record length of 40.
To look up a key we use the first byte of the fingerprint to get
the recnum from this hash table and then look up the addressed
record:
- If that record is another hash table, we use 2nd byte to index
that hash table and so on;
- if that record is a hash list, we walk all entries until we find
a matching one; or
- if that record is a key record, we compare the fingerprint to
decide whether it is the requested key;
** Hash list (RECTYPE_HLST, 11)
See hash table above on how it is used. It may also be used for
other purposes.
- 1 u8 :: Record type (value: 11).
- 1 u8 :: Reserved.
- 1 u32 :: =next=. Record number of the next hash list record or 0
if none.
- n u32 :: =rnum=. Array with record numbers to values. With
$n=(reclen-5)/5$ and our record length of 40, n is 7.
** Trust record (RECTYPE_TRUST, 12)
- 1 u8 :: Record type (value: 12).
- 1 u8 :: Reserved.
- 20 byte :: =fingerprint=.
- 1 u8 :: =ownertrust=.
- 1 u8 :: =depth=.
- 1 u8 :: =min_ownertrust=.
- 1 byte :: =flags=.
- 1 u32 :: =validlist=.
- 10 byte :: Not used.
** Validity record (RECTYPE_VALID, 13)
- 1 u8 :: Record type (value: 13).
- 1 u8 :: Reserved.
- 20 byte :: =namehash=.
- 1 u8 :: =validity=
- 1 u32 :: =next=.
- 1 u8 :: =full_count=.
- 1 u8 :: =marginal_count=.
- 11 byte :: Not used.
** Free record (RECTYPE_FREE, 254)
All these records form a linked list of unused records in the TrustDB.
- 1 u8 :: Record type (value: 254)
- 1 u8 :: Reserved.
- 1 u32 :: =next=. Record number of the next rcord of this type.
The record number to the head of this linked list is
stored in the version info record.
* Database scheme for the TOFU info
#+begin_src sql
--
-- The VERSION table holds the version of our TOFU data structures.
--
CREATE TABLE version (
version integer -- As of now this is always 1
);
--
-- The BINDINGS table associates mail addresses with keys.
--
CREATE TABLE bindings (
oid integer primary key autoincrement,
fingerprint text, -- The key's fingerprint in hex
email text, -- The normalized mail address destilled from user_id
user_id text, -- The unmodified user id
time integer, -- The time this binding was first observed.
policy boolean check
(policy in (1, 2, 3, 4, 5)), -- The trust policy with the values:
-- 1 := Auto
-- 2 := Good
-- 3 := Unknown
-- 4 := Bad
-- 5 := Ask
conflict string, -- NULL or a hex formatted fingerprint.
unique (fingerprint, email)
);
CREATE INDEX bindings_fingerprint_email on bindings (fingerprint, email);
CREATE INDEX bindings_email on bindings (email);
--
-- The SIGNATURES table records all data signatures we verified
--
CREATE TABLE signatures (
binding integer not null, -- Link to bindings table,
-- references bindings.oid.
sig_digest text, -- The digest of the signed message.
origin text, -- String describing who initially fed
-- the signature to gpg (e.g. "email:claws").
sig_time integer, -- Timestamp from the signature.
time integer, -- Time this record was created.
primary key (binding, sig_digest, origin)
);
#+end_src
* GNU extensions to the S2K algorithm
1 octet - S2K Usage: either 254 or 255.
1 octet - S2K Cipher Algo: 0
1 octet - S2K Specifier: 101
3 octets - "GNU"
1 octet - GNU S2K Extension Number.
If such a GNU extension is used neither an IV nor any kind of
checksum is used. The defined GNU S2K Extension Numbers are:
- 1 :: Do not store the secret part at all. No specific data
follows.
- 2 :: A stub to access smartcards. This data follows:
- One octet with the length of the following serial number.
- The serial number. Regardless of what the length octet
indicates no more than 16 octets are stored.
- 3 :: The internal representation of a private key: For v4 keys we
first write 4 octets big endian length of the following
s-expression with the protected or unprotected private key;
for v5 keys this is not necessarily because that length
header is always there. The actual data are N octets of
s-expression. Any protection (including the real S2K) is
part of that data. Note that the public key aparemters are
repeated in th s-expression.
Note that gpg stores the GNU S2K Extension Number internally as an
S2K Specifier with an offset of 1000.
* Format of the OpenPGP TRUST packet
According to RFC4880 (5.10), the trust packet (aka ring trust) is
only used within keyrings and contains data that records the user's
specifications of which key holds trusted introducers. The RFC also
states that the format of this packet is implementation defined and
SHOULD NOT be emitted to output streams or should be ignored on
import. GnuPG uses this packet in several additional ways:
- 1 octet :: Trust-Value (only used by Subtype SIG)
- 1 octet :: Signature-Cache (only used by Subtype SIG; value must
be less than 128)
- 3 octets :: Fixed value: "gpg"
- 1 octet :: Subtype
- 0 :: Signature cache (SIG)
- 1 :: Key source on the primary key (KEY)
- 2 :: Key source on a user id (UID)
- 1 octet :: Key Source; i.e. the origin of the key:
- 0 :: Unknown source.
- 1 :: Public keyserver.
- 2 :: Preferred keyserver.
- 3 :: OpenPGP DANE.
- 4 :: Web Key Directory.
- 5 :: Import from a trusted URL.
- 6 :: Import from a trusted file.
- 7 :: Self generated.
- 4 octets :: Time of last update. This is a four-octet scalar
with the seconds since Epoch.
- 1 octet :: Scalar with the length of the following field.
- N octets :: String with the URL of the source. This may be a
zero-length string.
If the packets contains only two octets a Subtype of 0 is assumed;
this is the only format recognized by GnuPG versions < 2.1.18.
Trust-Value and Signature-Cache must be zero for all subtypes other
than SIG.
* Keyserver helper message format
*This information is obsolete*
(Keyserver helpers have been replaced by dirmngr)
The keyserver may be contacted by a Unix Domain socket or via TCP.
The format of a request is:
#+begin_example
command-tag
"Content-length:" digits
CRLF
#+end_example
Where command-tag is
#+begin_example
NOOP
GET <user-name>
PUT
DELETE <user-name>
#+end_example
The format of a response is:
#+begin_example
"GNUPG/1.0" status-code status-text
"Content-length:" digits
CRLF
#+end_example
followed by <digits> bytes of data
Status codes are:
- 1xx :: Informational - Request received, continuing process
- 2xx :: Success - The action was successfully received, understood,
and accepted
- 4xx :: Client Error - The request contains bad syntax or cannot be
fulfilled
- 5xx :: Server Error - The server failed to fulfill an apparently
valid request
* Object identifiers
OIDs below the GnuPG arc:
#+begin_example
1.3.6.1.4.1.11591.2 GnuPG
1.3.6.1.4.1.11591.2.1 notation
1.3.6.1.4.1.11591.2.1.1 pkaAddress
1.3.6.1.4.1.11591.2.2 X.509 extensions
1.3.6.1.4.1.11591.2.2.1 standaloneCertificate
1.3.6.1.4.1.11591.2.2.2 wellKnownPrivateKey
1.3.6.1.4.1.11591.2.2.10 OpenPGP KDF/KEK parameter
1.3.6.1.4.1.11591.2.3 CMS contentType
1.3.6.1.4.1.11591.2.3.1 OpenPGP keyblock (as octet string)
1.3.6.1.4.1.11591.2.4 LDAP stuff
1.3.6.1.4.1.11591.2.4.1 attributes
1.3.6.1.4.1.11591.2.4.1.1 gpgFingerprint attribute
1.3.6.1.4.1.11591.2.4.1.2 gpgSubFingerprint attribute
1.3.6.1.4.1.11591.2.4.1.3 gpgMailbox attribute
1.3.6.1.4.1.11591.2.4.1.4 gpgSubCertID attribute
1.3.6.1.4.1.11591.2.5 LDAP URL extensions
1.3.6.1.4.1.11591.2.5.1 gpgNtds=1 (auth. with current AD user)
1.3.6.1.4.1.11591.2.6 GnuPG extended key usage
1.3.6.1.4.1.11591.2.6.1 use for certification key
1.3.6.1.4.1.11591.2.6.2 use for signing key
1.3.6.1.4.1.11591.2.6.3 use for encryption key
1.3.6.1.4.1.11591.2.6.4 use for authentication key
1.3.6.1.4.1.11591.2.12242973 invalid encoded OID
#+end_example
The OpenPGP KDF/KEK parameter extension is used to convey additional
info for OpenPGP keys as an X.509 extensions.
* Debug flags
This tables gives the flag values for the --debug option along with
the alternative names used by the components.
| | gpg | gpgsm | agent | scd | dirmngr | g13 | wks |
|-------+---------+---------+---------+---------+---------+---------+---------|
| 1 | packet | x509 | | | x509 | mount | mime |
| 2 | mpi | mpi | mpi | mpi | | | parser |
| 4 | crypto | crypto | crypto | crypto | crypto | crypto | crypto |
| 8 | filter | | | | | | |
| 16 | iobuf | | | | dns | | |
| 32 | memory | memory | memory | memory | memory | memory | memory |
| 64 | cache | cache | cache | cache | cache | | |
| 128 | memstat | memstat | memstat | memstat | memstat | memstat | memstat |
| 256 | trust | | | | | | |
| 512 | hashing | hashing | hashing | hashing | hashing | | |
| 1024 | ipc | ipc | ipc | ipc | ipc | ipc | ipc |
| 2048 | | | | cardio | network | | |
| 4096 | clock | | | reader | | | |
| 8192 | lookup | | | | lookup | | |
| 16384 | extprog | | | | | | extprog |
Description of some debug flags:
- cardio :: Used by scdaemon to trace the APDUs exchange with the
card.
- clock :: Show execution times of certain functions.
- crypto :: Trace crypto operations.
- hashing :: Create files with the hashed data.
- ipc :: Trace the Assuan commands.
- mpi :: Show the values of the MPIs.
- reader :: Used by scdaemon to trace card reader related code. For
example: Open and close reader.
* Miscellaneous notes
** List of useful RFCs and I-D.
- RFC-1423 :: PEM, Part III: Algorithms, Modes, and Identifiers
- RFC-1750 :: Randomness Recommendations for Security
- RFC-1991 :: PGP Message Exchange Formats (obsolete)
- RFC-2144 :: The CAST-128 Encryption Algorithm
- RFC-2253 :: UTF-8 String Representation of Distinguished Names.
- RFC-2279 :: UTF-8, a transformation format of ISO 10646
- RFC-2440 :: OpenPGP (obsolete).
- RFC-3156 :: MIME Security with Pretty Good Privacy (PGP).
- RFC-3447 :: PKCS #1: RSA Cryptography Specifications Version 2.1
- RFC-4880 :: OpenPGP
- RFC-5083 :: CMS - Authenticated-Enveloped-Data
- RFC-5084 :: CMS - AES-GCM
- RFC-5280 :: X.509 PKI Certificate and CRL Profile
- RFC-5480 :: ECC Subject Public Key Information
- RFC-5639 :: ECC Brainpool Standard Curves
- RFC-5652 :: CMS (STD0070)
- RFC-5753 :: ECC in CMS
- RFC-5758 :: CMS - Additional Algorithms for DSA and ECDSA
- RFC-6818 :: Updates to the X.509 PKI Certificate and CRL Profile
- RFC-6960 :: Online Certificate Status Protocol - OCSP
- RFC-8954 :: Online Certificate Status Protocol (OCSP) Nonce Extension
- RFC-8398 :: Internationalized Email Addresses in X.509 Certificates
- RFC-8399 :: Internationalization Updates to RFC 5280
- RFC-8813 :: Clarifications for ECC Subject Public Key
- RFC-5915 :: ECC Private Key Structure
- RFC-5958 :: Asymmetric Key Packages
- RFC-6337 :: ECC in OpenPGP
- RFC-7748 :: Elliptic Curves for Security (X25519 and X448)
- RFC-8410 :: Algorithm Identifiers for Ed25519, Ed448, X25519, and X448
- RFC-7292 :: PKCS #12: Personal Information Exchange Syntax v1.1
- RFC-8351 :: The PKCS #8 EncryptedPrivateKeyInfo Media Type
- RFC-8550 :: S/MIME Version 4.0 Certificate Handling
- RFC-8551 :: S/MIME Version 4.0 Message Specification
- RFC-2634 :: Enhanced Security Services for S/MIME
- RFC-5035 :: Enhanced Security Services (ESS) Update
- RFC-7253 :: The OCB Authenticated-Encryption Algorithm
- draft-koch-openpgp-2015-rfc4880bis :: Updates to RFC-4880
- T6390 :: Notes on use of X25519 in GnuPG (https://dev.gnupg.org/T6390)
** v3 fingerprints
For packet version 3 we calculate the keyids this way:
- RSA :: Low 64 bits of n
- ELGAMAL :: Build a v3 pubkey packet (with CTB 0x99) and
calculate a RMD160 hash value from it. This is used
as the fingerprint and the low 64 bits are the keyid.
** gnupg.org notations
- rem@gnupg.org :: Used by Kleopatra to implement the tag feature.
These tags are used to mark keys for easier
searching and grouping.
** Simplified revocation certificates
Revocation certificates consist only of the signature packet;
"--import" knows how to handle this. The rationale behind it is to
keep them small.
** Documentation on HKP (the http keyserver protocol):
A minimalistic HTTP server on port 11371 recognizes a GET for
/pks/lookup. The standard http URL encoded query parameters are
this (always key=value):
- op=index (like pgp -kv), op=vindex (like pgp -kvv) and op=get (like
pgp -kxa)
- search=<stringlist>. This is a list of words that must occur in the key.
The words are delimited with space, points, @ and so on. The delimiters
are not searched for and the order of the words doesn't matter (but see
next option).
- exact=on. This switch tells the hkp server to only report exact matching
keys back. In this case the order and the "delimiters" are important.
- fingerprint=on. Also reports the fingerprints when used with 'index' or
'vindex'
The keyserver also recognizes http-POSTs to /pks/add. Use this to upload
keys.
A better way to do this would be a request like:
/pks/lookup/<gnupg_formatierte_user_id>?op=<operation>
This can be implemented using Hurd's translator mechanism.
However, I think the whole keyserver stuff has to be re-thought;
I have some ideas and probably create a white paper.
** Algorithm names for the "keygen.algo" prompt
When using a --command-fd controlled key generation or "addkey"
there is way to know the number to enter on the "keygen.algo"
prompt. The displayed numbers are for human reception and may
change with releases. To provide a stable way to enter a desired
algorithm choice the prompt also accepts predefined names for the
algorithms, which will not change.
| Name | No | Description |
|---------+----+---------------------------------|
| rsa+rsa | 1 | RSA and RSA (default) |
| dsa+elg | 2 | DSA and Elgamal |
| dsa | 3 | DSA (sign only) |
| rsa/s | 4 | RSA (sign only) |
| elg | 5 | Elgamal (encrypt only) |
| rsa/e | 6 | RSA (encrypt only) |
| dsa/* | 7 | DSA (set your own capabilities) |
| rsa/* | 8 | RSA (set your own capabilities) |
| ecc+ecc | 9 | ECC and ECC |
| ecc/s | 10 | ECC (sign only) |
| ecc/* | 11 | ECC (set your own capabilities) |
| ecc/e | 12 | ECC (encrypt only) |
| keygrip | 13 | Existing key |
| cardkey | 14 | Existing key from card |
If one of the "foo/*" names are used a "keygen.flags" prompt needs
to be answered as well. Instead of toggling the predefined flags,
it is also possible to set them direct: Use a "=" character
directly followed by a combination of "a" (for authentication), "s"
(for signing), or "c" (for certification).
** extendedKeyUsage and keyUsage in gpgsm
This table describes how the extended KeyUsage masks the KeyUsage.
| ExtKeyUsage | Valid KeyUsages |
|-----------------+------------------|
| serverAuth | digitalSignature |
| | keyEncipherment |
| | keyAgreement |
|-----------------+------------------|
| clientAuth | digitalSignature |
| | keyAgreement |
|-----------------+------------------|
| codeSigning | digitalSignature |
|-----------------+------------------|
| emailProtection | digitalSignature |
| | nonRepudiation |
| | keyEncipherment |
| | keyAgreement |
|-----------------+------------------|
| timeStamping | digitalSignature |
| | nonRepudiation |
|-----------------+------------------|
diff --git a/doc/HACKING b/doc/HACKING
index ec04a2e37..cb7e400fc 100644
--- a/doc/HACKING
+++ b/doc/HACKING
@@ -1,445 +1,445 @@
# HACKING -*- org -*-
#+TITLE: A Hacker's Guide to GnuPG
#+TEXT: Some notes on GnuPG internals
#+STARTUP: showall
#+OPTIONS: ^:{}
# Note: This might be a copy; the original lives in gnupg/doc/HACKING.
* How to contribute
The following stuff explains some basic procedures you need to
follow if you want to contribute code or documentation.
** No more ChangeLog files
Do not modify any of the ChangeLog files in GnuPG. Starting on
December 1st, 2011 we put change information only in the GIT commit
log, and generate a top-level ChangeLog file from logs at "make dist"
time. As such, there are strict requirements on the form of the
commit log messages. The old ChangeLog files have all be renamed to
ChangeLog-2011
** Commit log requirements
Your commit log should always start with a one-line summary, the
second line should be blank, and the remaining lines are usually
ChangeLog-style entries for all affected files. However, it's fine
--- even recommended --- to write a few lines of prose describing the
change, when the summary and ChangeLog entries don't give enough of
the big picture. Omit the leading TABs that you are seeing in a
"real" ChangeLog file, but keep the maximum line length at 72 or
smaller, so that the generated ChangeLog lines, each with its leading
TAB, will not exceed 80 columns. If you want to add text which shall
not be copied to the ChangeLog, separate it by a line consisting of
two dashes at the begin of a line.
The one-line summary usually starts with a keyword to identify the
mainly affected subsystem (that is not the directory). If more than
one keyword is required they are delimited by a comma
(e.g. =scd,w32:=). Commonly found keywords are
- agent :: The gpg-agent component
- build :: Changes to the build system
- ccid :: The CCID driver in scdaemon
- common :: Code in common
- dirmngr :: The dirmngr component
- doc :: Documentation changes
- gpg :: The gpg or gpgv components
- sm :: The gpgsm component (also "gpgsm")
- gpgscm :: The regression test driver
- indent :: Indentation and similar changes
- iobuf :: The IOBUF system in common
- po :: Translations
- scd :: The scdaemon component
- speedo :: Speedo build system specific changes
- ssh :: The ssh-agent part of the agent
- tests :: The regressions tests
- tools :: Other code in tools
- w32 :: Windows related code
- wks :: The web key service tools
- yat2m :: The yat2m tool.
Typo fixes and documentation updates don't need a ChangeLog entry;
thus you would use a commit message like
#+begin_example
doc: Fix typo in a comment
--
#+end_example
The marker line here is important; without it the first line would
appear in the ChangeLog.
If you exceptionally need to have longer lines in a commit log you may
do this after this scissor line:
#+begin_example
# ------------------------ >8 ------------------------
#+end_example
(hash, blank, 24 dashes, blank, scissor, blank, 24 dashes).
Note that such a comment will be removed if the git commit option
=--cleanup=scissor= is used.
** License policy
GnuPG is licensed under the GPLv3+ with some files under a mixed
LGPLv3+/GPLv2+ license. It is thus important, that all contributed
code allows for an update of the license; for example we can't
accept code under the GPLv2(only).
GnuPG used to have a strict policy of requiring copyright
assignments to the FSF. To avoid this major organizational overhead
and to allow inclusion of code, not copyrighted by the FSF, this
policy has been relaxed on 2013-03-29. It is now also possible to
contribute code by asserting that the contribution is in accordance
to the "Libgcrypt Developer's Certificate of Origin" as found in the
file "DCO". (Except for a slight wording change, this DCO is
identical to the one used by the Linux kernel.)
If you want to contribute code or documentation to GnuPG and you
didn't sign a copyright assignment with the FSF in the past, you
need to take these simple steps:
- Decide which mail address you want to use. Please have your real
name in the address and not a pseudonym. Anonymous contributions
can only be done if you find a proxy who certifies for you.
- If your employer or school might claim ownership of code written
by you; you need to talk to them to make sure that you have the
right to contribute under the DCO.
- Send an OpenPGP signed mail to the gnupg-devel@gnupg.org mailing
list from your mail address. Include a copy of the DCO as found
in the official master branch. Insert your name and email address
into the DCO in the same way you want to use it later. Example:
Signed-off-by: Joe R. Hacker <joe@example.org>
(If you really need it, you may perform simple transformations of
the mail address: Replacing "@" by " at " or "." by " dot ".)
- That's it. From now on you only need to add a "Signed-off-by:"
line with your name and mail address to the commit message. It is
recommended to send the patches using a PGP/MIME signed mail. See
below on how to send patches.
** Coding standards
Please follow the GNU coding standards. If you are in doubt consult
the existing code as an example. Do no re-indent code without a
need. If you really need to do it, use a separate commit for such a
change.
- Only certain C99 features may be used (see below); in general
stick to C90.
- Please do not use C++ =//= style comments.
- Do not use comments like:
#+begin_src
if (foo)
/* Now that we know that foo is true we can call bar. */
bar ();
#+end_src
instead write the comment on the if line or before it. You may
also use a block and put the comment inside.
- Please use asterisks on the left of longer comments. This makes
it easier to read without syntax highlighting, on printouts, and
for blind people.
- Try to fit lines into 80 columns.
- Ignore signed/unsigned pointer mismatches
- No arithmetic on void pointers; cast to char* first.
- Do not use
#+begin_src
if ( 42 == foo )
#+end_src
this is harder to read and modern compilers are pretty good in
- detecing accidental assignments. It is also suggested not to
+ detecting accidental assignments. It is also suggested not to
compare to 0 or NULL but to test the value direct or with a '!';
this makes it easier to see that a boolean test is done.
- We use our own printf style functions like =es_printf=, and
=gpgrt_asprintf= (or the =es_asprintf= macro) which implement most
C99 features with the exception of =wchar_t= (which should anyway
not be used). Please use them always and do not resort to those
provided by libc. The rationale for using them is that we know
that the format specifiers work on all platforms and that we do
not need to chase platform dependent bugs. Note also that in
gnupg asprintf is a macro already evaluating to gpgrt_asprintf.
- It is common to have a label named "leave" for a function's
cleanup and return code. This helps with freeing memory and is a
convenient location to set a breakpoint for debugging.
- Always use xfree() instead of free(). If it is not easy to see
that the freed variable is not anymore used, explicitly set the
variable to NULL.
- New code shall in general use xtrymalloc or xtrycalloc and check
for an error (use gpg_error_from_syserror()).
- Init function local variables only if needed so that the compiler
can do a better job in detecting uninitialized variables which may
indicate a problem with the code.
- Never init static or file local variables to 0 to make sure they
end up in BSS.
- Put extra parenthesis around terms with binary operators to make
it clear that the binary operator was indeed intended.
- Use --enable-maintainer-mode with configure so that all suitable
warnings are enabled.
** Variable names
Follow the GNU standards. Here are some conventions you may want to
stick to (do not rename existing "wrong" uses without a good reason).
- err :: This conveys an error code of type =gpg_error_t= which is
compatible to an =int=. To compare such a variable to a
GPG_ERR_ constant, it is necessary to access the value like
this: =gpg_err_code(err)=.
- ec :: This is used for a gpg-error code which has no source part
(=gpg_err_code_t=) and will eventually be used as input to
=gpg_err_make=.
- rc :: Used for all kind of other errors; for example system
calls. The value is not compatible with gpg-error.
*** C99 language features
In GnuPG 2.x, but *not in 1.4* and not in most libraries, a limited
set of C99 features may be used:
- Variadic macros:
: #define foo(a,...) bar(a, __VA_ARGS__)
- The predefined macro =__func__=:
: log_debug ("%s: Problem with foo\n", __func__);
Although we usually make use of the =u16=, =u32=, and =u64= types,
it is also possible to include =<stdint.h>= and use =int16_t=,
=int32_t=, =int64_t=, =uint16_t=, =uint32_t=, and =uint64_t=. But do
not use =int8_t= or =uint8_t=.
** Commit log keywords
- GnuPG-bug-id :: Values are comma or space delimited bug numbers
from bug.gnupg.org pertaining to this commit.
- Debian-bug-id :: Same as above but from the Debian bug tracker.
- CVE-id :: CVE id number pertaining to this commit.
- Regression-due-to :: Commit id of the regression fixed by this commit.
- Fixes-commit :: Commit id this commit fixes.
- Updates-commit :: Commit id this commit updates.
- See-commit :: Commit id of a related commit.
- Reported-by :: Value is a name or mail address of a bug reporte.
- Suggested-by :: Value is a name or mail address of someone how
suggested this change.
- Co-authored-by :: Name or mail address of a co-author
- Some-comments-by :: Name or mail address of the author of
additional comments (commit log or code).
- Proofread-by :: Sometimes used by translation commits.
- Signed-off-by :: Name or mail address of the developer.
- Backported-from-master :: Value is the commit id of the original patch.
- Ported-from-stable :: Value is the commit id of the original patch.
** Sending patches
Submitting patches, and subsequent discussions around them,
happens via the gnupg-devel@gnupg.org public mailing list.
Send your patches to that list, preferably PGP/MIME signed. Make sure
to include a mention of 'gnupg' (or gpgme, libassuan, etc) in the
subject line; the list is used for several different projects.
In general you should send patches only for the master branch; we may
later decide to backport to another branch. Please ask first before
-sending pacthes for another branch.
+sending patches for another branch.
If you're working from the Git repo, here's a suggested workflow:
- Configure git send-email defaults:
: git config format.subjectPrefix 'PATCH gnupg'
: git config sendemail.to gnupg-devel@gnupg.org
(For other sub-projects adjust accordingly)
- hack hack hack
- Commit your changes; group changes into easily-reviewable commit
units, feel free to submit several patches at once.
e.g. if you want to submit a single patch on top of master, do:
: git send-email --annotate -1
e.g. if you have two commits on top of master, do:
: git send-email --annotate --cover-letter -2
(that prompts you for a summary mail to precede your actual patch
mails)
- use Git's --dry-run option to test your setup
* Windows
** How to build an installer for Windows
Your best bet is to use a decent Debian System for development.
You need to install a long list of tools for building. This list
still needs to be compiled. However, the build process will stop
if a tool is missing. GNU make is required (on non GNU systems
often installed as "gmake"). The installer requires a couple of
extra software to be available either as tarballs or as local git
repositories. In case this file here is part of a gnupg-w32-2.*.xz
complete tarball as distributed from the same place as a binary
installer, all such tarballs are already included.
Cd to the GnuPG source directory and use one of one of these
command:
- If sources are included (gnupg-w32-*.tar.xz)
make -f build-aux/speedo.mk WHAT=this installer
- To build from tarballs
make -f build-aux/speedo.mk WHAT=release TARBALLS=TARDIR installer
- To build from local GIT repos
make -f build-aux/speedo.mk WHAT=git TARBALLS=TARDIR installer
Note that also you need to supply tarballs with supporting
libraries even if you build from git. The makefile expects only
the core GnuPG software to be available as local GIT repositories.
speedo.mk has the versions of the tarballs and the branch names of
the git repositories. In case of problems, don't hesitate to ask
on the gnupg-devel mailing for help.
* Debug hints
See the manual for some hints.
* Various information
** Directory Layout
- ./ :: Readme, configure
- ./agent :: Gpg-agent and related tools
- ./doc :: Documentation
- ./g10 :: Gpg program here called gpg2
- ./sm :: Gpgsm program
- ./jnlib :: Not used (formerly used utility functions)
- ./common :: Utility functions
- ./kbx :: Keybox library
- ./scd :: Smartcard daemon
- ./scripts :: Scripts needed by configure and others
- ./dirmngr :: The directory manager
** Detailed Roadmap
This list of files is not up to date!
- g10/gpg.c :: Main module with option parsing and all the stuff you
have to do on startup. Also has the exit handler and
some helper functions.
- g10/parse-packet.c ::
- g10/build-packet.c ::
- g10/free-packet.c :: Parsing and creating of OpenPGP message packets.
- g10/getkey.c :: Key selection code
- g10/pkclist.c :: Build a list of public keys
- g10/skclist.c :: Build a list of secret keys
- g10/keyring.c :: Keyring access functions
- g10/keydb.h ::
- g10/keyid.c :: Helper functions to get the keyid, fingerprint etc.
- g10/trustdb.c :: Web-of-Trust computations
- g10/trustdb.h ::
- g10/tdbdump.c :: Export/import/list the trustdb.gpg
- g10/tdbio.c :: I/O handling for the trustdb.gpg
- g10/tdbio.h ::
- g10/compress.c :: Filter to handle compression
- g10/filter.h :: Declarations for all filter functions
- g10/delkey.c :: Delete a key
- g10/kbnode.c :: Helper for the kbnode_t linked list
- g10/main.h :: Prototypes and some constants
- g10/mainproc.c :: Message processing
- g10/armor.c :: Ascii armor filter
- g10/mdfilter.c :: Filter to calculate hashes
- g10/textfilter.c :: Filter to handle CR/LF and trailing white space
- g10/cipher.c :: En-/Decryption filter
- g10/misc.c :: Utility functions
- g10/options.h :: Structure with all the command line options
and related constants
- g10/openfile.c :: Create/Open Files
- g10/keyserver.h :: Keyserver access dispatcher.
- g10/packet.h :: Definition of OpenPGP structures.
- g10/passphrase.c :: Passphrase handling code
- g10/pubkey-enc.c :: Process a public key encoded packet.
- g10/seckey-cert.c :: Not anymore used
- g10/seskey.c :: Make session keys etc.
- g10/import.c :: Import keys into our key storage.
- g10/export.c :: Export keys to the OpenPGP format.
- g10/sign.c :: Create signature and optionally encrypt.
- g10/plaintext.c :: Process plaintext packets.
- g10/decrypt-data.c :: Decrypt an encrypted data packet
- g10/encrypt.c :: Main encryption driver
- g10/revoke.c :: Create recovation certificates.
- g10/keylist.c :: Print information about OpenPGP keys
- g10/sig-check.c :: Check a signature
- g10/helptext.c :: Show online help texts
- g10/verify.c :: Verify signed data.
- g10/decrypt.c :: Decrypt and verify data.
- g10/keyedit.c :: Edit properties of a key.
- g10/dearmor.c :: Armor utility.
- g10/keygen.c :: Generate a key pair
** Memory allocation
Use only the functions:
- xmalloc
- xmalloc_secure
- xtrymalloc
- xtrymalloc_secure
- xcalloc
- xcalloc_secure
- xtrycalloc
- xtrycalloc_secure
- xrealloc
- xtryrealloc
- xstrdup
- xtrystrdup
- xfree
The *secure versions allocate memory in the secure memory. That is,
swapping out of this memory is avoided and is gets overwritten on
free. Use this for passphrases, session keys and other sensitive
material. This memory set aside for secure memory is linited to a few
k. In general the function don't print a memory message and
terminate the process if there is not enough memory available. The
"try" versions of the functions return NULL instead.
** Logging
TODO
** Option parsing
GnuPG does not use getopt or GNU getopt but functions of it's own.
See util/argparse.c for details. The advantage of these functions is
that it is more easy to display and maintain the help texts for the
options. The same option table is also used to parse resource files.
** What is an IOBUF
This is the data structure used for most I/O of gnupg. It is similar
to System V Streams but much simpler. Because OpenPGP messages are
nested in different ways; the use of such a system has big advantages.
Here is an example, how it works: If the parser sees a packet header
with a partial length, it pushes the block_filter onto the IOBUF to
handle these partial length packets: from now on you don't have to
worry about this. When it sees a compressed packet it pushes the
uncompress filter and the next read byte is one which has already been
uncompressed by this filter. Same goes for enciphered packet,
plaintext packets and so on. The file g10/encode.c might be a good
starting point to see how it is used - actually this is the other way:
constructing messages using pushed filters but it may be easier to
understand.
diff --git a/doc/dirmngr.texi b/doc/dirmngr.texi
index 420340ee3..16d4cd4ab 100644
--- a/doc/dirmngr.texi
+++ b/doc/dirmngr.texi
@@ -1,1365 +1,1365 @@
@c Copyright (C) 2002 Klar"alvdalens Datakonsult AB
@c Copyright (C) 2004, 2005, 2006, 2007 g10 Code GmbH
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking DIRMNGR
@chapter Invoking DIRMNGR
@cindex DIRMNGR command options
@cindex command options
@cindex options, DIRMNGR command
@manpage dirmngr.8
@ifset manverb
.B dirmngr
\- GnuPG's network access daemon
@end ifset
@mansect synopsis
@ifset manverb
.B dirmngr
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@mansect description
Since version 2.1 of GnuPG, @command{dirmngr} takes care of accessing
the OpenPGP keyservers. As with previous versions it is also used as
a server for managing and downloading certificate revocation lists
(CRLs) for X.509 certificates, downloading X.509 certificates, and
providing access to OCSP providers. Dirmngr is invoked internally by
@command{gpg}, @command{gpgsm}, or via the @command{gpg-connect-agent}
tool.
@manpause
@noindent
@xref{Option Index},for an index to @command{DIRMNGR}'s commands and
options.
@mancont
@menu
* Dirmngr Commands:: List of all commands.
* Dirmngr Options:: List of all options.
* Dirmngr Configuration:: Configuration files.
* Dirmngr Signals:: Use of signals.
* Dirmngr Examples:: Some usage examples.
* Dirmngr Protocol:: The protocol dirmngr uses.
@end menu
@node Dirmngr Commands
@section Commands
@mansect commands
Commands are not distinguished from options except for the fact that
only one command is allowed.
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@item --server
@opindex server
Run in server mode and wait for commands on the @code{stdin}. The
default mode is to create a socket and listen for commands there.
This is only used for testing.
@item --daemon
@opindex daemon
Run in background daemon mode and listen for commands on a socket.
This is the way @command{dirmngr} is started on demand by the other
GnuPG components. To force starting @command{dirmngr} it is in
general best to use @code{gpgconf --launch dirmngr}.
@item --supervised
@opindex supervised
Run in the foreground, sending logs to stderr, and listening on file
descriptor 3, which must already be bound to a listening socket. This
option is deprecated and not supported on Windows.
@item --list-crls
@opindex list-crls
List the contents of the CRL cache on @code{stdout}. This is probably
only useful for debugging purposes.
@item --load-crl @var{file}
@opindex load-crl
This command requires a filename as additional argument, and it will
make Dirmngr try to import the CRL in @var{file} into it's cache.
Note, that this is only possible if Dirmngr is able to retrieve the
CA's certificate directly by its own means. In general it is better
to use @code{gpgsm}'s @code{--call-dirmngr loadcrl filename} command
so that @code{gpgsm} can help dirmngr.
@item --fetch-crl @var{url}
@opindex fetch-crl
This command requires an URL as additional argument, and it will make
dirmngr try to retrieve and import the CRL from that @var{url} into
it's cache. This is mainly useful for debugging purposes. The
@command{dirmngr-client} provides the same feature for a running dirmngr.
@item --shutdown
@opindex shutdown
This commands shuts down an running instance of Dirmngr. This command
has currently no effect.
@item --flush
@opindex flush
This command removes all CRLs from Dirmngr's cache. Client requests
will thus trigger reading of fresh CRLs.
@end table
@mansect options
@node Dirmngr Options
@section Option Summary
Note that all long options with the exception of @option{--options}
and @option{--homedir} may also be given in the configuration file
after stripping off the two leading dashes.
@table @gnupgtabopt
@item --options @var{file}
@opindex options
Reads configuration from @var{file} instead of from the default
per-user configuration file. The default configuration file is named
@file{dirmngr.conf} and expected in the home directory.
@item --homedir @var{dir}
@opindex options
Set the name of the home directory to @var{dir}. This option is only
effective when used on the command line. The default is
the directory named @file{.gnupg} directly below the home directory
of the user unless the environment variable @code{GNUPGHOME} has been set
in which case its value will be used. Many kinds of data are stored within
this directory.
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @sc{dirmngr}, such as @option{-vv}.
@item --log-file @var{file}
@opindex log-file
Append all logging output to @var{file}. This is very helpful in
seeing what the agent actually does. Use @file{socket://} to log to
socket.
@item --compatibility-flags @var{flags}
@opindex compatibility-flags
Set compatibility flags to work around certain problems or to emulate
bugs. The @var{flags} are given as a comma separated list of flag
names and are OR-ed together. The special flag "none" clears the list
and allows one to start over with an empty list. To get a list of
available flags the sole word "help" can be used.
@item --faked-system-time @var{epoch}
@opindex faked-system-time
This option is only useful for testing; it sets the system time back or
forth to @var{epoch} which is the number of seconds elapsed since the year
1970. Alternatively @var{epoch} may be given as a full ISO time string
(e.g. "20070924T154812").
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be a
numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
Set debug flags. All flags are or-ed and @var{flags} may be given in
C syntax (e.g., 0x0042) or as a comma separated list of flag names. To
get a list of all supported flags the single word "help" can be used.
This option is only useful for debugging and the behavior may change
at any time without notice.
@item --debug-all
@opindex debug-all
Same as @code{--debug=0xffffffff}
@item --tls-debug @var{level}
@opindex tls-debug
Enable debugging of the TLS layer at @var{level}. The details of the
debug level depend on the used TLS library and are not set in stone.
@item --debug-wait @var{n}
@opindex debug-wait
When running in server mode, wait @var{n} seconds before entering the
actual processing loop and print the pid. This gives time to attach a
debugger.
@item --disable-check-own-socket
@opindex disable-check-own-socket
On some platforms @command{dirmngr} is able to detect the removal of
its socket file and shutdown itself. This option disable this
self-test for debugging purposes.
@item -s
@itemx --sh
@itemx -c
@itemx --csh
@opindex s
@opindex sh
@opindex c
@opindex csh
Format the info output in daemon mode for use with the standard Bourne
shell respective the C-shell. The default is to guess it based on the
environment variable @code{SHELL} which is in almost all cases
sufficient.
@item --force
@opindex force
Enabling this option forces loading of expired CRLs; this is only
useful for debugging.
@item --use-tor
@itemx --no-use-tor
@opindex use-tor
@opindex no-use-tor
The option @option{--use-tor} switches Dirmngr and thus GnuPG into
``Tor mode'' to route all network access via Tor (an anonymity
network). Certain other features are disabled in this mode. The
effect of @option{--use-tor} cannot be overridden by any other command
or even by reloading dirmngr. The use of @option{--no-use-tor}
disables the use of Tor. The default is to use Tor if it is available
on startup or after reloading dirmngr. The test on the availability of
Tor is done by trying to connect to a SOCKS proxy at either port 9050
or 9150; if another type of proxy is listening on one of these ports,
you should use @option{--no-use-tor}.
@item --standard-resolver
@opindex standard-resolver
This option forces the use of the system's standard DNS resolver code.
This is mainly used for debugging. Note that on Windows a standard
resolver is not used and all DNS access will return the error ``Not
Implemented'' if this option is used. Using this together with enabled
Tor mode returns the error ``Not Enabled''.
@item --recursive-resolver
@opindex recursive-resolver
When possible use a recursive resolver instead of a stub resolver.
@item --resolver-timeout @var{n}
@opindex resolver-timeout
Set the timeout for the DNS resolver to N seconds. The default are 30
seconds.
@item --connect-timeout @var{n}
@item --connect-quick-timeout @var{n}
@opindex connect-timeout
@opindex connect-quick-timeout
Set the timeout for HTTP and generic TCP connection attempts to N
seconds. The value set with the quick variant is used when the
@option{--quick} option has been given to certain Assuan commands. The quick
value is capped at the value of the regular connect timeout. The
default values are 15 and 2 seconds. Note that the timeout values are
for each connection attempt; the connection code will attempt to
connect all addresses listed for a server.
@item --listen-backlog @var{n}
@opindex listen-backlog
Set the size of the queue for pending connections. The default is 64.
@item --allow-version-check
@opindex allow-version-check
Allow Dirmngr to connect to @code{https://versions.gnupg.org} to get
the list of current software versions. If this option is enabled
the list is retrieved in case the local
copy does not exist or is older than 5 to 7 days. See the option
@option{--query-swdb} of the command @command{gpgconf} for more
details. Note, that regardless of this option a version check can
always be triggered using this command:
@example
gpg-connect-agent --dirmngr 'loadswdb --force' /bye
@end example
@item --keyserver @var{name}
@opindex keyserver
Use @var{name} as your keyserver. This is the server that @command{gpg}
communicates with to receive keys, send keys, and search for
keys. The format of the @var{name} is a URI:
`scheme:[//]keyservername[:port]' The scheme is the type of keyserver:
"hkp" for the HTTP (or compatible) keyservers or "ldap" for the LDAP
keyservers. Note that your particular installation of GnuPG may have
other keyserver types available as well. Keyserver schemes are
case-insensitive. After the keyserver name, optional keyserver
configuration options may be provided. These are the same as the
@option{--keyserver-options} of @command{gpg}, but apply only to this
particular keyserver.
Some keyservers synchronize with each other, so there is not always a
need to send keys to more than one server. Some keyservers use round
robin DNS to give a different keyserver each time you use it.
If exactly two keyservers are configured and only one is a Tor hidden
service (.onion), Dirmngr selects the keyserver to use depending on
whether Tor is locally running or not. The check for a running Tor is
done for each new connection.
If no keyserver is explicitly configured, dirmngr will use the
built-in default of @code{https://keyserver.ubuntu.com}. To avoid the
use of a default keyserver the value @code{none} can be used.
Windows users with a keyserver running on their Active Directory
may use the short form @code{ldap:///} for @var{name} to access this directory.
For accessing anonymous LDAP keyservers @var{name} is in general just
a @code{ldaps://ldap.example.com}. A BaseDN parameter should never be
specified. If authentication is required things are more complicated
and two methods are available:
The modern method (since version 2.2.28) is to use the very same syntax
as used with the option @option{--ldapserver}. Please see over
there for details; here is an example:
@example
keyserver ldap:ldap.example.com::uid=USERNAME,ou=GnuPG Users,
dc=example,dc=com:PASSWORD::starttls
@end example
The other method is to use a full URL for @var{name}; for example:
@example
keyserver ldaps://ldap.example.com/????bindname=uid=USERNAME
%2Cou=GnuPG%20Users%2Cdc=example%2Cdc=com,password=PASSWORD
@end example
Put this all on one line without any spaces and keep the '%2C'
as given. Replace USERNAME, PASSWORD, and the 'dc' parts
according to the instructions received from your LDAP
administrator. Note that only simple authentication
(i.e., cleartext passwords) is supported and thus using ldaps is
strongly suggested (since 2.2.28 "ldaps" defaults to port 389
and uses STARTTLS). On Windows authentication via AD can be
requested by adding @code{gpgNtds=1} after the fourth question
mark instead of the bindname and password parameter.
@item --nameserver @var{ipaddr}
@opindex nameserver
In ``Tor mode'' Dirmngr uses a public resolver via Tor to resolve DNS
names. If the default public resolver, which is @code{8.8.8.8}, shall
not be used a different one can be given using this option. Note that
a numerical IP address must be given (IPv6 or IPv4) and that no error
checking is done for @var{ipaddr}.
@item --disable-ipv4
@item --disable-ipv6
@opindex disable-ipv4
@opindex disable-ipv6
Disable the use of all IPv4 or IPv6 addresses.
@item --disable-ldap
@opindex disable-ldap
Entirely disables the use of LDAP.
@item --disable-http
@opindex disable-http
Entirely disables the use of HTTP.
@item --ignore-http-dp
@opindex ignore-http-dp
When looking for the location of a CRL, the to be tested certificate
usually contains so called @dfn{CRL Distribution Point} (DP) entries
which are URLs describing the way to access the CRL. The first found DP
entry is used. With this option all entries using the @acronym{HTTP}
scheme are ignored when looking for a suitable DP.
@item --ignore-ldap-dp
@opindex ignore-ldap-dp
This is similar to @option{--ignore-http-dp} but ignores entries using
the @acronym{LDAP} scheme. Both options may be combined resulting in
ignoring DPs entirely.
@item --ignore-ocsp-service-url
@opindex ignore-ocsp-service-url
Ignore all OCSP URLs contained in the certificate. The effect is to
force the use of the default responder.
@item --honor-http-proxy
@opindex honor-http-proxy
If the environment variable @env{http_proxy} has been set, use its
value to access HTTP servers. If on Windows the option is used but
the environment variable is not set, the proxy settings are taken
from the system.
@item --http-proxy @var{host}[:@var{port}]
@opindex http-proxy
@efindex http_proxy
Use @var{host} and @var{port} to access HTTP servers. The use of this
option overrides the environment variable @env{http_proxy} regardless
whether @option{--honor-http-proxy} has been set.
@item --ldap-proxy @var{host}[:@var{port}]
@opindex ldap-proxy
Use @var{host} and @var{port} to connect to LDAP servers. If @var{port}
is omitted, port 389 (standard LDAP port) is used. This overrides any
specified host and port part in a LDAP URL and will also be used if host
and port have been omitted from the URL.
@item --only-ldap-proxy
@opindex only-ldap-proxy
Never use anything else but the LDAP "proxy" as configured with
@option{--ldap-proxy}. Usually @command{dirmngr} tries to use other
configured LDAP server if the connection using the "proxy" failed.
@item --ldapserverlist-file @var{file}
@opindex ldapserverlist-file
Read the list of LDAP servers to consult for CRLs and X.509 certificates from
file instead of the default per-user ldap server list file. The default
value for @var{file} is @file{dirmngr_ldapservers.conf}.
This server list file contains one LDAP server per line in the format
@sc{hostname:port:username:password:base_dn:flags}
Lines starting with a @samp{#} are comments.
Note that as usual all strings entered are expected to be UTF-8 encoded.
Obviously this will lead to problems if the password has originally been
encoded as Latin-1. There is no other solution here than to put such a
password in the binary encoding into the file (i.e., non-ascii characters
won't show up readable).@footnote{The @command{gpgconf} tool might be
helpful for frontends as it enables editing this configuration file using
percent-escaped strings.}
@item --ldapserver @var{spec}
@opindex ldapserver
This is an alternative way to specify LDAP servers for CRL and X.509
certificate retrieval. If this option is used the servers configured
in @file{dirmngr_ldapservers.conf} (or the file given by
@option{--ldapserverlist-file}) are cleared. Note that
@file{dirmngr_ldapservers.conf} is not read again by a reload
signal. However, @option{--ldapserver} options are read again.
@var{spec} is either a proper LDAP URL or a colon delimited list of
the form
@sc{hostname:port:username:password:base_dn:flags:}
with an optional prefix of @code{ldap:} (but without the two slashes
which would turn this into a proper LDAP URL). @sc{flags} is a list
of one or more comma delimited keywords:
@table @code
@item plain
The default: Do not use a TLS secured connection at all; the default
port is 389.
@item starttls
Use STARTTLS to secure the connection; the default port is 389.
@item ldaptls
Tunnel LDAP through a TLS connection; the default port is 636.
@item ntds
On Windows authenticate the LDAP connection using the Active Directory
with the current user.
@item areconly
On Windows use only the A or AAAA record when resolving the LDAP
server name.
@end table
Note that in an URL style specification the scheme @code{ldaps://}
refers to STARTTLS and _not_ to LDAP-over-TLS.
@item --ldaptimeout @var{secs}
@opindex ldaptimeout
Specify the number of seconds to wait for an LDAP query before timing
out. The default are 15 seconds. 0 will never timeout.
@item --add-servers
@opindex add-servers
This option makes dirmngr add any servers it discovers when validating
certificates against CRLs to the internal list of servers to consult for
certificates and CRLs. This option should in general not be used.
This option might be useful when trying to validate a certificate that
has a CRL distribution point that points to a server that is not
already listed in the ldapserverlist. Dirmngr will always go to this
server and try to download the CRL, but chances are high that the
certificate used to sign the CRL is located on the same server. So if
dirmngr doesn't add that new server to list, it will often not be able
to verify the signature of the CRL unless the @code{--add-servers}
option is used.
Caveat emptor: Using this option may enable denial-of-service attacks
and leak search requests to unknown third parties. This is because
arbitrary servers are added to the internal list of LDAP servers which
in turn is used for all unspecific LDAP queries as well as a fallback
for queries which did not return a result.
@item --allow-ocsp
@opindex allow-ocsp
This option enables OCSP support if requested by the client.
OCSP requests are rejected by default because they may violate the
privacy of the user; for example it is possible to track the time when
a user is reading a mail.
@item --ocsp-responder @var{url}
@opindex ocsp-responder
Use @var{url} as the default OCSP Responder if the certificate does
not contain information about an assigned responder. Note, that
@code{--ocsp-signer} must also be set to a valid certificate.
@item --ocsp-signer @var{fpr}|@var{file}
@opindex ocsp-signer
Use the certificate with the fingerprint @var{fpr} to check the
responses of the default OCSP Responder. Alternatively a filename can be
given in which case the response is expected to be signed by one of the
certificates described in that file. Any argument which contains a
slash, dot or tilde is considered a filename. Usual filename expansion
takes place: A tilde at the start followed by a slash is replaced by the
content of @env{HOME}, no slash at start describes a relative filename
which will be searched at the home directory. To make sure that the
@var{file} is searched in the home directory, either prepend the name
with "./" or use a name which contains a dot.
If a response has been signed by a certificate described by these
fingerprints no further check upon the validity of this certificate is
done.
The format of the @var{FILE} is a list of SHA-1 fingerprint, one per
line with optional colons between the bytes. Empty lines and lines
prefix with a hash mark are ignored.
@item --ocsp-max-clock-skew @var{n}
@opindex ocsp-max-clock-skew
The number of seconds a skew between the OCSP responder and them local
clock is accepted. Default is 600 (10 minutes).
@item --ocsp-max-period @var{n}
@opindex ocsp-max-period
Seconds a response is at maximum considered valid after the time given
in the thisUpdate field. Default is 7776000 (90 days).
@item --ocsp-current-period @var{n}
@opindex ocsp-current-period
The number of seconds an OCSP response is considered valid after the
time given in the NEXT_UPDATE datum. Default is 10800 (3 hours).
@item --max-replies @var{n}
@opindex max-replies
Do not return more that @var{n} items in one query. The default is
10.
@item --ignore-cert-extension @var{oid}
@opindex ignore-cert-extension
Add @var{oid} to the list of ignored certificate extensions. The
@var{oid} is expected to be in dotted decimal form, like
@code{2.5.29.3}. This option may be used more than once. Critical
flagged certificate extensions matching one of the OIDs in the list
are treated as if they are actually handled and thus the certificate
won't be rejected due to an unknown critical extension. Use this
option with care because extensions are usually flagged as critical
for a reason.
@item --ignore-crl-extension @var{oid}
@opindex ignore-crl-extension
Add @var{oid} to the list of ignored CRL extensions. The @var{oid} is
expected to be in dotted decimal form. Critical flagged CRL
extensions matching one of the OIDs in the list are treated as if they
are actually handled and thus the certificate won't be rejected due to
an unknown critical extension. Use this option with care because
extensions are usually flagged as critical for a reason.
@item --ignore-cert @var{fpr}|@var{file}
@opindex ignore-cert
Entirely ignore certificates with the fingerprint @var{fpr}. As an
alternative to the fingerprint a filename can be given in which case
all certificates described in that file are ignored. Any argument
which contains a slash, dot or tilde is considered a filename. Usual
filename expansion takes place: A tilde at the start followed by a
slash is replaced by the content of @env{HOME}, no slash at start
describes a relative filename which will be searched at the home
directory. To make sure that the @var{file} is searched in the home
directory, either prepend the name with "./" or use a name which
contains a dot. The format of such a file is a list of SHA-1
fingerprint, one per line with optional colons between the bytes.
Empty lines and lines prefixed with a hash mark are ignored.
This option is useful as a quick workaround to exclude certain
certificates from the system store.
@item --hkp-cacert @var{file}
Use the root certificates in @var{file} for verification of the TLS
certificates used with @code{hkps} (keyserver access over TLS). If
the file is in PEM format a suffix of @code{.pem} is expected for
@var{file}. This option may be given multiple times to add more
root certificates. Tilde expansion is supported.
If no @code{hkp-cacert} directive is present, dirmngr will use the
system CAs.
@end table
@c
@c Dirmngr Configuration
@c
@mansect files
@node Dirmngr Configuration
@section Configuration
Dirmngr makes use of several directories when running in daemon mode:
There are a few configuration files to control the operation of
dirmngr. By default they may all be found in the current home
directory (@pxref{option --homedir}).
@table @file
@item dirmngr.conf
@efindex dirmngr.conf
This is the standard configuration file read by @command{dirmngr} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This file
is also read after a @code{SIGHUP} however not all options will
actually have an effect. This default name may be changed on the
command line (@pxref{option --options}). You should backup this file.
@item /etc/gnupg/trusted-certs
This directory should be filled with certificates of Root CAs you
are trusting in checking the CRLs and signing OCSP Responses.
Usually these are the same certificates you use with the applications
making use of dirmngr. It is expected that each of these certificate
files contain exactly one @acronym{DER} encoded certificate in a file
with the suffix @file{.crt} or @file{.der}. @command{dirmngr} reads
those certificates on startup and when given a SIGHUP. Certificates
which are not readable or do not make up a proper X.509 certificate
are ignored; see the log file for details.
Applications using dirmngr (e.g., gpgsm) can request these
certificates to complete a trust chain in the same way as with the
extra-certs directory (see below).
Note that for OCSP responses the certificate specified using the option
@option{--ocsp-signer} is always considered valid to sign OCSP requests.
@item /etc/gnupg/extra-certs
This directory may contain extra certificates which are preloaded
into the internal cache on startup. Applications using dirmngr (e.g., gpgsm)
can request cached certificates to complete a trust chain.
This is convenient in cases you have a couple intermediate CA certificates
or certificates usually used to sign OCSP responses.
These certificates are first tried before going
out to the net to look for them. These certificates must also be
@acronym{DER} encoded and suffixed with @file{.crt} or @file{.der}.
@item ~/.gnupg/crls.d
This directory is used to store cached CRLs. The @file{crls.d}
part will be created by dirmngr if it does not exists but you need to
make sure that the upper directory exists.
@end table
Several options control the use of trusted certificates for TLS and
CRLs. Here is an Overview on the use and origin of those Root CA
certificates:
@table @asis
@item System
These System root certificates are used by: FIXME
The origin of the system provided certificates depends on the
platform. On Windows all certificates from the Windows System Stores
@code{ROOT} and @code{CA} are used.
On other platforms the certificates are read from the first file found
form this list: @file{/etc/ssl/ca-bundle.pem},
@file{/etc/ssl/certs/ca-certificates.crt},
@file{/etc/pki/tls/cert.pem},
@file{/usr/local/share/certs/ca-root-nss.crt},
@file{/etc/ssl/cert.pem}.
@item GnuPG
The GnuPG specific certificates stored in the directory
@file{/etc/gnupg/trusted-certs} are only used to validate CRLs.
@c Note that dirmngr's VALIDATE command also uses them but that
@c command is anyway only intended for debugging.
@item OpenPGP keyserver
For accessing the OpenPGP keyservers the only certificates used are
those set with the configuration option @option{hkp-cacert}.
@item OpenPGP keyserver pool
This is usually only one certificate read from the file
@file{@value{DATADIR}/gnupg/sks-keyservers.netCA.pem}. If this
certificate exists it is used to access the special keyservers
@code{hkps.pool.sks-keyservers.net} (or @file{hkps://keys.gnupg.net}).
@end table
Please note that @command{gpgsm} accepts Root CA certificates for its
own purposes only if they are listed in its file @file{trustlist.txt}.
@command{dirmngr} does not make use of this list --- except FIXME.
@mansect notes
To be able to see diagnostics it is often useful to put at least the
following lines into the configuration file
@file{~/gnupg/dirmngr.conf}:
@example
log-file ~/dirmngr.log
verbose
@end example
You may want to check the log file to see whether all desired root CA
certificates are correctly loaded.
To be able to perform OCSP requests you probably want to add the line:
@example
allow-ocsp
@end example
To make sure that new options are read or that after the installation
of a new GnuPG versions the right dirmngr version is running, you
should kill an existing dirmngr so that a new instance is started as
needed by the other components:
@example
gpgconf --kill dirmngr
@end example
Direct interfaction with the dirmngr is possible by using the command
@example
gpg-connect-agent --dirmngr
@end example
Enter @code{HELP} at the prompt to see a list of commands and enter
@code{HELP} followed by a command name to get help on that command.
@c
@c Dirmngr Signals
@c
@mansect signals
@node Dirmngr Signals
@section Use of signals
A running @command{dirmngr} may be controlled by signals, i.e., using
the @command{kill} command to send a signal to the process.
Here is a list of supported signals:
@table @gnupgtabopt
@item SIGHUP
@cpindex SIGHUP
This signal flushes all internally cached CRLs as well as any cached
certificates. Then the certificate cache is reinitialized as on
startup. Options are re-read from the configuration file. Instead of
sending this signal it is better to use
@example
gpgconf --reload dirmngr
@end example
@item SIGTERM
@cpindex SIGTERM
Shuts down the process but waits until all current requests are
fulfilled. If the process has received 3 of these signals and requests
are still pending, a shutdown is forced. You may also use
@example
gpgconf --kill dirmngr
@end example
instead of this signal
@item SIGINT
@cpindex SIGINT
Shuts down the process immediately.
@item SIGUSR1
@cpindex SIGUSR1
This prints some caching statistics to the log file.
@end table
@c
@c Examples
@c
@mansect examples
@node Dirmngr Examples
@section Examples
Here is an example on how to show dirmngr's internal table of OpenPGP
keyserver addresses. The output is intended for debugging purposes
and not part of a defined API.
@example
gpg-connect-agent --dirmngr 'keyserver --hosttable' /bye
@end example
To inhibit the use of a particular host you have noticed in one of the
keyserver pools, you may use
@example
gpg-connect-agent --dirmngr 'keyserver --dead pgpkeys.bnd.de' /bye
@end example
The description of the @code{keyserver} command can be printed using
@example
gpg-connect-agent --dirmngr 'help keyserver' /bye
@end example
@c
@c Assuan Protocol
@c
@manpause
@node Dirmngr Protocol
@section Dirmngr's Assuan Protocol
Assuan is the IPC protocol used to access dirmngr. This is a
description of the commands implemented by dirmngr.
@menu
* Dirmngr LOOKUP:: Look up a certificate via LDAP
* Dirmngr ISVALID:: Validate a certificate using a CRL or OCSP.
* Dirmngr CHECKCRL:: Validate a certificate using a CRL.
* Dirmngr CHECKOCSP:: Validate a certificate using OCSP.
* Dirmngr CACHECERT:: Put a certificate into the internal cache.
* Dirmngr VALIDATE:: Validate a certificate for debugging.
@end menu
@node Dirmngr LOOKUP
@subsection Return the certificate(s) found
Lookup certificate. To allow multiple patterns (which are ORed)
quoting is required: Spaces are to be translated into "+" or into
"%20"; obviously this requires that the usual escape quoting rules
are applied. The server responds with:
@example
S: D <DER encoded certificate>
S: END
S: D <second DER encoded certificate>
S: END
S: OK
@end example
In this example 2 certificates are returned. The server may return
any number of certificates; OK will also be returned when no
certificates were found. The dirmngr might return a status line
@example
S: S TRUNCATED <n>
@end example
To indicate that the output was truncated to N items due to a
limitation of the server or by an arbitrary set limit.
The option @option{--url} may be used if instead of a search pattern a
complete URL to the certificate is known:
@example
C: LOOKUP --url CN%3DWerner%20Koch,o%3DIntevation%20GmbH,c%3DDE?userCertificate
@end example
If the option @option{--cache-only} is given, no external lookup is done
so that only certificates from the cache are returned.
With the option @option{--single}, the first and only the first match
will be returned. Unless option @option{--cache-only} is also used, no
local lookup will be done in this case.
@node Dirmngr ISVALID
@subsection Validate a certificate using a CRL or OCSP
@example
ISVALID [--only-ocsp] [--force-default-responder] @var{certid}|@var{certfpr}
@end example
Check whether the certificate described by the @var{certid} has been
revoked. Due to caching, the Dirmngr is able to answer immediately in
most cases.
The @var{certid} is a hex encoded string consisting of two parts,
delimited by a single dot. The first part is the SHA-1 hash of the
issuer name and the second part the serial number.
Alternatively the certificate's SHA-1 fingerprint @var{certfpr} may be
given in which case an OCSP request is done before consulting the CRL.
If the option @option{--only-ocsp} is given, no fallback to a CRL check
will be used. If the option @option{--force-default-responder} is
given, only the default OCSP responder will be used and any other
methods of obtaining an OCSP responder URL won't be used.
@noindent
Common return values are:
@table @code
@item GPG_ERR_NO_ERROR (0)
This is the positive answer: The certificate is not revoked and we have
an up-to-date revocation list for that certificate. If OCSP was used
the responder confirmed that the certificate has not been revoked.
@item GPG_ERR_CERT_REVOKED
This is the negative answer: The certificate has been revoked. Either
it is in a CRL and that list is up to date or an OCSP responder informed
us that it has been revoked.
@item GPG_ERR_NO_CRL_KNOWN
No CRL is known for this certificate or the CRL is not valid or out of
date.
@item GPG_ERR_NO_DATA
The OCSP responder returned an ``unknown'' status. This means that it
is not aware of the certificate's status.
@item GPG_ERR_NOT_SUPPORTED
This is commonly seen if OCSP support has not been enabled in the
configuration.
@end table
If DirMngr has not enough information about the given certificate (which
is the case for not yet cached certificates), it will inquire the
missing data:
@example
S: INQUIRE SENDCERT <CertID>
C: D <DER encoded certificate>
C: END
@end example
A client should be aware that DirMngr may ask for more than one
certificate.
If Dirmngr has a certificate but the signature of the certificate
could not been validated because the root certificate is not known to
dirmngr as trusted, it may ask back to see whether the client trusts
this the root certificate:
@example
S: INQUIRE ISTRUSTED <CertHexfpr>
C: D 1
C: END
@end example
Only this answer will let Dirmngr consider the certificate as valid.
@node Dirmngr CHECKCRL
@subsection Validate a certificate using a CRL
Check whether the certificate with FINGERPRINT (SHA-1 hash of the
entire X.509 certificate blob) is valid or not by consulting the CRL
responsible for this certificate. If the fingerprint has not been
given or the certificate is not known, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match FINGERPRINT) as a binary blob. Processing then
takes place without further interaction; in particular dirmngr tries
to locate other required certificate by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
@noindent
The return code is 0 for success; i.e., the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CHECKOCSP
@subsection Validate a certificate using OCSP
@example
CHECKOCSP [--force-default-responder] [@var{fingerprint}]
@end example
Check whether the certificate with @var{fingerprint} (the SHA-1 hash of
the entire X.509 certificate blob) is valid by consulting the appropriate
OCSP responder. If the fingerprint has not been given or the
certificate is not known by Dirmngr, the function inquires the
certificate using:
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
(which should match @var{fingerprint}) as a binary blob. Processing
then takes place without further interaction; in particular dirmngr
tries to locate other required certificates by its own mechanism which
includes a local certificate store as well as a list of trusted root
certificates.
If the option @option{--force-default-responder} is given, only the
default OCSP responder is used. This option is the per-command variant
of the global option @option{--ignore-ocsp-service-url}.
@noindent
The return code is 0 for success; i.e., the certificate has not been
revoked or one of the usual error codes from libgpg-error.
@node Dirmngr CACHECERT
@subsection Put a certificate into the internal cache
Put a certificate into the internal cache. This command might be
useful if a client knows in advance certificates required for a test and
wants to make sure they get added to the internal cache. It is also
helpful for debugging. To get the actual certificate, this command
immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@noindent
The return code is 0 for success; i.e., the certificate has not been
successfully cached or one of the usual error codes from libgpg-error.
@node Dirmngr VALIDATE
@subsection Validate a certificate for debugging
Validate a certificate using the certificate validation function used
internally by dirmngr. This command is only useful for debugging. To
get the actual certificate, this command immediately inquires it using
@example
S: INQUIRE TARGETCERT
C: D <DER encoded certificate>
C: END
@end example
Thus the caller is expected to return the certificate for the request
as a binary blob.
@mansect see also
@ifset isman
@command{gpgsm}(1),
@command{dirmngr-client}(1)
@end ifset
@include see-also-note.texi
@c
@c !!! UNDER CONSTRUCTION !!!
@c
@c
@c @section Verifying a Certificate
@c
@c There are several ways to request services from Dirmngr. Almost all of
@c them are done using the Assuan protocol. What we describe here is the
@c Assuan command CHECKCRL as used for example by the dirmnr-client tool if
@c invoked as
@c
@c @example
@c dirmngr-client foo.crt
@c @end example
@c
@c This command will send an Assuan request to an already running Dirmngr
@c instance. foo.crt is expected to be a standard X.509 certificate and
@c dirmngr will receive the Assuan command
@c
@c @example
@c CHECKCRL @var [{fingerprint}]
@c @end example
@c
@c @var{fingerprint} is optional and expected to be the SHA-1 has of the
@c DER encoding of the certificate under question. It is to be HEX
@c encoded. The rationale for sending the fingerprint is that it allows
@c dirmngr to reply immediately if it has already cached such a request. If
@c this is not the case and no certificate has been found in dirmngr's
@c internal certificate storage, dirmngr will request the certificate using
@c the Assuan inquiry
@c
@c @example
@c INQUIRE TARGETCERT
@c @end example
@c
@c The caller (in our example dirmngr-client) is then expected to return
@c the certificate for the request (which should match @var{fingerprint})
@c as a binary blob.
@c
@c Dirmngr now passes control to @code{crl_cache_cert_isvalid}. This
@c function checks whether a CRL item exists for target certificate. These
@c CRL items are kept in a database of already loaded and verified CRLs.
@c This mechanism is called the CRL cache. Obviously timestamps are kept
@c there with each item to cope with the expiration date of the CRL. The
@c possible return values are: @code{0} to indicate that a valid CRL is
@c available for the certificate and the certificate itself is not listed
@c in this CRL, @code{GPG_ERR_CERT_REVOKED} to indicate that the certificate is
@c listed in the CRL or @code{GPG_ERR_NO_CRL_KNOWN} in cases where no CRL or no
@c information is available. The first two codes are immediately returned to
@c the caller and the processing of this request has been done.
@c
@c Only the @code{GPG_ERR_NO_CRL_KNOWN} needs more attention: Dirmngr now
@c calls @code{clr_cache_reload_crl} and if this succeeds calls
@c @code{crl_cache_cert_isvald) once more. All further errors are
@c immediately returned to the caller.
@c
@c @code{crl_cache_reload_crl} is the actual heart of the CRL management.
@c It locates the corresponding CRL for the target certificate, reads and
@c verifies this CRL and stores it in the CRL cache. It works like this:
@c
@c * Loop over all crlDPs in the target certificate.
@c * If the crlDP is invalid immediately terminate the loop.
@c * Loop over all names in the current crlDP.
@c * If the URL scheme is unknown or not enabled
@c (--ignore-http-dp, --ignore-ldap-dp) continues with
@c the next name.
@c * @code{crl_fetch} is called to actually retrieve the CRL.
@c In case of problems this name is ignore and we continue with
@c the next name. Note that @code{crl_fetch} does only return
@c a descriptor for the CRL for further reading so does the CRL
@c does not yet end up in memory.
@c * @code{crl_cache_insert} is called with that descriptor to
@c actually read the CRL into the cache. See below for a
@c description of this function. If there is any error (e.g., read
@c problem, CRL not correctly signed or verification of signature
@c not possible), this descriptor is rejected and we continue
@c with the next name. If the CRL has been successfully loaded,
@c the loop is terminated.
@c * If no crlDP has been found in the previous loop use a default CRL.
@c Note, that if any crlDP has been found but loading of the CRL failed,
@c this condition is not true.
@c * Try to load a CRL from all configured servers (ldapservers.conf)
@c in turn. The first server returning a CRL is used.
@c * @code(crl_cache_insert) is then used to actually insert the CRL
@c into the cache. If this failed we give up immediately without
@c checking the rest of the servers from the first step.
@c * Ready.
@c
@c
@c The @code{crl_cache_insert} function takes care of reading the bulk of
@c the CRL, parsing it and checking the signature. It works like this: A
@c new database file is created using a temporary file name. The CRL
@c parsing machinery is started and all items of the CRL are put into
@c this database file. At the end the issuer certificate of the CRL
@c needs to be retrieved. Three cases are to be distinguished:
@c
@c a) An authorityKeyIdentifier with an issuer and serialno exits: The
@c certificate is retrieved using @code{find_cert_bysn}. If
@c the certificate is in the certificate cache, it is directly
@c returned. Then the requester (i.e., the client who requested the
@c CRL check) is asked via the Assuan inquiry ``SENDCERT'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested issuer and seriano (This is
@c needed because the LDAP layer may return several certificates as
@c LDAP as no standard way to retrieve by serial number).
@c
@c b) An authorityKeyIdentifier with a key ID exists: The certificate is
@c retrieved using @code{find_cert_bysubject}. If the certificate is
@c in the certificate cache, it is directly returned. Then the
@c requester is asked via the Assuan inquiry ``SENDCERT_SKI'' whether
@c he can provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject and key ID.
@c
@c c) No authorityKeyIdentifier exits: The certificate is retrieved
@c using @code{find_cert_bysubject} without the key ID argument. If
@c the certificate is in the certificate cache the first one with a
@c matching subject is directly returned. Then the requester is
@c asked via the Assuan inquiry ``SENDCERT'' and an exact
@c specification of the subject whether he can
@c provide this certificate. If this succeed the returned
@c certificate gets cached and returned. Note, that dirmngr does not
@c verify in any way whether the expected certificate is returned.
@c It is in the interest of the client to return a useful certificate
@c as otherwise the service request will fail due to a bad signature.
@c The last way to get the certificate is by looking it up at
@c external resources. This is done using the @code{ca_cert_fetch}
@c and @code{fetch_next_ksba_cert} and comparing the returned
@c certificate to match the requested subject; the first certificate
@c with a matching subject is then returned.
@c
@c If no certificate was found, the function returns with the error
@c GPG_ERR_MISSING_CERT. Now the signature is verified. If this fails,
@c the error is returned. On success the @code{validate_cert_chain} is
@c used to verify that the certificate is actually valid.
@c
@c Here we may encounter a recursive situation:
@c @code{validate_cert_chain} needs to look at other certificates and
@c also at CRLs to check whether these other certificates and well, the
@c CRL issuer certificate itself are not revoked. FIXME: We need to make
@c sure that @code{validate_cert_chain} does not try to lookup the CRL we
@c are currently processing. This would be a catch-22 and may indicate a
@c broken PKI. However, due to overlapping expiring times and imprecise
@c clocks this may actually happen.
@c
@c For historical reasons the Assuan command ISVALID is a bit different
@c to CHECKCRL but this is mainly due to different calling conventions.
-@c In the end the same fucntionality is used, albeit hidden by a couple
+@c In the end the same functionality is used, albeit hidden by a couple
@c of indirection and argument and result code mangling. It furthere
@c ingetrages OCSP checking depending on options are the way it is
@c called. GPGSM still uses this command but might eventually switch over
@c to CHECKCRL and CHECKOCSP so that ISVALID can be retired.
@c
@c
@c @section Validating a certificate
@c
@c We describe here how the internal function @code{validate_cert_chain}
@c works. Note that mainly testing purposes this functionality may be
@c called directly using @cmd{dirmngr-client --validate @file{foo.crt}}.
@c
@c The function takes the target certificate and a mode argument as
@c parameters and returns an error code and optionally the closes
@c expiration time of all certificates in the chain.
@c
@c We first check that the certificate may be used for the requested
@c purpose (i.e., OCSP or CRL signing). If this is not the case
@c GPG_ERR_WRONG_KEY_USAGE is returned.
@c
@c The next step is to find the trust anchor (root certificate) and to
@c assemble the chain in memory: Starting with the target certificate,
@c the expiration time is checked against the current date, unknown
@c critical extensions are detected and certificate policies are matched
@c (We only allow 2.289.9.9 but I have no clue about that OID and from
@c where I got it - it does not even seem to be assigned - debug cruft?).
@c
@c Now if this certificate is a self-signed one, we have reached the
@c trust anchor. In this case we check that the signature is good, the
@c certificate is allowed to act as a CA, that it is a trusted one (by
@c checking whether it is has been put into the trusted-certs
@c configuration directory) and finally prepend into to our list
@c representing the certificate chain. This steps ends then.
@c
@c If it is not a self-signed certificate, we check that the chain won't
@c get too long (current limit is 100), if this is the case we terminate
@c with the error GPG_ERR_BAD_CERT_CHAIN.
@c
@c Now the issuer's certificate is looked up: If an
@c authorityKeyIdentifier is available, this one is used to locate the
@c certificate either using issuer and serialnumber or subject DN
@c (i.e., the issuer's DN) and the keyID. The functions
@c @code{find_cert_bysn) and @code{find_cert_bysubject} are used
@c respectively. The have already been described above under the
@c description of @code{crl_cache_insert}. If no certificate was found
@c or with no authorityKeyIdentifier, only the cache is consulted using
@c @code{get_cert_bysubject}. The latter is done under the assumption
@c that a matching certificate has explicitly been put into the
@c certificate cache. If the issuer's certificate could not be found,
@c the validation terminates with the error code @code{GPG_ERR_MISSING_CERT}.
@c
@c If the issuer's certificate has been found, the signature of the
@c actual certificate is checked and in case this fails the error
@c #code{GPG_ERR_BAD_CERT_CHAIN} is returned. If the signature checks out, the
@c maximum chain length of the issuing certificate is checked as well as
@c the capability of the certificate (i.e., whether he may be used for
@c certificate signing). Then the certificate is prepended to our list
@c representing the certificate chain. Finally the loop is continued now
@c with the issuer's certificate as the current certificate.
@c
@c After the end of the loop and if no error as been encountered
@c (i.e., the certificate chain has been assempled correctly), a check is
@c done whether any certificate expired or a critical policy has not been
@c met. In any of these cases the validation terminates with an
@c appropriate error.
@c
@c Finally the function @code{check_revocations} is called to verify no
@c certificate in the assempled chain has been revoked: This is an
@c recursive process because a CRL has to be checked for each certificate
@c in the chain except for the root certificate, of which we already know
@c that it is trusted and we avoid checking a CRL here due to common
@c setup problems and the assumption that a revoked root certificate has
@c been removed from the list of trusted certificates.
@c
@c
@c
@c
@c @section Looking up certificates through LDAP.
@c
@c This describes the LDAP layer to retrieve certificates.
@c the functions @code{ca_cert_fetch} and @code{fetch_next_ksba_cert} are
@c used for this. The first one starts a search and the second one is
@c used to retrieve certificate after certificate.
@c
diff --git a/doc/examples/common.conf b/doc/examples/common.conf
index d58c43c3b..365fc4e8f 100644
--- a/doc/examples/common.conf
+++ b/doc/examples/common.conf
@@ -1,22 +1,22 @@
# common.conf - common defaults for all components.
#
# This file may provide defaults as well as options which needs to be
# synchronized between components. As usual this file is read from
# the system wide config directory (e.g. /etc/gnupg/common.conf) as
# well as from the home directory (e.g. ~/.gnupg.common.conf).
# Uncomment to enable the use if the keybox daemon (keyboxd) by gpg
# and gpgsm.
#use-keyboxd
-# For testing ist is somethimes useful to use a different binary
-# of keybox. This option can be used to speicify this.
+# For testing it is sometimes useful to use a different binary
+# of keybox. This option can be used to specify this.
#keyboxd-program /foo/bar/keyboxd
# For the daemons (gpg-agent, scdaemon, dirmngr, keyboxd) it is often
# useful to define a shared logging destination. This is either the
# standard logging socket (socket://) or a tcp server (tcp://ip:port).
# If a file name is given the name of the component is internally
# appended.
#log-file socket://
diff --git a/doc/examples/trustlist.txt b/doc/examples/trustlist.txt
index 4d5724275..59fb729d7 100644
--- a/doc/examples/trustlist.txt
+++ b/doc/examples/trustlist.txt
@@ -1,66 +1,66 @@
# This is the global list of trusted keys. Comment lines, like this
# one, as well as empty lines are ignored. Lines have a length limit
# but this is not serious limitation as the format of the entries is
# fixed and checked by gpg-agent. A non-comment line starts with
-# optional white space, followed by the SHA-1 fingerpint in hex,
+# optional white space, followed by the SHA-1 fingerprint in hex,
# optionally followed by a flag character which my either be 'P', 'S'
# or '*'. This file will be read by gpg-agent if no local trustlist
# is available or if the statement "include-default" is used in the
# local list. You should give the gpg-agent(s) a HUP after editing
# this file.
#Serial number: 32D18D
# Issuer: /CN=6R-Ca 1:PN/NameDistinguisher=1/O=RegulierungsbehÈorde
# fÈur Telekommunikation und Post/C=DE
EA:8D:99:DD:36:AA:2D:07:1A:3C:7B:69:00:9E:51:B9:4A:2E:E7:60 S
#Serial number: 00C48C8D
# Issuer: /CN=7R-CA 1:PN/NameDistinguisher=1/O=RegulierungsbehÈorde
# fÈur Telekommunikation und Post/C=DE
DB:45:3D:1B:B0:1A:F3:23:10:6B:DE:D0:09:61:57:AA:F4:25:E0:5B S
#Serial number: 01
# Issuer: /CN=8R-CA 1:PN/O=Regulierungsbehörde für
# Telekommunikation und Post/C=DE
42:6A:F6:78:30:E9:CE:24:5B:EF:41:A2:C1:A8:51:DA:C5:0A:6D:F5 S
#Serial number: 02
# Issuer: /CN=9R-CA 1:PN/O=Regulierungsbehörde für
# Telekommunikation und Post/C=DE
75:9A:4A:CE:7C:DA:7E:89:1B:B2:72:4B:E3:76:EA:47:3A:96:97:24 S
#Serial number: 2A
# Issuer: /CN=10R-CA 1:PN/O=Bundesnetzagentur/C=DE
31:C9:D2:E6:31:4D:0B:CC:2C:1A:45:00:A6:6B:97:98:27:18:8E:CD S
#Serial number: 2D
# Issuer: /CN=11R-CA 1:PN/O=Bundesnetzagentur/C=DE
A0:8B:DF:3B:AA:EE:3F:9D:64:6C:47:81:23:21:D4:A6:18:81:67:1D S
# S/N: 0139
# Issuer: /CN=12R-CA 1:PN/O=Bundesnetzagentur/C=DE
44:7E:D4:E3:9A:D7:92:E2:07:FA:53:1A:2E:F5:B8:02:5B:47:57:B0 de
# S/N: 013C
# Issuer: /CN=13R-CA 1:PN/O=Bundesnetzagentur/C=DE
AC:A7:BE:45:1F:A6:BF:09:F2:D1:3F:08:7B:BC:EB:7F:46:A2:CC:8A de
# S/N: 00B3963E0E6C2D65125853E970665402E5
# Issuer: /CN=S-TRUST Qualified Root CA 2008-001:PN
# /O=Deutscher Sparkassen Verlag GmbH/L=Stuttgart/C=DE
C9:2F:E6:50:DB:32:59:E0:CE:65:55:F3:8C:76:E0:B8:A8:FE:A3:CA S
# S/N: 00C4216083F35C54F67B09A80C3C55FE7D
# Issuer: /CN=S-TRUST Qualified Root CA 2008-002:PN
# /O=Deutscher Sparkassen Verlag GmbH/L=Stuttgart/C=DE
D5:C7:50:F2:FE:4E:EE:D7:C7:B1:E4:13:7B:FB:54:84:3A:7D:97:9B S
#Serial number: 00
# Issuer: /CN=CA Cert Signing Authority/OU=http:\x2f\x2fwww.
# cacert.org/O=Root CA/EMail=support@cacert.org
13:5C:EC:36:F4:9C:B8:E9:3B:1A:B2:70:CD:80:88:46:76:CE:8F:33 S
diff --git a/doc/gnupg.texi b/doc/gnupg.texi
index 78d4669da..11883ae98 100644
--- a/doc/gnupg.texi
+++ b/doc/gnupg.texi
@@ -1,241 +1,241 @@
\input texinfo @c -*-texinfo-*-
@c %**start of header
@setfilename gnupg.info
@include defs.inc
@settitle Using the GNU Privacy Guard
@c A couple of macros with no effect on texinfo
@c but used by the yat2m processor.
@macro manpage {a}
@end macro
@macro mansect {a}
@end macro
@macro manpause
@end macro
@macro mancont
@end macro
@c Create a separate index for command line options.
@defcodeindex op
-@c Create an index vor environment variables and files.
+@c Create an index for environment variables and files.
@defcodeindex ef
@c Merge the function index into the concept index.
@syncodeindex fn cp
@c Merge the variable index into the concept index.
@syncodeindex vr cp
@c Merge the keystroke index into the concept index.
@syncodeindex ky cp
@c Merge the program index into the concept index.
@syncodeindex pg cp
@c Merge the data type index into the concept index.
@syncodeindex tp cp
@c %**end of header
@copying
This is the @cite{The GNU Privacy Guard Manual} (version
@value{VERSION}, @value{UPDATED-MONTH}).
@iftex
Published by The GnuPG Project@*
@url{https://gnupg.org}@*
(or @url{http://ic6au7wa3f6naxjq.onion})
@end iftex
@copyright{} 2002, 2004, 2005, 2006, 2007, 2010 Free Software Foundation, Inc.@*
@copyright{} 2013, 2014, 2015 Werner Koch.@*
@copyright{} 2015, 2016, 2017 g10 Code GmbH.
@quotation
Permission is granted to copy, distribute and/or modify this document
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. The text of the license can be found in the
section entitled ``Copying''.
@end quotation
@end copying
@dircategory GNU Utilities
@direntry
* gpg2: (gnupg). OpenPGP encryption and signing tool.
* gpgsm: (gnupg). S/MIME encryption and signing tool.
* gpg-agent: (gnupg). The secret key daemon.
* dirmngr: (gnupg). X.509 CRL and OCSP server.
* dirmngr-client: (gnupg). X.509 CRL and OCSP client.
@end direntry
@c
@c Printing stuff taken from gcc.
@c
@macro gnupgtabopt{body}
@code{\body\}
@end macro
@macro gnupgoptlist{body}
@smallexample
\body\
@end smallexample
@end macro
@c Makeinfo handles the above macro OK, TeX needs manual line breaks;
@c they get lost at some point in handling the macro. But if @macro is
@c used here rather than @alias, it produces double line breaks.
@iftex
@alias gol = *
@end iftex
@ifnottex
@macro gol
@end macro
@end ifnottex
@c
@c Titlepage
@c
@setchapternewpage odd
@titlepage
@title Using the GNU Privacy Guard
@subtitle Version @value{VERSION}
@subtitle @value{UPDATED-MONTH}
@sp 3
@image{gnupg-logo,,,The GnuPG Logo}
@sp 3
@author The GnuPG Project (@url{https://gnupg.org})
@page
@vskip 0pt plus 1filll
@insertcopying
@end titlepage
@ifnothtml
@summarycontents
@contents
@page
@end ifnothtml
@ifhtml
@center @image{gnupg-logo-tr,6cm,,The GnuPG Logo}
@end ifhtml
@ifnottex
@node Top
@top
@insertcopying
This manual documents how to use the GNU Privacy Guard system as well as
the administration and the architecture.
@end ifnottex
@menu
* Installation:: A short installation guide.
* Invoking GPG-AGENT:: How to launch the secret key daemon.
* Invoking DIRMNGR:: How to launch the CRL and OCSP daemon.
* Invoking GPG:: Using the OpenPGP protocol.
* Invoking GPGSM:: Using the S/MIME protocol.
* Invoking SCDAEMON:: How to handle Smartcards.
* Specify a User ID:: How to Specify a User Id.
* Trust Values:: How GnuPG displays trust values.
* Smart Card Tool:: Tool to administrate smart cards.
* Helper Tools:: Description of small helper tools.
* Web Key Service:: Tools for the Web Key Service.
* Howtos:: How to do certain things.
* System Notes:: Notes pertaining to certain OSes.
* Debugging:: How to solve problems.
* Copying:: GNU General Public License says
how you can copy and share GnuPG.
* Contributors:: People who have contributed to GnuPG.
* Glossary:: Short description of terms used.
* Option Index:: Index to command line options.
* Environment Index:: Index to environment variables and files.
* Index:: Index of concepts and symbol names.
@end menu
@ifhtml
@page
@summarycontents
@contents
@end ifhtml
@include instguide.texi
@include gpg-agent.texi
@include dirmngr.texi
@include gpg.texi
@include gpgsm.texi
@include scdaemon.texi
@node Specify a User ID
@chapter How to Specify a User Id
@anchor{how-to-specify-a-user-id}
@include specify-user-id.texi
@node Trust Values
@chapter Trust Values
@anchor{trust-values}
@cindex trust values
@include trust-values.texi
@include gpg-card.texi
@include tools.texi
@include wks.texi
@include howtos.texi
@include sysnotes.texi
@include debugging.texi
@include gpl.texi
@include contrib.texi
@c ---------------------------------------------------------------------
@c Indexes
@c ---------------------------------------------------------------------
@include glossary.texi
@node Option Index
@unnumbered Option Index
@printindex op
@node Environment Index
@unnumbered Environment Variable and File Index
@printindex ef
@node Index
@unnumbered Index
@printindex cp
@c ---------------------------------------------------------------------
@c Epilogue
@c ---------------------------------------------------------------------
@c @node History
@c @unnumbered History
@c
@c Here are the notices from the old dirmngr manual:
@c
@c @itemize
@c @item Using DirMngr, 2002, Steffen Hansen, Klar"alvdalens Datakonsult AB.
@c @item Using DirMngr, 2004, 2005, 2006, 2008 Werner Koch, g10 Code GmbH.
@c @end itemize
@c
@bye
diff --git a/doc/gpg-card.texi b/doc/gpg-card.texi
index 6458598bd..612a202c5 100644
--- a/doc/gpg-card.texi
+++ b/doc/gpg-card.texi
@@ -1,895 +1,895 @@
@c card-tool.texi - man page for gpg-card-tool
@c Copyright (C) 2019 g10 Code GmbH
@c This is part of the GnuPG manual.
@c For copying conditions, see the file GnuPG.texi.
@include defs.inc
@node Smart Card Tool
@chapter Smart Card Tool
GnuPG comes with a tool to administrate smart cards and USB tokens.
This tool is an enhanced version of the @option{--edit-key} command
available with @command{gpg}.
@menu
* gpg-card:: Administrate smart cards.
@end menu
@c
@c GPG-CARD-TOOL
@c
@manpage gpg-card.1
@node gpg-card
@section Administrate smart cards.
@ifset manverb
.B gpg-card
\- Administrate Smart Cards
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-card
.RI [ options ]
.br
.B gpg-card
.RI [ options ]
.I command
.RI {
.B --
.I command
.RI }
@end ifset
@mansect description
The @command{gpg-card} is used to administrate smart cards and USB
tokens. It provides a superset of features from @command{gpg
--card-edit} an can be considered a frontend to @command{scdaemon}
which is a daemon started by @command{gpg-agent} to handle smart
cards.
If @command{gpg-card} is invoked without commands an interactive
mode is used.
If @command{gpg-card} is invoked with one or more commands the
same commands as available in the interactive mode are run from the
command line. These commands need to be delimited with a double-dash.
If a double-dash or a shell specific character is required as part of
a command the entire command needs to be put in quotes. If one of
those commands returns an error the remaining commands are not anymore
run unless the command was prefixed with a single dash.
A list of commands is available by using the command @code{help} and a
brief description of each command is printed by using @code{help CMD}.
See the section COMMANDS for a full description.
See the NOTES sections for instructions pertaining to specific cards
or card applications.
@mansect options
@noindent
@command{gpg-card} understands these options:
@table @gnupgtabopt
@item --with-colons
@opindex with-colons
This option has currently no effect.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}. This
program returns only the status messages SUCCESS or FAILURE which are
helpful when the caller uses a double fork approach and can't easily
get the return code of the process.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --quiet
@opindex quiet
Disable almost all informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent if it has not yet been started and its
service is required. This option is mostly useful on machines where
the connection to gpg-agent has been redirected to another machines.
@item --no-history
@opindex --no-history
In interactive mode the command line history is usually saved and
restored to and from a file below the GnuPG home directory. This
option inhibits the use of that file.
@item --agent-program @var{file}
@opindex agent-program
Specify the agent program to be started if none is running. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}.
@item --gpg-program @var{file}
@opindex gpg-program
Specify a non-default gpg binary to be used by certain commands.
@item --gpgsm-program @var{file}
@opindex gpgsm-program
Specify a non-default gpgsm binary to be used by certain commands.
@item --chuid @var{uid}
@opindex chuid
Change the current user to @var{uid} which may either be a number or a
name. This can be used from the root account to run gpg-card for
another user. If @var{uid} is not the current UID a standard PATH is
set and the envvar GNUPGHOME is unset. To override the latter the
option @option{--homedir} can be used. This option has only an effect
when used on the command line. This option has currently no effect at
all on Windows.
@end table
@mansect commands
@noindent
@command{gpg-card} understands the following commands, which have
options of their own. The pseudo-option @samp{--} can be used to
separate command options from arguments; if this pseudo option is used
on the command line the entire command with options and arguments must
be quoted, so that it is not mixed up with the @samp{--} as used on
the command line to separate commands. Note that a short online help
is available for all commands by prefixing them with ``help''.
Command completion in the interactive mode is also supported.
@table @gnupgtabopt
@item AUTHENTICATE [--setkey] [--raw] [< @var{file}]|@var{key}]
@itemx AUTH
@opindex authenticate
Authenticate to the card. Perform a mutual authentication either by
reading the key from @var{file} or by taking it from the command line
as @var{key}. Without the option @option{--raw} the key is expected
to be hex encoded. To install a new administration key
@option{--setkey} is used; this requires a prior authentication with
the old key. This is used with PIV cards.
@item CAFPR [--clear] N
@opindex cafpr
Change the CA fingerprint number N of an OpenPGP card. N must be in the
range 1 to 3. The option @option{--clear} clears the specified
CA fingerprint N or all of them if N is 0 or not given.
@item FACTORY-RESET
@opindex factory-reset
Do a complete reset of some OpenPGP and PIV cards. This command
deletes all data and keys and resets the PINs to their default. Don't
worry, you need to confirm before the command proceeds.
@item FETCH
@opindex fetch
Retrieve a key using the URL data object of an OpenPGP card or if that
is missing using the stored fingerprint.
@item FORCESIG
@opindex forcesig
Toggle the forcesig flag of an OpenPGP card.
@item GENERATE [--force] [--algo=@var{algo}@{+@var{algo2}@}] @var{keyref}
@opindex generate
Create a new key on a card. Use @option{--force} to overwrite an
existing key. Use "help" for @var{algo} to get a list of known
algorithms. For OpenPGP cards several algos may be given. Note that
the OpenPGP key generation is done interactively unless
@option{--algo} or @var{keyref} are given.
@item KDF-SETUP
@opindex kdf-setup
Prepare the OpenPGP card KDF feature for this card.
@item LANG [--clear]
@opindex lang
Change the language info for the card. This info can be used by
applications for a personalized greeting. Up to 4 two-digit language
identifiers can be entered as a preference. The option
@option{--clear} removes all identifiers. GnuPG does not use this
info.
@item LIST [--cards] [--apps] [--info] [--no-key-lookup] [@var{n}] [@var{app}]
@itemx L
@opindex list
This command reads all information from the current card and display
them in a human readable format. The first section shows generic
information vaialable for all cards. The next section shows
information pertaining to keys which depend on the actual card and
application.
With @var{n} given select and list the n-th card;
with @var{app} also given select that application.
To select an @var{app} on the current card use "-" for @var{n}.
The serial number of the card may be used instead of @var{n}.
The option @option{--cards} lists the serial numbers of available
cards. The option @option{--apps} lists all card applications. The
option @option{--info} selects a card and prints its serial number.
The option @option{--no-key-lookup} suppresses the listing of matching
OpenPGP or X.509 keys.
@item LOGIN [--clear] [< @var{file}]
@opindex login
Set the login data object of OpenPGP cards. If @var{file} is given
the data is is read from that file. This allows one to store binary data
in the login field. The option @option{--clear} deletes the login
data object.
@item NAME [--clear]
@opindex name
Set the name field of an OpenPGP card. With option @option{--clear}
the stored name is cleared off the card.
@item PASSWD [--reset|--nullpin] [@var{pinref}]
@opindex passwd
Change or unblock the PINs. Note that in interactive mode and without
a @var{pinref} a menu is presented for certain cards." In
non-interactive mode and without a @var{pinref} a default value i used
for these cards. The option @option{--reset} is used with TCOS cards
to reset the PIN using the PUK or vice versa; the option
@var{--nullpin} is used for these cards to set the initial PIN.
@item PRIVATEDO [--clear] @var{n} [< @var{file}]
@opindex privatedo
Change the private data object @var{n} of an OpenPGP card. @var{n}
must be in the range 1 to 4. If @var{file} is given the data is is
read from that file. The option @option{--clear} clears the data.
@item QUIT
@itemx Q
@opindex quit
@opindex q
Stop processing and terminate @command{gpg-card}.
@item READCERT [--openpgp] @var{certref} > @var{file}
@opindex readcert
Read the certificate for key @var{certref} and store it in @var{file}.
With option @option{--openpgp} an OpenPGP keyblock wrapped in a
dedicated CMS content type (OID=1.3.6.1.4.1.11591.2.3.1) is expected
and extracted to @var{file}. Note that for current OpenPGP cards a
certificate may only be available at the @var{certref} "OPENPGP.3".
@item RESET
@opindex reset
Send a reset to the card daemon.
@item SALUTATION [--clear]
@itemx SALUT
@opindex salutation
@opindex salut
Change the salutation info for the card. This info can be used by
applications for a personalized greeting. The option @option{--clear}
removes this data object. GnuPG does not use this info.
@item UIF @var{N} [on|off|permanent]
@opindex uif
Change the User Interaction Flag. That flags tells whether the
confirmation button of a token shall be used. @var{n} must in the
range 1 to 3. "permanent" is the same as "on" but the flag can't be
-changed anmore.
+changed anymore.
@item UNBLOCK
@opindex unblock
Unblock a PIN using a PUK or Reset Code. Note that OpenPGP cards
prior to version 2 can't use this; instead the @command{PASSWD} can be
used to set a new PIN.
@item URL [--clear]
@opindex url
Set the URL data object of an OpenPGP card. That data object can be
used by by @command{gpg}'s @option{--fetch} command to retrieve the
full public key. The option @option{--clear} deletes the content of
that data object.
@item VERIFY [@var{chvid}]
@opindex verify
Verify the PIN identified by @var{chvid} or the default PIN.
@item WRITECERT @var{certref} < @var{file}
@itemx WRITECERT --openpgp @var{certref} [< @var{file}|@var{fpr}]
@itemx WRITECERT --clear @var{certref}
@opindex writecert
Write a certificate to the card under the id @var{certref}. The
option @option{--clear} removes the certificate from the card. The
option @option{--openpgp} expects an OpenPGP keyblock and stores it
encapsulated in a CMS container; the keyblock is taken from @var{file}
or directly from the OpenPGP key identified by fingerprint @var{fpr}.
@item WRITEKEY [--force] @var{keyref} @var{keygrip}
@opindex writekey
Write a private key object identified by @var{keygrip} to the card
under the id @var{keyref}. Option @option{--force} allows overwriting
an existing key.
@item CHECKKEYS [--ondisk] [--delete-clear-copy] [--delete-protected-copy]
@opindex checkkeys
Print a list of keys noticed on all inserted cards. With
@option{--ondisk} only smartcard keys with a copy on disk are listed.
With @option{--delete-clear-copy} copies of smartcard keys stored on
disk without any protection will be deleted. With
@option{--delete-protected-copy} password protected copies of
smartcard keys stored on disk will be deleted.
This command creates missing shadow keys. The delete options print
the status of the keys before they are deleted.
The format of the output is:
@table @var
@item Serial number
A hex-string with the serial number of the card.
@item Type
This gives the type of the card's application. For example "OpenPGP"
or "PIV".
@item Keygrip
A hex-string identifying a key.
@item Keyref
The application slot where the key is stored on the card. For example
"OpenPGP.1"
@item Status
The status of the key. The most common value is "shadowed" for a key
where only the public key along with the card's serial number is
stored on the disk. The value "clear" indicates that a copy of the
card's key is stored unprotected on disk. The value "protected"
indicated that a copy of the car's key is stored on disk but is
protected by a password. The value "error" may also be shown if there
was a problem reading information from the card.
@end table
@item YUBIKEY @var{cmd} @var{args}
@opindex yubikey
Various commands pertaining to Yubikey tokens with @var{cmd} being:
@table @var
@item LIST
List supported and enabled Yubikey applications.
@item ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]
@itemx DISABLE
Enable or disable the specified or all applications on the
given interface.
@end table
@end table
@mansect notes (OpenPGP)
The support for OpenPGP cards in @command{gpg-card} is not yet
complete. For missing features, please continue to use @code{gpg
--card-edit}.
@mansect notes (PIV)
@noindent
GnuPG has support for PIV cards (``Personal Identity Verification''
as specified by NIST Special Publication 800-73-4). This section
describes how to initialize (personalize) a fresh Yubikey token
featuring the PIV application (requires Yubikey-5). We assume that
the credentials have not yet been changed and thus are:
@table @asis
@item Authentication key
This is a 24 byte key described by the hex string @*
@code{010203040506070801020304050607080102030405060708}.
@item PIV Application PIN
This is the string @code{123456}.
@item PIN Unblocking Key
This is the string @code{12345678}.
@end table
See the example section on how to change these defaults. For
production use it is important to use secure values for them. Note that
the Authentication Key is not queried via the usual Pinentry dialog
but needs to be entered manually or read from a file. The use of a
dedicated machine to personalize tokens is strongly suggested.
To see what is on the card, the command @code{list} can be given. We
will use the interactive mode in the following (the string
@emph{gpg/card>} is the prompt). An example output for a fresh card
is:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: D2760001240102010006090746250000
Application type .: OpenPGP
Version ..........: 2.1
[...]
@end example
It can be seen by the ``Application type'' line that GnuPG selected
the OpenPGP application of the Yubikey. This is because GnuPG assigns
the highest priority to the OpenPGP application. To use the PIV
application of the Yubikey several methods can be used:
With a Yubikey 5 or later the OpenPGP application on the Yubikey can
be disabled:
@example
gpg/card> yubikey disable all opgp
gpg/card> yubikey list
Application USB NFC
-----------------------
OTP yes yes
U2F yes yes
OPGP no no
PIV yes no
OATH yes yes
FIDO2 yes yes
gpg/card> reset
@end example
The @code{reset} is required so that the GnuPG system rereads the
card. Note that disabled applications keep all their data and can at
any time be re-enabled (use @kbd{help yubikey}).
Another option, which works for all Yubikey versions, is to disable
the support for OpenPGP cards in scdaemon. This is done by adding the
line
@smallexample
disable-application openpgp
@end smallexample
to @file{~/.gnupg/scdaemon.conf} and by restarting scdaemon, either by
killing the process or by using @kbd{gpgconf --kill scdaemon}. Finally
the default order in which card applications are tried by scdaemon can
be changed. For example to prefer PIV over OpenPGP it is sufficient
to add
@smallexample
application-priority piv
@end smallexample
to @file{~/.gnupg/scdaemon.conf} and to restart @command{scdaemon}.
This has an effect only on tokens which support both, PIV and OpenPGP,
but does not hamper the use of OpenPGP only tokens.
With one of these methods employed the @code{list} command of
@command{gpg-card} shows this:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - 3 -
PIV authentication: [none]
keyref .....: PIV.9A
Card authenticat. : [none]
keyref .....: PIV.9E
Digital signature : [none]
keyref .....: PIV.9C
Key management ...: [none]
keyref .....: PIV.9D
@end example
In case several tokens are plugged into the computer, gpg-card will
show only one. To show another token the number of the token (0, 1,
2, ...) can be given as an argument to the @code{list} command. The
command @kbd{list --cards} prints a list of all inserted tokens.
Note that the ``Displayed s/n'' is printed on the token and also
shown in Pinentry prompts asking for the PIN. The four standard key
slots are always shown, if other key slots are initialized they are
shown as well. The @emph{PIV authentication} key (internal reference
@emph{PIV.9A}) is used to authenticate the card and the card holder.
The use of the associated private key is protected by the Application
PIN which needs to be provided once and the key can the be used until
the card is reset or removed from the reader or USB port. GnuPG uses
this key with its @emph{Secure Shell} support. The @emph{Card
authentication} key (@emph{PIV.9E}) is also known as the CAK and used
to support physical access applications. The private key is not
protected by a PIN and can thus immediately be used. The @emph{Digital
signature} key (@emph{PIV.9C}) is used to digitally sign documents.
The use of the associated private key is protected by the Application
PIN which needs to be provided for each signing operation. The
@emph{Key management} key (@emph{PIV.9D}) is used for encryption. The
use of the associated private key is protected by the Application PIN
which needs to be provided only once so that decryption operations can
then be done until the card is reset or removed from the reader or USB
port.
We now generate three of the four keys. Note that GnuPG does
currently not use the the @emph{Card authentication} key; however,
that key is mandatory by the PIV standard and thus we create it too.
Key generation requires that we authenticate to the card. This can be
done either on the command line (which would reveal the key):
@example
gpg/card> auth 010203040506070801020304050607080102030405060708
@end example
or by reading the key from a file. That file needs to consist of one
LF terminated line with the hex encoded key (as above):
@example
gpg/card> auth < myauth.key
@end example
As usual @samp{help auth} gives help for this command. An error
message is printed if a non-matching key is used. The authentication
is valid until a reset of the card or until the card is removed from
the reader or the USB port. Note that that in non-interactive mode
the @samp{<} needs to be quoted so that the shell does not interpret
it as a its own redirection symbol.
@noindent
Here are the actual commands to generate the keys:
@example
gpg/card> generate --algo=nistp384 PIV.9A
PIV card no. yk-9074625 detected
gpg/card> generate --algo=nistp256 PIV.9E
PIV card no. yk-9074625 detected
gpg/card> generate --algo=rsa2048 PIV.9C
PIV card no. yk-9074625 detected
@end example
If a key has already been created for one of the slots an error will
be printed; to create a new key anyway the option @samp{--force} can be
used. Note that only the private and public keys have been created
but no certificates are stored in the key slots. In fact, GnuPG uses
its own non-standard method to store just the public key in place of
the certificate. Other application will not be able to make use
these keys until @command{gpgsm} or another tool has been used to
create and store the respective certificates. Let us see what the
list command now shows:
@example
gpg/card> list
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - 3 -
PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E
keyref .....: PIV.9A (auth)
algorithm ..: nistp384
Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C
keyref .....: PIV.9E (auth)
algorithm ..: nistp256
Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED
keyref .....: PIV.9C (sign,cert)
algorithm ..: rsa2048
Key management ...: [none]
keyref .....: PIV.9D
@end example
The primary information for each key is the @emph{keygrip}, a 40 byte
hex-string identifying the key. This keygrip is a unique identifier
for the specific parameters of a key. It is used by
@command{gpg-agent} and other parts of GnuPG to associate a private
key to its protocol specific certificate format (X.509, OpenPGP, or
SecureShell). Below the keygrip the key reference along with the key
usage capabilities are show. Finally the algorithm is printed in the
format used by @command {gpg}. At that point no other information is
shown because for these new keys gpg won't be able to find matching
certificates.
Although we could have created the @emph{Key management} key also with
the generate command, we will create that key off-card so that a
backup exists. To accomplish this a key needs to be created with
either @command{gpg} or @command{gpgsm} or imported in one of these
tools. In our example we create a self-signed X.509 certificate (exit
the gpg-card tool, first):
@example
$ gpgsm --gen-key -o encr.crt
(1) RSA
(2) Existing key
(3) Existing key from card
Your selection? 1
What keysize do you want? (3072) 2048
Requested keysize is 2048 bits
Possible actions for a RSA key:
(1) sign, encrypt
(2) sign
(3) encrypt
Your selection? 3
Enter the X.509 subject name: CN=Encryption key for yk-9074625,O=example,C=DE
Enter email addresses (end with an empty line):
> otto@@example.net
>
Enter DNS names (optional; end with an empty line):
>
Enter URIs (optional; end with an empty line):
>
Create self-signed certificate? (y/N) y
These parameters are used:
Key-Type: RSA
Key-Length: 2048
Key-Usage: encrypt
Serial: random
Name-DN: CN=Encryption key for yk-9074625,O=example,C=DE
Name-Email: otto@@example.net
Proceed with creation? (y/N)
Now creating self-signed certificate. This may take a while ...
gpgsm: about to sign the certificate for key: &34798AAFE0A7565088101CC4AE31C5C8C74461CB
gpgsm: certificate created
Ready.
$ gpgsm --import encr.crt
gpgsm: certificate imported
gpgsm: total number processed: 1
gpgsm: imported: 1
@end example
Note the last step which imported the created certificate. If you
you instead created a certificate signing request (CSR) instead of a
self-signed certificate and sent this off to a CA you would do the
same import step with the certificate received from the CA. Take note
of the keygrip (prefixed with an ampersand) as shown during the
certificate creation or listed it again using @samp{gpgsm
--with-keygrip -k otto@@example.net}. Now to move the key and
certificate to the card start @command{gpg-card} again and enter:
@example
gpg/card> writekey PIV.9D 34798AAFE0A7565088101CC4AE31C5C8C74461CB
gpg/card> writecert PIV.9D < encr.crt
@end example
If you entered a passphrase to protect the private key, you will be
asked for it via the Pinentry prompt. On success the key and the
certificate has been written to the card and a @code{list} command
shows:
@example
[...]
Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB
keyref .....: PIV.9D (encr)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Encryption key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
@end example
In case the same key (identified by the keygrip) has been used for
several certificates you will see several ``used for'' parts. With
this the encryption key is now fully functional and can be used to
decrypt messages encrypted to this certificate. @sc{Take care:} the
original key is still stored on-disk and should be moved to a backup
medium. This can simply be done by copying the file
@file{34798AAFE0A7565088101CC4AE31C5C8C74461CB.key} from the directory
@file{~/.gnupg/private-keys-v1.d/} to the backup medium and deleting
the file at its original place.
The final example is to create a self-signed certificate for digital
signatures. Leave @command{gpg-card} using @code{quit} or by pressing
Control-D and use gpgsm:
@example
$ gpgsm --learn
$ gpgsm --gen-key -o sign.crt
Please select what kind of key you want:
(1) RSA
(2) Existing key
(3) Existing key from card
Your selection? 3
Serial number of the card: FF020001008A77C1
Available keys:
(1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384
(2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256
(3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048
(4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048
Your selection? 3
Possible actions for a RSA key:
(1) sign, encrypt
(2) sign
(3) encrypt
Your selection? 2
Enter the X.509 subject name: CN=Signing key for yk-9074625,O=example,C=DE
Enter email addresses (end with an empty line):
> otto@@example.net
>
Enter DNS names (optional; end with an empty line):
>
Enter URIs (optional; end with an empty line):
>
Create self-signed certificate? (y/N)
These parameters are used:
Key-Type: card:PIV.9C
Key-Length: 1024
Key-Usage: sign
Serial: random
Name-DN: CN=Signing key for yk-9074625,O=example,C=DE
Name-Email: otto@@example.net
Proceed with creation? (y/N) y
Now creating self-signed certificate. This may take a while ...
gpgsm: about to sign the certificate for key: &32A6C6FAFCB8421878608AAB452D5470DD3223ED
gpgsm: certificate created
Ready.
$ gpgsm --import sign.crt
gpgsm: certificate imported
gpgsm: total number processed: 1
gpgsm: imported: 1
@end example
The use of @samp{gpgsm --learn} is currently necessary so that
gpg-agent knows what keys are available on the card. The need for
this command will eventually be removed. The remaining commands are
similar to the creation of an on-disk key. However, here we select
the @samp{Digital signature} key. During the creation process you
will be asked for the Application PIN of the card. The final step is
to write the certificate to the card using @command{gpg-card}:
@example
gpg/card> writecert PIV.9C < sign.crt
@end example
By running list again we will see the fully initialized card:
@example
Reader ...........: 1050:0407:X:0
Card type ........: yubikey
Card firmware ....: 5.1.2
Serial number ....: FF020001008A77C1
Application type .: PIV
Version ..........: 1.0
Displayed s/n ....: yk-9074625
PIN usage policy .: app-pin
PIN retry counter : - [verified] -
PIV authentication: 213D1825FDE0F8240CB4E4229F01AF90AC658C2E
keyref .....: PIV.9A (auth)
algorithm ..: nistp384
Card authenticat. : 7A53E6CFFE7220A0E646B4632EE29E5A7104499C
keyref .....: PIV.9E (auth)
algorithm ..: nistp256
Digital signature : 32A6C6FAFCB8421878608AAB452D5470DD3223ED
keyref .....: PIV.9C (sign,cert)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Signing key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
Key management ...: 34798AAFE0A7565088101CC4AE31C5C8C74461CB
keyref .....: PIV.9D (encr)
algorithm ..: rsa2048
used for ...: X.509
user id ..: CN=Encryption key for yk-9074625,O=example,C=DE
user id ..: <otto@@example.net>
@end example
It is now possible to sign and to encrypt with this card using gpgsm
and to use the @samp{PIV authentication} key with ssh:
@example
$ ssh-add -l
384 SHA256:0qnJ0Y0ehWxKcx2frLfEljf6GCdlO55OZed5HqGHsaU cardno:yk-9074625 (ECDSA)
@end example
As usual use ssh-add with the uppercase @samp{-L} to list the public
ssh key. To use the certificates with Thunderbird or Mozilla, please
consult the Scute manual for details.
If you want to use the same PIV keys also for OpenPGP (for example on
a Yubikey to avoid switching between OpenPGP and PIV), this is also
possible:
@example
$ gpgsm --learn
$ gpg --full-gen-key
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
(14) Existing key from card
Your selection? 14
Serial number of the card: FF020001008A77C1
Available keys:
(1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 (auth)
(2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 (auth)
(3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 (cert,sign)
(4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 (encr)
Your selection? 3
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
GnuPG needs to construct a user ID to identify your key.
Real name:
Email address: otto@@example.net
Comment:
You selected this USER-ID:
"otto@@example.net"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
gpg: key C3AFA9ED971BB365 marked as ultimately trusted
gpg: revocation certificate stored as '[...]D971BB365.rev'
public and secret key created and signed.
Note that this key cannot be used for encryption. You may want to use
the command "--edit-key" to generate a subkey for this purpose.
pub rsa2048 2019-04-04 [SC]
7F899AE2FB73159DD68A1B20C3AFA9ED971BB365
uid otto@@example.net
@end example
Note that you will be asked two times to enter the PIN of your PIV
card. If you run @command{gpg} in @option{--expert} mode you will
also ge given the option to change the usage flags of the key. The next
typescript shows how to add the encryption subkey:
@example
$ gpg --edit-key 7F899AE2FB73159DD68A1B20C3AFA9ED971BB365
Secret key is available.
sec rsa2048/C3AFA9ED971BB365
created: 2019-04-04 expires: never usage: SC
card-no: FF020001008A77C1
trust: ultimate validity: ultimate
[ultimate] (1). otto@@example.net
gpg> addkey
Secret parts of primary key are stored on-card.
Please select what kind of key you want:
(3) DSA (sign only)
(4) RSA (sign only)
(5) Elgamal (encrypt only)
(6) RSA (encrypt only)
(14) Existing key from card
Your selection? 14
Serial number of the card: FF020001008A77C1
Available keys:
(1) 213D1825FDE0F8240CB4E4229F01AF90AC658C2E PIV.9A nistp384 (auth)
(2) 7A53E6CFFE7220A0E646B4632EE29E5A7104499C PIV.9E nistp256 (auth)
(3) 32A6C6FAFCB8421878608AAB452D5470DD3223ED PIV.9C rsa2048 (cert,sign)
(4) 34798AAFE0A7565088101CC4AE31C5C8C74461CB PIV.9D rsa2048 (encr)
Your selection? 4
Please specify how long the key should be valid.
0 = key does not expire
<n> = key expires in n days
<n>w = key expires in n weeks
<n>m = key expires in n months
<n>y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
Really create? (y/N) y
sec rsa2048/C3AFA9ED971BB365
created: 2019-04-04 expires: never usage: SC
card-no: FF020001008A77C1
trust: ultimate validity: ultimate
ssb rsa2048/7067860A98FCE6E1
created: 2019-04-04 expires: never usage: E
card-no: FF020001008A77C1
[ultimate] (1). otto@@example.net
gpg> save
@end example
Now you can use your PIV card also with @command{gpg}.
@c @mansect examples
@mansect see also
@ifset isman
@command{scdaemon}(1)
@end ifset
diff --git a/doc/gpg.texi b/doc/gpg.texi
index 446189b4b..02131da75 100644
--- a/doc/gpg.texi
+++ b/doc/gpg.texi
@@ -1,4804 +1,4804 @@
@c Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
@c 2008, 2009, 2010 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file gnupg.texi.
@include defs.inc
@node Invoking GPG
@chapter Invoking GPG
@cindex GPG command options
@cindex command options
@cindex options, GPG command
@c Begin standard stuff
@ifclear gpgtwohack
@manpage gpg.1
@ifset manverb
.B gpg
\- OpenPGP encryption and signing tool
@end ifset
@mansect synopsis
@ifset manverb
.B gpg
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@end ifclear
@c End standard stuff
@c Begin gpg2 hack stuff
@ifset gpgtwohack
@manpage gpg2.1
@ifset manverb
.B gpg2
\- OpenPGP encryption and signing tool
@end ifset
@mansect synopsis
@ifset manverb
.B gpg2
.RB [ \-\-homedir
.IR dir ]
.RB [ \-\-options
.IR file ]
.RI [ options ]
.I command
.RI [ args ]
@end ifset
@end ifset
@c End gpg2 hack stuff
@mansect description
@command{@gpgname} is the OpenPGP part of the GNU Privacy Guard (GnuPG). It
is a tool to provide digital encryption and signing services using the
OpenPGP standard. @command{@gpgname} features complete key management and
all the bells and whistles you would expect from a full OpenPGP
implementation.
There are two main versions of GnuPG: GnuPG 1.x and GnuPG 2.x. GnuPG
2.x supports modern encryption algorithms and thus should be preferred
over GnuPG 1.x. You only need to use GnuPG 1.x if your platform
doesn't support GnuPG 2.x, or you need support for some features that
GnuPG 2.x has deprecated, e.g., decrypting data created with PGP-2
keys.
@ifclear gpgtwohack
If you are looking for version 1 of GnuPG, you may find that version
installed under the name @command{gpg1}.
@end ifclear
@ifset gpgtwohack
In contrast to the standalone command @command{gpg} from GnuPG 1.x,
the 2.x version is commonly installed under the name
@command{@gpgname}.
@end ifset
@manpause
@xref{Option Index}, for an index to @command{@gpgname}'s commands and options.
@mancont
@menu
* GPG Commands:: List of all commands.
* GPG Options:: List of all options.
* GPG Configuration:: Configuration files.
* GPG Examples:: Some usage examples.
Developer information:
* Unattended Usage of GPG:: Using @command{gpg} from other programs.
@end menu
@c * GPG Protocol:: The protocol the server mode uses.
@c *******************************************
@c *************** ****************
@c *************** COMMANDS ****************
@c *************** ****************
@c *******************************************
@mansect commands
@node GPG Commands
@section Commands
Commands are not distinguished from options except for the fact that
only one command is allowed. Generally speaking, irrelevant options
are silently ignored, and may not be checked for correctness.
@command{@gpgname} may be run with no commands. In this case it will
print a warning perform a reasonable action depending on the type of
file it is given as input (an encrypted message is decrypted, a
signature is verified, a file containing keys is listed, etc.).
If you run into any problems, please add the option @option{--verbose}
to the invocation to see more diagnostics.
@menu
* General GPG Commands:: Commands not specific to the functionality.
* Operational GPG Commands:: Commands to select the type of operation.
* OpenPGP Key Management:: How to manage your keys.
@end menu
@c *******************************************
@c ********** GENERAL COMMANDS *************
@c *******************************************
@node General GPG Commands
@subsection Commands not specific to the function
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you
cannot abbreviate this command.
@item --help
@itemx -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot arbitrarily abbreviate this command
(though you can use its short form @option{-h}).
@item --warranty
@opindex warranty
Print warranty information.
@item --dump-options
@opindex dump-options
Print a list of all available options and commands. Note that you cannot
abbreviate this command.
@end table
@c *******************************************
@c ******** OPERATIONAL COMMANDS ***********
@c *******************************************
@node Operational GPG Commands
@subsection Commands to select the type of operation
@table @gnupgtabopt
@item --sign
@itemx -s
@opindex sign
Sign a message. This command may be combined with @option{--encrypt}
(to sign and encrypt a message), @option{--symmetric} (to sign and
symmetrically encrypt a message), or both @option{--encrypt} and
@option{--symmetric} (to sign and encrypt a message that can be
decrypted using a secret key or a passphrase). The signing key is
chosen by default or can be set explicitly using the
@option{--local-user} and @option{--default-key} options.
@item --clear-sign
@opindex clear-sign
@itemx --clearsign
@opindex clearsign
Make a cleartext signature. The content in a cleartext signature is
readable without any special software. OpenPGP software is only needed
to verify the signature. cleartext signatures may modify end-of-line
whitespace for platform independence and are not intended to be
reversible. The signing key is chosen by default or can be set
explicitly using the @option{--local-user} and @option{--default-key}
options.
@item --detach-sign
@itemx -b
@opindex detach-sign
Make a detached signature.
@item --encrypt
@itemx -e
@opindex encrypt
Encrypt data to one or more public keys. This command may be combined
with @option{--sign} (to sign and encrypt a message),
@option{--symmetric} (to encrypt a message that can be decrypted using a
secret key or a passphrase), or @option{--sign} and
@option{--symmetric} together (for a signed message that can be
decrypted using a secret key or a passphrase). @option{--recipient}
and related options specify which public keys to use for encryption.
@item --symmetric
@itemx -c
@opindex symmetric
Encrypt with a symmetric cipher using a passphrase. The default
symmetric cipher used is @value{GPGSYMENCALGO}, but may be chosen with the
@option{--cipher-algo} option. This command may be combined with
@option{--sign} (for a signed and symmetrically encrypted message),
@option{--encrypt} (for a message that may be decrypted via a secret key
or a passphrase), or @option{--sign} and @option{--encrypt} together
(for a signed message that may be decrypted via a secret key or a
passphrase). @command{@gpgname} caches the passphrase used for
symmetric encryption so that a decrypt operation may not require that
the user needs to enter the passphrase. The option
@option{--no-symkey-cache} can be used to disable this feature.
@item --store
@opindex store
Store only (make a simple literal data packet).
@item --decrypt
@itemx -d
@opindex decrypt
Decrypt the file given on the command line (or STDIN if no file
is specified) and write it to STDOUT (or the file specified with
@option{--output}). If the decrypted file is signed, the signature is also
verified. This command differs from the default operation, as it never
writes to the filename which is included in the file and it rejects
files that don't begin with an encrypted message.
@item --verify
@opindex verify
Assume that the first argument is a signed file and verify it without
generating any output. With no arguments, the signature packet is
read from STDIN. If only one argument is given, the specified file is
expected to include a complete signature.
With more than one argument, the first argument should specify a file
with a detached signature and the remaining files should contain the
signed data. To read the signed data from STDIN, use @samp{-} as the
second filename. For security reasons, a detached signature will not
read the signed material from STDIN if not explicitly specified.
Note: If the option @option{--batch} is not used, @command{@gpgname}
may assume that a single argument is a file with a detached signature,
and it will try to find a matching data file by stripping certain
suffixes. Using this historical feature to verify a detached
signature is strongly discouraged; you should always specify the data file
explicitly.
Note: When verifying a cleartext signature, @command{@gpgname} verifies
only what makes up the cleartext signed data and not any extra data
outside of the cleartext signature or the header lines directly following
the dash marker line. The option @code{--output} may be used to write
out the actual signed data, but there are other pitfalls with this
format as well. It is suggested to avoid cleartext signatures in
favor of detached signatures.
Note: To check whether a file was signed by a certain key the option
@option{--assert-signer} can be used. As an alternative the
@command{gpgv} tool can be used. @command{gpgv} is designed to
compare signed data against a list of trusted keys and returns with
success only for a good signature. It has its own manual page.
@item --multifile
@opindex multifile
This modifies certain other commands to accept multiple files for
processing on the command line or read from STDIN with each filename on
a separate line. This allows for many files to be processed at
once. @option{--multifile} may currently be used along with
@option{--verify}, @option{--encrypt}, and @option{--decrypt}. Note that
@option{--multifile --verify} may not be used with detached signatures.
@item --verify-files
@opindex verify-files
Identical to @option{--multifile --verify}.
@item --encrypt-files
@opindex encrypt-files
Identical to @option{--multifile --encrypt}.
@item --decrypt-files
@opindex decrypt-files
Identical to @option{--multifile --decrypt}.
@item --list-keys
@itemx -k
@itemx --list-public-keys
@opindex list-keys
List the specified keys. If no keys are specified, then all keys from
the configured public keyrings are listed.
Never use the output of this command in scripts or other programs.
The output is intended only for humans and its format is likely to
change. The @option{--with-colons} option emits the output in a
stable, machine-parseable format, which is intended for use by scripts
and other programs.
@item --list-secret-keys
@itemx -K
@opindex list-secret-keys
List the specified secret keys. If no keys are specified, then all
known secret keys are listed. A @code{#} after the initial tags
@code{sec} or @code{ssb} means that the secret key or subkey is
currently not usable. We also say that this key has been taken
offline (for example, a primary key can be taken offline by exporting
the key using the command @option{--export-secret-subkeys}). A
@code{>} after these tags indicate that the key is stored on a
smartcard. See also @option{--list-keys}.
@item --check-signatures
@opindex check-signatures
@itemx --check-sigs
@opindex check-sigs
Same as @option{--list-keys}, but the key signatures are verified and
listed too. Note that for performance reasons the revocation status
of a signing key is not shown. This command has the same effect as
using @option{--list-keys} with @option{--with-sig-check}.
The status of the verification is indicated by a flag directly
following the "sig" tag (and thus before the flags described below. A
"!" indicates that the signature has been successfully verified, a "-"
denotes a bad signature and a "%" is used if an error occurred while
checking the signature (e.g., a non supported algorithm). Signatures
where the public key is not available are not listed; to see their
keyids the command @option{--list-sigs} can be used.
For each signature listed, there are several flags in between the
signature status flag and keyid. These flags give additional
information about each key signature. From left to right, they are
the numbers 1--3 for certificate check level (see
@option{--ask-cert-level}), "L" for a local or non-exportable
signature (see @option{--lsign-key}), "R" for a nonRevocable signature
(see the @option{--edit-key} command "nrsign"), "P" for a signature
that contains a policy URL (see @option{--cert-policy-url}), "N" for a
signature that contains a notation (see @option{--cert-notation}), "X"
for an eXpired signature (see @option{--ask-cert-expire}), and the
numbers 1--9 or "T" for 10 and above to indicate trust signature levels
(see the @option{--edit-key} command "tsign").
@item --locate-keys
@itemx --locate-external-keys
@opindex locate-keys
@opindex locate-external-keys
Locate the keys given as arguments. This command basically uses the
same algorithm as used when locating keys for encryption and may thus
be used to see what keys @command{@gpgname} might use. In particular
external methods as defined by @option{--auto-key-locate} are used to
locate a key if the arguments comain valid mail addresses. Only
public keys are listed.
The variant @option{--locate-external-keys} does not consider a
locally existing key and can thus be used to force the refresh of a
key via the defined external methods. If a fingerprint is given and
and the methods defined by @option{--auto-key-locate} define LDAP
servers, the key is fetched from these resources; defined non-LDAP
keyservers are skipped.
@item --show-keys
@opindex show-keys
This commands takes OpenPGP keys as input and prints information about
them in the same way the command @option{--list-keys} does for locally
stored key. In addition the list options @code{show-unusable-uids},
@code{show-unusable-subkeys}, @code{show-notations} and
@code{show-policy-urls} are also enabled. As usual for automated
processing, this command should be combined with the option
@option{--with-colons}.
@item --fingerprint
@opindex fingerprint
List all keys (or the specified ones) along with their
fingerprints. This is the same output as @option{--list-keys} but with
the additional output of a line with the fingerprint. May also be
combined with @option{--check-signatures}. If this
command is given twice, the fingerprints of all secondary keys are
listed too. This command also forces pretty printing of fingerprints
if the keyid format has been set to "none".
@item --list-packets
@opindex list-packets
List only the sequence of packets. This command is only useful for
debugging. When used with option @option{--verbose} the actual MPI
values are dumped and not only their lengths. Note that the output of
this command may change with new releases.
@item --edit-card
@opindex edit-card
@itemx --card-edit
@opindex card-edit
Present a menu to work with a smartcard. The subcommand "help"
provides an overview on available commands. For a detailed
description, please see the Card HOWTO at
https://gnupg.org/documentation/howtos.html#GnuPG-cardHOWTO . Please
note that the command "openpgp" can be used to switch to the OpenPGP
application of cards which by default are presenting another
application (e.g., PIV).
@item --card-status
@opindex card-status
Show the content of the smart card.
@item --change-pin
@opindex change-pin
Present a menu to allow changing the PIN of a smartcard. This
functionality is also available as the subcommand "passwd" with the
@option{--edit-card} command.
@item --delete-keys @var{name}
@opindex delete-keys
Remove key from the public keyring. In batch mode either @option{--yes} is
required or the key must be specified by fingerprint. This is a
safeguard against accidental deletion of multiple keys. If the
exclamation mark syntax is used with the fingerprint of a subkey only
that subkey is deleted; if the exclamation mark is used with the
fingerprint of the primary key the entire public key is deleted.
@item --delete-secret-keys @var{name}
@opindex delete-secret-keys
Remove key from the secret keyring. In batch mode the key must be
specified by fingerprint. The option @option{--yes} can be used to
advise gpg-agent not to request a confirmation. This extra
pre-caution is done because @command{@gpgname} can't be sure that the
secret key (as controlled by gpg-agent) is only used for the given
OpenPGP public key. If the exclamation mark syntax is used with the
fingerprint of a subkey only the secret part of that subkey is
deleted; if the exclamation mark is used with the fingerprint of the
primary key only the secret part of the primary key is deleted.
@item --delete-secret-and-public-key @var{name}
@opindex delete-secret-and-public-key
Same as @option{--delete-key}, but if a secret key exists, it will be
removed first. In batch mode the key must be specified by fingerprint.
The option @option{--yes} can be used to advise gpg-agent not to
request a confirmation.
@item --export
@opindex export
Either export all keys from all keyrings (default keyring and those
registered via option @option{--keyring}), or if at least one name is given,
those of the given name. The exported keys are written to STDOUT or to the
file given with option @option{--output}. Use together with
@option{--armor} to mail those keys.
@item --send-keys @var{keyIDs}
@opindex send-keys
Similar to @option{--export} but sends the keys to a keyserver.
Fingerprints may be used instead of key IDs.
Don't send your complete keyring to a keyserver --- select
only those keys which are new or changed by you. If no @var{keyIDs}
are given, @command{@gpgname} does nothing.
Take care: Keyservers are by design write only systems and thus it is
not possible to ever delete keys once they have been send to a
keyserver.
@item --export-secret-keys
@itemx --export-secret-subkeys
@opindex export-secret-keys
@opindex export-secret-subkeys
Same as @option{--export}, but exports the secret keys instead. The
exported keys are written to STDOUT or to the file given with option
@option{--output}. This command is often used along with the option
@option{--armor} to allow for easy printing of the key for paper backup;
however the external tool @command{paperkey} does a better job of
creating backups on paper. Note that exporting a secret key can be a
security risk if the exported keys are sent over an insecure channel.
The second form of the command has the special property to render the
secret part of the primary key useless; this is a GNU extension to
OpenPGP and other implementations can not be expected to successfully
import such a key. Its intended use is in generating a full key with
an additional signing subkey on a dedicated machine. This command
then exports the key without the primary key to the main machine.
GnuPG may ask you to enter the passphrase for the key. This is
required, because the internal protection method of the secret key is
different from the one specified by the OpenPGP protocol.
@item --export-ssh-key
@opindex export-ssh-key
This command is used to export a key in the OpenSSH public key format.
It requires the specification of one key by the usual means and
exports the latest valid subkey which has an authentication capability
to STDOUT or to the file given with option @option{--output}. That
output can directly be added to ssh's @file{authorized_key} file.
By specifying the key to export using a key ID or a fingerprint
suffixed with an exclamation mark (!), a specific subkey or the
primary key can be exported. This does not even require that the key
has the authentication capability flag set.
@item --import
@itemx --fast-import
@opindex import
Import/merge keys. This adds the given keys to the
keyring. The fast version is currently just a synonym.
There are a few other options which control how this command works.
Most notable here is the @option{--import-options merge-only} option
which does not insert new keys but does only the merging of new
signatures, user-IDs and subkeys.
@item --receive-keys @var{keyIDs}
@opindex receive-keys
@itemx --recv-keys @var{keyIDs}
@opindex recv-keys
Import the keys with the given @var{keyIDs} from a keyserver.
@item --refresh-keys
@opindex refresh-keys
Request updates from a keyserver for keys that already exist on the
local keyring. This is useful for updating a key with the latest
signatures, user IDs, etc. Calling this with no arguments will refresh
the entire keyring.
@item --search-keys @var{names}
@opindex search-keys
Search the keyserver for the given @var{names}. Multiple names given
here will be joined together to create the search string for the
keyserver. Note that keyservers search for @var{names} in a different
and simpler way than gpg does. The best choice is to use a mail
address. Due to data privacy reasons keyservers may even not even
allow searching by user id or mail address and thus may only return
results when being used with the @option{--recv-key} command to
search by key fingerprint or keyid.
@item --fetch-keys @var{URIs}
@opindex fetch-keys
Retrieve keys located at the specified @var{URIs}. Note that different
installations of GnuPG may support different protocols (HTTP, FTP,
LDAP, etc.). When using HTTPS the system provided root certificates
are used by this command.
@item --update-trustdb
@opindex update-trustdb
Do trust database maintenance. This command iterates over all keys and
builds the Web of Trust. This is an interactive command because it may
have to ask for the "ownertrust" values for keys. The user has to give
an estimation of how far she trusts the owner of the displayed key to
correctly certify (sign) other keys. GnuPG only asks for the ownertrust
value if it has not yet been assigned to a key. Using the
@option{--edit-key} menu, the assigned value can be changed at any time.
@item --check-trustdb
@opindex check-trustdb
Do trust database maintenance without user interaction. From time to
time the trust database must be updated so that expired keys or
signatures and the resulting changes in the Web of Trust can be
tracked. Normally, GnuPG will calculate when this is required and do it
automatically unless @option{--no-auto-check-trustdb} is set. This
command can be used to force a trust database check at any time. The
processing is identical to that of @option{--update-trustdb} but it
skips keys with a not yet defined "ownertrust".
For use with cron jobs, this command can be used together with
@option{--batch} in which case the trust database check is done only if
a check is needed. To force a run even in batch mode add the option
@option{--yes}.
@anchor{option --export-ownertrust}
@item --export-ownertrust
@opindex export-ownertrust
Send the ownertrust values to STDOUT. This is useful for backup purposes
as these values are the only ones which can't be re-created from a
corrupted trustdb. Example:
@c man:.RS
@example
@gpgname{} --export-ownertrust > otrust.txt
@end example
@c man:.RE
@item --import-ownertrust
@opindex import-ownertrust
Update the trustdb with the ownertrust values stored in @code{files} (or
STDIN if not given); existing values will be overwritten. In case of a
severely damaged trustdb and if you have a recent backup of the
ownertrust values (e.g., in the file @file{otrust.txt}), you may re-create
the trustdb using these commands:
@c man:.RS
@example
cd ~/.gnupg
rm trustdb.gpg
@gpgname{} --import-ownertrust < otrust.txt
@end example
@c man:.RE
@item --rebuild-keydb-caches
@opindex rebuild-keydb-caches
When updating from version 1.0.6 to 1.0.7 this command should be used
to create signature caches in the keyring. It might be handy in other
situations too.
@item --print-md @var{algo}
@itemx --print-mds
@opindex print-md
Print message digest of algorithm @var{algo} for all given files or STDIN.
With the second form (or a deprecated "*" for @var{algo}) digests for all
available algorithms are printed.
@item --gen-random @var{0|1|2|16|30} @var{count}
@opindex gen-random
Emit @var{count} random bytes of the given quality level 0, 1 or 2. If
@var{count} is not given or zero, an endless sequence of random bytes
will be emitted. If used with @option{--armor} the output will be
base64 encoded. The special level 16 uses a quality level of 1 and
outputs an endless stream of hex-encoded octets. The special level
30 outputs random as 30 zBase-32 characters.
@item --gen-prime @var{mode} @var{bits}
@opindex gen-prime
Use the source, Luke :-). The output format is subject to change
with any release.
@item --enarmor
@itemx --dearmor
@opindex enarmor
@opindex dearmor
Pack or unpack an arbitrary input into/from an OpenPGP ASCII armor.
This is a GnuPG extension to OpenPGP and in general not very useful.
The @option{--dearmor} command can also be used to dearmor PEM armors.
@item --unwrap
@opindex unwrap
This option modifies the command @option{--decrypt} to output the
original message with the encryption layer removed. Thus the output
will be an OpenPGP data structure which often means a signed OpenPGP
message. Note that this option may or may not remove a compression
layer which is often found beneath the encryption layer.
@item --tofu-policy @{auto|good|unknown|bad|ask@} @var{keys}
@opindex tofu-policy
Set the TOFU policy for all the bindings associated with the specified
@var{keys}. For more information about the meaning of the policies,
@pxref{trust-model-tofu}. The @var{keys} may be specified either by their
fingerprint (preferred) or their keyid.
@c @item --server
@c @opindex server
@c Run gpg in server mode. This feature is not yet ready for use and
@c thus not documented.
@end table
@c ********************************************
@c ******* KEY MANAGEMENT COMMANDS **********
@c ********************************************
@node OpenPGP Key Management
@subsection How to manage your keys
This section explains the main commands for key management.
@table @gnupgtabopt
@item --quick-generate-key @var{user-id} [@var{algo} [@var{usage} [@var{expire}]]]
@itemx --quick-gen-key
@opindex quick-generate-key
@opindex quick-gen-key
This is a simple command to generate a standard key with one user id.
In contrast to @option{--generate-key} the key is generated directly
without the need to answer a bunch of prompts. Unless the option
@option{--yes} is given, the key creation will be canceled if the
given user id already exists in the keyring.
If invoked directly on the console without any special options an
answer to a ``Continue?'' style confirmation prompt is required. In
case the user id already exists in the keyring a second prompt to
force the creation of the key will show up.
If @var{algo} or @var{usage} are given, only the primary key is
created and no prompts are shown. To specify an expiration date but
still create a primary and subkey use ``default'' or
``future-default'' for @var{algo} and ``default'' for @var{usage}.
For a description of these optional arguments see the command
@code{--quick-add-key}. The @var{usage} accepts also the value
``cert'' which can be used to create a certification only primary key;
the default is to a create certification and signing key.
The @var{expire} argument can be used to specify an expiration date
for the key. Several formats are supported; commonly the ISO formats
``YYYY-MM-DD'' or ``YYYYMMDDThhmmss'' are used. To make the key
expire in N seconds, N days, N weeks, N months, or N years use
``seconds=N'', ``Nd'', ``Nw'', ``Nm'', or ``Ny'' respectively. Not
specifying a value, or using ``-'' results in a key expiring in a
reasonable default interval. The values ``never'', ``none'' can be
used for no expiration date.
If this command is used with @option{--batch},
@option{--pinentry-mode} has been set to @code{loopback}, and one of
the passphrase options (@option{--passphrase},
@option{--passphrase-fd}, or @option{--passphrase-file}) is used, the
supplied passphrase is used for the new key and the agent does not ask
for it. To create a key without any protection @code{--passphrase ''}
may be used.
To create an OpenPGP key from the keys available on the currently
inserted smartcard, the special string ``card'' can be used for
@var{algo}. If the card features an encryption and a signing key, gpg
will figure them out and creates an OpenPGP key consisting of the
usual primary key and one subkey. This works only with certain
smartcards. Note that the interactive @option{--full-gen-key} command
allows one to do the same but with greater flexibility in the selection of
the smartcard keys.
Note that it is possible to create a primary key and a subkey using
non-default algorithms by using ``default'' and changing the default
parameters using the option @option{--default-new-key-algo}.
@item --quick-set-expire @var{fpr} @var{expire} [*|@var{subfprs}]
@opindex quick-set-expire
With two arguments given, directly set the expiration time of the
primary key identified by @var{fpr} to @var{expire}. To remove the
expiration time @code{0} can be used. With three arguments and the
third given as an asterisk, the expiration time of all non-revoked and
not yet expired subkeys are set to @var{expire}. With more than two
arguments and a list of fingerprints given for @var{subfprs}, all
non-revoked subkeys matching these fingerprints are set to
@var{expire}.
@item --quick-add-key @var{fpr} [@var{algo} [@var{usage} [@var{expire}]]]
@opindex quick-add-key
Directly add a subkey to the key identified by the fingerprint
@var{fpr}. Without the optional arguments an encryption subkey is
added. If any of the arguments are given a more specific subkey is
added.
@var{algo} may be any of the supported algorithms or curve names
given in the format as used by key listings. To use the default
algorithm the string ``default'' or ``-'' can be used. Supported
algorithms are ``rsa'', ``dsa'', ``elg'', ``ed25519'', ``cv25519'',
and other ECC curves. For example the string ``rsa'' adds an RSA key
with the default key length; a string ``rsa4096'' requests that the
key length is 4096 bits. The string ``future-default'' is an alias
for the algorithm which will likely be used as default algorithm in
future versions of gpg. To list the supported ECC curves the command
@code{gpg --with-colons --list-config curve} can be used.
Depending on the given @var{algo} the subkey may either be an
encryption subkey or a signing subkey. If an algorithm is capable of
signing and encryption and such a subkey is desired, a @var{usage}
string must be given. This string is either ``default'' or ``-'' to
keep the default or a comma delimited list (or space delimited list)
of keywords: ``sign'' for a signing subkey, ``auth'' for an
authentication subkey, and ``encr'' for an encryption subkey
(``encrypt'' can be used as alias for ``encr''). The valid
combinations depend on the algorithm.
The @var{expire} argument can be used to specify an expiration date
for the key. Several formats are supported; commonly the ISO formats
``YYYY-MM-DD'' or ``YYYYMMDDThhmmss'' are used. To make the key
expire in N seconds, N days, N weeks, N months, or N years use
``seconds=N'', ``Nd'', ``Nw'', ``Nm'', or ``Ny'' respectively. Not
specifying a value, or using ``-'' results in a key expiring in a
reasonable default interval. The values ``never'', ``none'' can be
used for no expiration date.
@item --quick-add-adsk @var{fpr} @var{adskfpr}
@opindex quick-add-adsk
Directly add an Additional Decryption Subkey to the key identified by
the fingerprint @var{fpr}. @var{adskfpr} is the fingerprint of
another key's encryption subkey. A subkey is commonly used here
because by default a primary key has no encryption capability. Use
the option @option{--with-subkey-fingerprint} with a list command to
display the subkey fingerprints.
@item --generate-key
@opindex generate-key
@itemx --gen-key
@opindex gen-key
Generate a new key pair using the current default parameters. This is
the standard command to create a new key. In addition to the key a
revocation certificate is created and stored in the
@file{openpgp-revocs.d} directory below the GnuPG home directory.
@item --full-generate-key
@opindex full-generate-key
@itemx --full-gen-key
@opindex full-gen-key
Generate a new key pair with dialogs for all options. This is an
extended version of @option{--generate-key}.
There is also a feature which allows you to create keys in batch
mode. See the manual section ``Unattended key generation'' on how
to use this.
@item --generate-revocation @var{name}
@opindex generate-revocation
@itemx --gen-revoke @var{name}
@opindex gen-revoke
Generate a revocation certificate for the complete key. To only revoke
a subkey or a key signature, use the @option{--edit} command.
This command merely creates the revocation certificate so that it can
be used to revoke the key if that is ever needed. To actually revoke
a key the created revocation certificate needs to be merged with the
key to revoke. This is done by importing the revocation certificate
using the @option{--import} command. Then the revoked key needs to be
published, which is best done by sending the key to a keyserver
(command @option{--send-key}) and by exporting (@option{--export}) it
to a file which is then send to frequent communication partners.
@item --generate-designated-revocation @var{name}
@opindex generate-designated-revocation
@itemx --desig-revoke @var{name}
@opindex desig-revoke
Generate a designated revocation certificate for a key. This allows a
user (with the permission of the keyholder) to revoke someone else's
key.
@item --edit-key
@opindex edit-key
Present a menu which enables you to do most of the key management
related tasks. It expects the specification of a key on the command
line.
@c ******** Begin Edit-key Options **********
@table @asis
@item uid @var{n}
@opindex keyedit:uid
Toggle selection of user ID or photographic user ID with index @var{n}.
Use @code{*} to select all and @code{0} to deselect all.
@item key @var{n}
@opindex keyedit:key
Toggle selection of subkey with index @var{n} or key ID @var{n}.
Use @code{*} to select all and @code{0} to deselect all.
@item sign
@opindex keyedit:sign
Make a signature on key of user @code{name}. If the key is not yet
signed by the default user (or the users given with @option{-u}), the program
displays the information of the key again, together with its
fingerprint and asks whether it should be signed. This question is
repeated for all users specified with
@option{-u}.
@item lsign
@opindex keyedit:lsign
Same as "sign" but the signature is marked as non-exportable and will
therefore never be used by others. This may be used to make keys
valid only in the local environment.
@item nrsign
@opindex keyedit:nrsign
Same as "sign" but the signature is marked as non-revocable and can
therefore never be revoked.
@item tsign
@opindex keyedit:tsign
Make a trust signature. This is a signature that combines the notions
of certification (like a regular signature), and trust (like the
"trust" command). It is generally useful in distinct communities
or groups to implement the concept of a Trusted Introducer. For
more information please read the sections ``Trust Signature'' and
``Regular Expression'' in RFC-4880.
@end table
@c man:.RS
Note that "l" (for local / non-exportable), "nr" (for non-revocable,
and "t" (for trust) may be freely mixed and prefixed to "sign" to
create a signature of any type desired.
@c man:.RE
If the option @option{--only-sign-text-ids} is specified, then any
non-text based user ids (e.g., photo IDs) will not be selected for
signing.
@table @asis
@item delsig
@opindex keyedit:delsig
Delete a signature. Note that it is not possible to retract a signature,
once it has been send to the public (i.e., to a keyserver). In that case
you better use @code{revsig}.
@item revsig
@opindex keyedit:revsig
Revoke a signature. For every signature which has been generated by
one of the secret keys, GnuPG asks whether a revocation certificate
should be generated.
@item check
@opindex keyedit:check
Check the signatures on all selected user IDs. With the extra
option @code{selfsig} only self-signatures are shown.
@item adduid
@opindex keyedit:adduid
Create an additional user ID.
@item addphoto
@opindex keyedit:addphoto
Create a photographic user ID. This will prompt for a JPEG file that
will be embedded into the user ID. Note that a very large JPEG will make
for a very large key. Also note that some programs will display your
JPEG unchanged (GnuPG), and some programs will scale it to fit in a
dialog box (PGP).
@item showphoto
@opindex keyedit:showphoto
Display the selected photographic user ID.
@item deluid
@opindex keyedit:deluid
Delete a user ID or photographic user ID. Note that it is not
possible to retract a user id, once it has been send to the public
(i.e., to a keyserver). In that case you better use @code{revuid}.
@item revuid
@opindex keyedit:revuid
Revoke a user ID or photographic user ID.
@item primary
@opindex keyedit:primary
Flag the current user id as the primary one, removes the primary user
id flag from all other user ids and sets the timestamp of all affected
self-signatures one second ahead. Note that setting a photo user ID
as primary makes it primary over other photo user IDs, and setting a
regular user ID as primary makes it primary over other regular user
IDs.
@item keyserver
@opindex keyedit:keyserver
Set a preferred keyserver for the specified user ID(s). This allows
other users to know where you prefer they get your key from. See
@option{--keyserver-options honor-keyserver-url} for more on how this
works. Setting a value of "none" removes an existing preferred
keyserver.
@item notation
@opindex keyedit:notation
Set a name=value notation for the specified user ID(s). See
@option{--cert-notation} for more on how this works. Setting a value of
"none" removes all notations, setting a notation prefixed with a minus
sign (-) removes that notation, and setting a notation name (without the
=value) prefixed with a minus sign removes all notations with that name.
@item pref
@opindex keyedit:pref
List preferences from the selected user ID. This shows the actual
preferences, without including any implied preferences.
@item showpref
@opindex keyedit:showpref
More verbose preferences listing for the selected user ID. This shows
the preferences in effect by including the implied preferences of 3DES
(cipher), SHA-1 (digest), and Uncompressed (compression) if they are
not already included in the preference list. In addition, the
preferred keyserver and signature notations (if any) are shown.
@item setpref @var{string}
@opindex keyedit:setpref
Set the list of user ID preferences to @var{string} for all (or just
the selected) user IDs. Calling setpref with no arguments sets the
preference list to the default (either built-in or set via
@option{--default-preference-list}), and calling setpref with "none"
as the argument sets an empty preference list. Use @command{@gpgname
--version} to get a list of available algorithms. Note that while you
can change the preferences on an attribute user ID (aka "photo ID"),
GnuPG does not select keys via attribute user IDs so these preferences
will not be used by GnuPG. Note that an unattended version of this
command is available as @option{--quick-update-pref}.
When setting preferences, you should list the algorithms in the order
which you'd like to see them used by someone else when encrypting a
message to your key. If you don't include 3DES, it will be
automatically added at the end. Note that there are many factors that
go into choosing an algorithm (for example, your key may not be the
only recipient), and so the remote OpenPGP application being used to
send to you may or may not follow your exact chosen order for a given
message. It will, however, only choose an algorithm that is present
on the preference list of every recipient key. See also the
INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS section below.
@item addkey
@opindex keyedit:addkey
Add a subkey to this key.
@item addcardkey
@opindex keyedit:addcardkey
Generate a subkey on a card and add it to this key.
@item keytocard
@opindex keyedit:keytocard
Transfer the selected secret subkey (or the primary key if no subkey
has been selected) to a smartcard. The secret key in the keyring
will be replaced by a stub if the key could be stored successfully
on the card and you use the save command later. Only certain key
types may be transferred to the card. A sub menu allows you to
select on what card to store the key. Note that it is not possible
to get that key back from the card --- if the card gets broken your
secret key will be lost unless you have a backup somewhere.
@item bkuptocard @var{file}
@opindex keyedit:bkuptocard
Restore the given @var{file} to a card. This command may be used to restore a
backup key (as generated during card initialization) to a new card. In
almost all cases this will be the encryption key. You should use this
command only with the corresponding public key and make sure that the
file given as argument is indeed the backup to restore. You should then
select 2 to restore as encryption key. You will first be asked to enter
the passphrase of the backup key and then for the Admin PIN of the card.
@item keytotpm
@opindex keyedit:keytotpm
Transfer the selected secret subkey (or the primary key if no subkey
has been selected) to TPM form. The secret key in the keyring will
be replaced by the TPM representation of that key, which can only be
read by the particular TPM that created it (so the keyfile now
becomes locked to the laptop containing the TPM). Only certain key
types may be transferred to the TPM (all TPM 2.0 systems are
mandated to have the rsa2048 and nistp256 algorithms but newer TPMs
may have more). Note that the key itself is not transferred into the
TPM, merely encrypted by the TPM in-place, so if the keyfile is
deleted, the key will be lost. Once transferred to TPM
representation, the key file can never be converted back to non-TPM
form and the key will die when the TPM does, so you should first
have a backup on secure offline storage of the actual secret key
file before conversion. It is essential to use the physical system
TPM that you have rw permission on the TPM resource manager device
(/dev/tpmrm0). Usually this means you must be a member of the tss
group.
@item delkey
@opindex keyedit:delkey
Remove a subkey (secondary key). Note that it is not possible to retract
a subkey, once it has been send to the public (i.e., to a keyserver). In
that case you better use @code{revkey}. Also note that this only
deletes the public part of a key.
@item revkey
@opindex keyedit:revkey
Revoke a subkey.
@item expire
@opindex keyedit:expire
Change the key or subkey expiration time. If a subkey is selected, the
expiration time of this subkey will be changed. With no selection, the
key expiration of the primary key is changed.
@item trust
@opindex keyedit:trust
Change the owner trust value for the key. This updates the trust-db
immediately and no save is required.
@item disable
@itemx enable
@opindex keyedit:disable
@opindex keyedit:enable
Disable or enable an entire key. A disabled key can not normally be
used for encryption.
@item addrevoker
@opindex keyedit:addrevoker
Add a designated revoker to the key. This takes one optional argument:
"sensitive". If a designated revoker is marked as sensitive, it will
not be exported by default (see export-options).
@item addadsk
@opindex keyedit:addadsk
Add an Additional Decryption Subkey. The user is asked to enter the
fingerprint of another encryption subkey. Note that the exact
fingerprint of another key's encryption subkey needs to be entered.
This is because commonly the primary key has no encryption
capability. Use the option @option{--with-subkey-fingerprint} with
a list command to display the subkey fingerprints.
@item passwd
@opindex keyedit:passwd
Change the passphrase of the secret key.
@item toggle
@opindex keyedit:toggle
This is dummy command which exists only for backward compatibility.
@item clean
@opindex keyedit:clean
Compact (by removing all signatures except the selfsig) any user ID
that is no longer usable (e.g., revoked, or expired). Then, remove any
signatures that are not usable by the trust calculations.
Specifically, this removes any signature that does not validate, any
signature that is superseded by a later signature, revoked signatures,
and signatures issued by keys that are not present on the keyring.
@item minimize
@opindex keyedit:minimize
Make the key as small as possible. This removes all signatures from
each user ID except for the most recent self-signature.
@item change-usage
@opindex keyedit:change-usage
Change the usage flags (capabilities) of the primary key or of
subkeys. These usage flags (e.g., Certify, Sign, Authenticate,
Encrypt) are set during key creation. Sometimes it is useful to
have the opportunity to change them (for example to add
Authenticate) after they have been created. Please take care when
doing this; the allowed usage flags depend on the key algorithm.
@item cross-certify
@opindex keyedit:cross-certify
Add cross-certification signatures to signing subkeys that may not
currently have them. Cross-certification signatures protect against a
subtle attack against signing subkeys. See
@option{--require-cross-certification}. All new keys generated have
this signature by default, so this command is only useful to bring
older keys up to date.
@item save
@opindex keyedit:save
Save all changes to the keyring and quit.
@item quit
@opindex keyedit:quit
Quit the program without updating the
keyring.
@end table
@c man:.RS
The listing shows you the key with its secondary keys and all user
IDs. The primary user ID is indicated by a dot, and selected keys or
user IDs are indicated by an asterisk. The trust
value is displayed with the primary key: "trust" is the assigned owner
trust and "validity" is the calculated validity of the key. Validity
values are also displayed for all user IDs.
For possible values of trust, @pxref{trust-values}.
@c man:.RE
@c ******** End Edit-key Options **********
@item --sign-key @var{name}
@opindex sign-key
Signs a public key with your secret key. This is a shortcut version of
the subcommand "sign" from @option{--edit-key}.
@item --lsign-key @var{name}
@opindex lsign-key
Signs a public key with your secret key but marks it as
non-exportable. This is a shortcut version of the subcommand "lsign"
from @option{--edit-key}.
@item --quick-sign-key @var{fpr} [@var{names}]
@itemx --quick-lsign-key @var{fpr} [@var{names}]
@opindex quick-sign-key
@opindex quick-lsign-key
Directly sign a key from the passphrase without any further user
interaction. The @var{fpr} must be the verified primary fingerprint
of a key in the local keyring. If no @var{names} are given, all
useful user ids are signed; with given [@var{names}] only useful user
ids matching one of these names are signed. By default, or if a name
is prefixed with a '*', a case insensitive substring match is used.
If a name is prefixed with a '=' a case sensitive exact match is done.
The command @option{--quick-lsign-key} marks the signatures as
non-exportable. If such a non-exportable signature already exists the
@option{--quick-sign-key} turns it into a exportable signature. If
you need to update an existing signature, for example to add or change
notation data, you need to use the option @option{--force-sign-key}.
This command uses reasonable defaults and thus does not provide the
full flexibility of the "sign" subcommand from @option{--edit-key}.
Its intended use is to help unattended key signing by utilizing a list
of verified fingerprints.
@item --quick-add-uid @var{user-id} @var{new-user-id}
@opindex quick-add-uid
This command adds a new user id to an existing key. In contrast to
the interactive sub-command @code{adduid} of @option{--edit-key} the
@var{new-user-id} is added verbatim with only leading and trailing
white space removed, it is expected to be UTF-8 encoded, and no checks
on its form are applied.
@item --quick-revoke-uid @var{user-id} @var{user-id-to-revoke}
@opindex quick-revoke-uid
This command revokes a user ID on an existing key. It cannot be used
to revoke the last user ID on key (some non-revoked user ID must
remain), with revocation reason ``User ID is no longer valid''. If
you want to specify a different revocation reason, or to supply
supplementary revocation text, you should use the interactive
sub-command @code{revuid} of @option{--edit-key}.
@item --quick-revoke-sig @var{fpr} @var{signing-fpr} [@var{names}]
@opindex quick-revoke-sig
This command revokes the key signatures made by @var{signing-fpr} from
the key specified by the fingerprint @var{fpr}. With @var{names}
given only the signatures on user ids of the key matching any of the
given names are affected (see @option{--quick-sign-key}). If a
revocation already exists a notice is printed instead of creating a
new revocation; no error is returned in this case. Note that key
signature revocations may be superseded by a newer key signature and
in turn again revoked.
@item --quick-set-primary-uid @var{user-id} @var{primary-user-id}
@opindex quick-set-primary-uid
This command sets or updates the primary user ID flag on an existing
key. @var{user-id} specifies the key and @var{primary-user-id} the
user ID which shall be flagged as the primary user ID. The primary
user ID flag is removed from all other user ids and the timestamp of
all affected self-signatures is set one second ahead.
@item --quick-update-pref @var{user-id}
@opindex quick-update-pref
This command updates the preference list of the key to the current
default value (either built-in or set via
@option{--default-preference-list}). This is the unattended version
of using "setpref" in the @option{--key-edit} menu without giving a
list. Note that you can show the preferences in a key listing by
using @option{--list-options show-pref} or @option{--list-options
show-pref-verbose}. You should also re-distribute updated keys to
your peers.
@item --quick-set-ownertrust @var{user-id} @var{value}
@opindex quick-set-ownertrust
This command sets the ownertrust of a key and can also be used to set
the disable flag of a key. This is the unattended version of using
"trust", "disable", or "enable" in the @option{--key-edit} menu.
@item --change-passphrase @var{user-id}
@opindex change-passphrase
@itemx --passwd @var{user-id}
@opindex passwd
Change the passphrase of the secret key belonging to the certificate
specified as @var{user-id}. This is a shortcut for the sub-command
@code{passwd} of the @option{--edit-key} menu. When using together with the
option @option{--dry-run} this will not actually change the passphrase
but check that the current passphrase is correct.
@end table
@c *******************************************
@c *************** ****************
@c *************** OPTIONS ****************
@c *************** ****************
@c *******************************************
@mansect options
@node GPG Options
@section Option Summary
@command{@gpgname} features a bunch of options to control the exact
behaviour and to change the default configuration.
@menu
* GPG Configuration Options:: How to change the configuration.
* GPG Key related Options:: Key related options.
* GPG Input and Output:: Input and Output.
* OpenPGP Options:: OpenPGP protocol specific options.
* Compliance Options:: Compliance options.
* GPG Esoteric Options:: Doing things one usually doesn't want to do.
* Deprecated Options:: Deprecated options.
@end menu
Long options can be put in an options file (default
"~/.gnupg/gpg.conf"). Short option names will not work --- for
example, "armor" is a valid option for the options file, while "a" is
not. Do not write the 2 dashes, but simply the name of the option and
any required arguments. Lines with a hash ('#') as the first
non-white-space character are ignored. Commands may be put in this
file too, but that is not generally useful as the command will execute
automatically with every execution of gpg.
Please remember that option parsing stops as soon as a non-option is
encountered, you can explicitly stop parsing by using the special option
@option{--}.
@c *******************************************
@c ******** CONFIGURATION OPTIONS **********
@c *******************************************
@node GPG Configuration Options
@subsection How to change the configuration
These options are used to change the configuration and most of them
are usually found in the option file.
@table @gnupgtabopt
@item --default-key @var{name}
@opindex default-key
Use @var{name} as the default key to sign with. It is suggested to
use a fingerprint or at least a long keyID for @var{name}. If this
option is not used, the default key is the first key found in the
secret keyring. Note that @option{-u} or @option{--local-user}
overrides this option. This option may be given multiple times. In
this case, the last key for which a secret key is available is used.
If there is no secret key available for any of the specified values,
GnuPG will not emit an error message but continue as if this option
wasn't given.
@item --default-recipient @var{name}
@opindex default-recipient
Use @var{name} as default recipient if option @option{--recipient} is
not used and don't ask if this is a valid one. @var{name} must be
non-empty and it is suggested to use a fingerprint for @var{name}.
@item --default-recipient-self
@opindex default-recipient-self
Use the default key as default recipient if option @option{--recipient} is not
used and don't ask if this is a valid one. The default key is the first
one from the secret keyring or the one set with @option{--default-key}.
@item --no-default-recipient
@opindex no-default-recipient
Reset @option{--default-recipient} and @option{--default-recipient-self}.
Should not be used in an option file.
@item -v, --verbose
@opindex verbose
Give more information during processing. If used
twice, the input data is listed in detail.
@item --no-verbose
@opindex no-verbose
Reset verbose level to 0. Should not be used in an option file.
@item -q, --quiet
@opindex quiet
Try to be as quiet as possible. Should not be used in an option file.
@item --batch
@itemx --no-batch
@opindex batch
@opindex no-batch
Use batch mode. Never ask, do not allow interactive commands.
@option{--no-batch} disables this option. Note that even with a
filename given on the command line, gpg might still need to read from
STDIN (in particular if gpg figures that the input is a
detached signature and no data file has been specified). Thus if you
do not want to feed data via STDIN, you should connect STDIN to
@file{/dev/null}.
It is highly recommended to use this option along with the options
@option{--status-fd} and @option{--with-colons} for any unattended use of
@command{gpg}. Should not be used in an option file.
@item --no-tty
@opindex no-tty
Make sure that the TTY (terminal) is never used for any output.
This option is needed in some cases because GnuPG sometimes prints
warnings to the TTY even if @option{--batch} is used.
@item --yes
@opindex yes
Assume "yes" on most questions. Should not be used in an option file.
@item --no
@opindex no
Assume "no" on most questions. Should not be used in an option file.
@item --list-filter @{select=@var{expr}@}
@opindex list-filter
A list filter can be used to output only certain keys during key
listing commands. For the available property names, see the description
of @option{--import-filter}.
@item --list-options @var{parameters}
@opindex list-options
This is a space or comma delimited string that gives options used when
listing keys and signatures (that is, @option{--list-keys},
@option{--check-signatures}, @option{--list-public-keys},
@option{--list-secret-keys}, and the @option{--edit-key} functions).
Options can be prepended with a @option{no-} (after the two dashes) to
give the opposite meaning. The options are:
@table @asis
@item show-photos
@opindex list-options:show-photos
Causes @option{--list-keys}, @option{--check-signatures},
@option{--list-public-keys}, and @option{--list-secret-keys} to
display any photo IDs attached to the key. Defaults to no. See also
@option{--photo-viewer}. Does not work with @option{--with-colons}:
see @option{--attribute-fd} for the appropriate way to get photo data
for scripts and other frontends.
@item show-usage
@opindex list-options:show-usage
Show usage information for keys and subkeys in the standard key
listing. This is a list of letters indicating the allowed usage for a
key (@code{E}=encryption, @code{S}=signing, @code{C}=certification,
@code{A}=authentication). Defaults to yes.
@item show-ownertrust
@opindex list-options:show-ownertrust
Show the ownertrust value for keys also in the standard key
listing. Defaults to no.
@item show-policy-urls
@opindex list-options:show-policy-urls
Show policy URLs in the @option{--check-signatures}
listings. Defaults to no.
@item show-notations
@itemx show-std-notations
@itemx show-user-notations
@opindex list-options:show-notations
@opindex list-options:show-std-notations
@opindex list-options:show-user-notations
Show all, IETF standard, or user-defined signature notations in the
@option{--check-signatures} listings. Defaults to no.
@item show-x509-notations
@opindex list-options:show-x509-notations
Print X.509 certificates embedded in key signatures as PEM data.
This is intended for debugging and the output format may change
without notice.
@item store-x509-notations
@opindex list-options:store-x509-notations
Store X.509 certificates embedded in key signatures as PEM data
files. The filename consists the 4 byte key ID of the certificate,
a dash, the fingerprint of the key or subkey, and the suffix ".pem".
@item show-keyserver-urls
@opindex list-options:show-keyserver-urls
Show any preferred keyserver URL in the
@option{--check-signatures} listings. Defaults to no.
@item show-uid-validity
@opindex list-options:show-uid-validity
Display the calculated validity of user IDs during key listings.
Defaults to yes.
@item show-unusable-uids
@opindex list-options:show-unusable-uids
Show revoked and expired user IDs in key listings. Defaults to no.
@item show-unusable-subkeys
@opindex list-options:show-unusable-subkeys
Show revoked and expired subkeys in key listings. Defaults to no.
@item show-unusable-sigs
@opindex list-options:show-unusable-sigs
Show key signature made using weak or unsupported algorithms.
@item show-keyring
@opindex list-options:show-keyring
Display the keyring name at the head of key listings to show which
keyring a given key resides on. Defaults to no.
@item show-sig-expire
@opindex list-options:show-sig-expire
Show signature expiration dates (if any) during
@option{--check-signatures} listings. Defaults to no.
@item show-sig-subpackets
@opindex list-options:show-sig-subpackets
Include signature subpackets in the key listing. This option can take an
optional argument list of the subpackets to list. If no argument is
passed, list all subpackets. Defaults to no. This option is only
meaningful when using @option{--with-colons} along with
@option{--check-signatures}.
@item show-only-fpr-mbox
@opindex list-options:show-only-fpr-mbox
For each user-id which has a valid mail address print
only the fingerprint followed by the mail address.
@item sort-sigs
@opindex list-options:sort-sigs
With @option{--list-sigs} and @option{--check-sigs} sort the
signatures by keyID and creation time to make it easier to view the
history of these signatures. The self-signature is also listed
before other signatures. Defaults to yes.
@end table
@item --verify-options @var{parameters}
@opindex verify-options
This is a space or comma delimited string that gives options used when
verifying signatures. Options can be prepended with a `no-' to give
the opposite meaning. The options are:
@table @asis
@item show-photos
@opindex verify-options:show-photos
Display any photo IDs present on the key that issued the signature.
Defaults to no. See also @option{--photo-viewer}.
@item show-policy-urls
@opindex verify-options:show-policy-urls
Show policy URLs in the signature being verified. Defaults to yes.
@item show-notations
@itemx show-std-notations
@itemx show-user-notations
@opindex verify-options:show-notations
@opindex verify-options:show-std-notations
@opindex verify-options:show-user-notations
Show all, IETF standard, or user-defined signature notations in the
signature being verified. Defaults to IETF standard.
@item show-keyserver-urls
@opindex verify-options:show-keyserver-urls
Show any preferred keyserver URL in the signature being verified.
Defaults to yes.
@item show-uid-validity
@opindex verify-options:show-uid-validity
Display the calculated validity of the user IDs on the key that issued
the signature. Defaults to yes.
@item show-unusable-uids
@opindex verify-options:show-unusable-uids
Show revoked and expired user IDs during signature verification.
Defaults to no.
@item show-primary-uid-only
@opindex verify-options:show-primary-uid-only
Show only the primary user ID during signature verification. That is
all the AKA lines as well as photo Ids are not shown with the signature
verification status.
@end table
@item --enable-large-rsa
@itemx --disable-large-rsa
@opindex enable-large-rsa
@opindex disable-large-rsa
With @option{--generate-key} and @option{--batch}, enable the creation
of RSA secret keys as large as 8192 bit. Note: 8192 bit is more than
is generally recommended. These large keys don't significantly
improve security, but they are more expensive to use, and their
signatures and certifications are larger. This option is only
available if the binary was build with large-secmem support.
@item --enable-dsa2
@itemx --disable-dsa2
@opindex enable-dsa2
@opindex disable-dsa2
Enable hash truncation for all DSA keys even for old DSA Keys up to
1024 bit. This is also the default with @option{--openpgp}. Note
that older versions of GnuPG also required this flag to allow the
generation of DSA larger than 1024 bit.
@item --photo-viewer @var{string}
@opindex photo-viewer
This is the command line that should be run to view a photo ID. "%i"
will be expanded to a filename containing the photo. "%I" does the
same, except the file will not be deleted once the viewer exits.
Other flags are "%k" for the key ID, "%K" for the long key ID, "%f"
for the key fingerprint, "%t" for the extension of the image type
(e.g., "jpg"), "%T" for the MIME type of the image (e.g., "image/jpeg"),
"%v" for the single-character calculated validity of the image being
viewed (e.g., "f"), "%V" for the calculated validity as a string (e.g.,
"full"), "%U" for a base32 encoded hash of the user ID,
and "%%" for an actual percent sign. If neither %i or %I are present,
then the photo will be supplied to the viewer on standard input.
On Unix the default viewer is
@code{xloadimage -fork -quiet -title 'KeyID 0x%k' STDIN}
with a fallback to
@code{display -title 'KeyID 0x%k' %i}
and finally to
@code{xdg-open %i}.
On Windows
@code{!ShellExecute 400 %i} is used; here the command is a meta
command to use that API call followed by a wait time in milliseconds
which is used to give the viewer time to read the temporary image file
before gpg deletes it again. Note that if your image viewer program
is not secure, then executing it from gpg does not make it secure.
@item --exec-path @var{string}
@opindex exec-path
@efindex PATH
Sets a list of directories to search for photo viewers If not provided
photo viewers use the @code{PATH} environment variable.
@item --keyring @var{file}
@opindex keyring
Add @var{file} to the current list of keyrings. If @var{file} begins
with a tilde and a slash, these are replaced by the $HOME directory. If
the filename does not contain a slash, it is assumed to be in the GnuPG
home directory ("~/.gnupg" unless @option{--homedir} or $GNUPGHOME is
used).
Note that this adds a keyring to the current list. If the intent is to
use the specified keyring alone, use @option{--keyring} along with
@option{--no-default-keyring}.
If the option @option{--no-keyring} has been used no keyrings will
be used at all.
Note that if the option @option{use-keyboxd} is enabled in
@file{common.conf}, no keyrings are used at all and keys are all
maintained by the keyboxd process in its own database.
@item --primary-keyring @var{file}
@opindex primary-keyring
-This is a varian of @option{--keyring} and designates @var{file} as
+This is a variant of @option{--keyring} and designates @var{file} as
the primary public keyring. This means that newly imported keys (via
@option{--import} or keyserver @option{--recv-from}) will go to this
keyring.
@item --secret-keyring @var{file}
@opindex secret-keyring
This is an obsolete option and ignored. All secret keys are stored in
the @file{private-keys-v1.d} directory below the GnuPG home directory.
@item --trustdb-name @var{file}
@opindex trustdb-name
Use @var{file} instead of the default trustdb. If @var{file} begins
with a tilde and a slash, these are replaced by the $HOME directory. If
the filename does not contain a slash, it is assumed to be in the GnuPG
home directory (@file{~/.gnupg} if @option{--homedir} or $GNUPGHOME is
not used).
@include opt-homedir.texi
@item --display-charset @var{name}
@opindex display-charset
Set the name of the native character set. This is used to convert some
informational strings like user IDs to the proper UTF-8 encoding.
Note that this has nothing to do with the character set of data to be
encrypted or signed; GnuPG does not recode user-supplied data. If this
option is not used, the default character set is determined from the
current locale. A verbosity level of 3 shows the chosen set. This
option should not be used on Windows. Valid values for @var{name}
are:
@table @asis
@item iso-8859-1
@opindex display-charset:iso-8859-1
This is the Latin 1 set.
@item iso-8859-2
@opindex display-charset:iso-8859-2
The Latin 2 set.
@item iso-8859-15
@opindex display-charset:iso-8859-15
This is currently an alias for
the Latin 1 set.
@item koi8-r
@opindex display-charset:koi8-r
The usual Russian set (RFC-1489).
@item utf-8
@opindex display-charset:utf-8
Bypass all translations and assume
that the OS uses native UTF-8 encoding.
@end table
@item --utf8-strings
@itemx --no-utf8-strings
@opindex utf8-strings
Assume that command line arguments are given as UTF-8 strings. The
default (@option{--no-utf8-strings}) is to assume that arguments are
encoded in the character set as specified by
@option{--display-charset}. These options affect all following
arguments. Both options may be used multiple times.
This option should not be used in an option file.
This option has no effect on Windows. There the internal used UTF-8
encoding is translated for console input and output. The command line
arguments are expected as Unicode and translated to UTF-8. Thus when
calling this program from another, make sure to use the Unicode
version of CreateProcess.
@anchor{gpg-option --options}
@item --options @var{file}
@opindex options
Read options from @var{file} and do not try to read them from the
default options file in the homedir (see @option{--homedir}). This
option is ignored if used in an options file.
@item --no-options
@opindex no-options
Shortcut for @option{--options /dev/null}. This option is detected
before an attempt to open an option file. Using this option will also
prevent the creation of a @file{~/.gnupg} homedir.
@item -z @var{n}
@itemx --compress-level @var{n}
@itemx --bzip2-compress-level @var{n}
@itemx --no-compress
@opindex compress-level
@opindex bzip2-compress-level
@opindex no-compress
Set compression level to @var{n} for the ZIP and ZLIB compression
algorithms. The default is to use the default compression level of zlib
(normally 6). @option{--bzip2-compress-level} sets the compression level
for the BZIP2 compression algorithm (defaulting to 6 as well). This is a
different option from @option{--compress-level} since BZIP2 uses a
significant amount of memory for each additional compression level.
Option @option{-z} sets both. A value of 0 for @var{n} disables
compression. A value of -1 forces compression using the default
level. Option @option{--no-compress} is identical to @option{-z0}.
Except for the @option{--store} command compression is always used
unless @command{gpg} detects that the input is already compressed. To
inhibit the use of compression use @option{-z0} or
@option{--no-compress}; to force compression use @option{-z-1} or
option @option{z} with another compression level than the default as
indicated by -1. Note that this overriding of the default deection
works only with @option{z} and not with the long variant of this
option.
@item --bzip2-decompress-lowmem
@opindex bzip2-decompress-lowmem
Use a different decompression method for BZIP2 compressed files. This
alternate method uses a bit more than half the memory, but also runs
at half the speed. This is useful under extreme low memory
circumstances when the file was originally compressed at a high
@option{--bzip2-compress-level}.
@item --mangle-dos-filenames
@itemx --no-mangle-dos-filenames
@opindex mangle-dos-filenames
@opindex no-mangle-dos-filenames
Older version of Windows cannot handle filenames with more than one
dot. @option{--mangle-dos-filenames} causes GnuPG to replace (rather
than add to) the extension of an output filename to avoid this
problem. This option is off by default and has no effect on non-Windows
platforms.
@item --ask-cert-level
@itemx --no-ask-cert-level
@opindex ask-cert-level
When making a key signature, prompt for a certification level. If this
option is not specified, the certification level used is set via
@option{--default-cert-level}. See @option{--default-cert-level} for
information on the specific levels and how they are
used. @option{--no-ask-cert-level} disables this option. This option
defaults to no.
@item --default-cert-level @var{n}
@opindex default-cert-level
The default to use for the check level when signing a key.
0 means you make no particular claim as to how carefully you verified
the key.
1 means you believe the key is owned by the person who claims to own
it but you could not, or did not verify the key at all. This is
useful for a "persona" verification, where you sign the key of a
pseudonymous user.
2 means you did casual verification of the key. For example, this
could mean that you verified the key fingerprint and checked the
user ID on the key against a photo ID.
3 means you did extensive verification of the key. For example, this
could mean that you verified the key fingerprint with the owner of the
key in person, and that you checked, by means of a hard to forge
document with a photo ID (such as a passport) that the name of the key
owner matches the name in the user ID on the key, and finally that you
verified (by exchange of email) that the email address on the key
belongs to the key owner.
Note that the examples given above for levels 2 and 3 are just that:
examples. In the end, it is up to you to decide just what "casual"
and "extensive" mean to you.
This option defaults to 0 (no particular claim).
@item --min-cert-level
@opindex min-cert-level
When building the trust database, treat any signatures with a
certification level below this as invalid. Defaults to 2, which
disregards level 1 signatures. Note that level 0 "no particular
claim" signatures are always accepted.
@item --trusted-key @var{long key ID or fingerprint}
@opindex trusted-key
Assume that the specified key (which should be given as fingerprint)
is as trustworthy as one of your own secret keys. This option is
useful if you don't want to keep your secret keys (or one of them)
online but still want to be able to check the validity of a given
recipient's or signator's key. If the given key is not locally
available but an LDAP keyserver is configured the missing key is
imported from that server. The value "none" is explicitly allowed to
distinguish between the use of any trusted-key option and no use of
this option at all (e.g. due to the @option{--no-options} option).
@item --add-desig-revoker [sensitive:]@var{fingerprint}
@opindex add-desig-revoker
Add the key specified by @var{fingerprint} as a designated revoker to
newly created keys. If the fingerprint is prefixed with the keyword
-``sensitive:'' that info is normally not exported wit the key. This
+``sensitive:'' that info is normally not exported with the key. This
option may be given several time to add more than one designated
revoker. If the keyword ``clear'' is used instead of a fingerprint,
all designated options previously encountered are discarded.
Designated revokers are marked on the key as non-revocable. Note that
a designated revoker specified using a parameter file will also be
added to the key.
@item --trust-model @{pgp|classic|tofu|tofu+pgp|direct|always|auto@}
@opindex trust-model
Set what trust model GnuPG should follow. The models are:
@table @asis
@item pgp
@opindex trust-model:pgp
This is the Web of Trust combined with trust signatures as used in PGP
5.x and later. This is the default trust model when creating a new
trust database.
@item classic
@opindex trust-model:classic
This is the standard Web of Trust as introduced by PGP 2.
@item tofu
@opindex trust-model:tofu
@anchor{trust-model-tofu}
TOFU stands for Trust On First Use. In this experimental trust
model, the first
time a key is seen, it is memorized. If later another key with a
user id with the same email address is seen, both keys are marked as
suspect. In that case, the next time either is used, a warning is
displayed describing the conflict, why it might have occurred
(either the user generated a new key and failed to cross sign the
old and new keys, the key is forgery, or a man-in-the-middle attack
is being attempted), and the user is prompted to manually confirm
the validity of the key in question.
Because a potential attacker is able to control the email address
and thereby circumvent the conflict detection algorithm by using an
email address that is similar in appearance to a trusted email
address, whenever a message is verified, statistics about the number
of messages signed with the key are shown. In this way, a user can
easily identify attacks using fake keys for regular correspondents.
When compared with the Web of Trust, TOFU offers significantly
weaker security guarantees. In particular, TOFU only helps ensure
consistency (that is, that the binding between a key and email
address doesn't change). A major advantage of TOFU is that it
requires little maintenance to use correctly. To use the web of
trust properly, you need to actively sign keys and mark users as
trusted introducers. This is a time-consuming process and anecdotal
evidence suggests that even security-conscious users rarely take the
time to do this thoroughly and instead rely on an ad-hoc TOFU
process.
In the TOFU model, policies are associated with bindings between
keys and email addresses (which are extracted from user ids and
normalized). There are five policies, which can be set manually
using the @option{--tofu-policy} option. The default policy can be
set using the @option{--tofu-default-policy} option.
The TOFU policies are: @code{auto}, @code{good}, @code{unknown},
@code{bad} and @code{ask}. The @code{auto} policy is used by
default (unless overridden by @option{--tofu-default-policy}) and
marks a binding as marginally trusted. The @code{good},
@code{unknown} and @code{bad} policies mark a binding as fully
trusted, as having unknown trust or as having trust never,
respectively. The @code{unknown} policy is useful for just using
TOFU to detect conflicts, but to never assign positive trust to a
binding. The final policy, @code{ask} prompts the user to indicate
the binding's trust. If batch mode is enabled (or input is
inappropriate in the context), then the user is not prompted and the
@code{undefined} trust level is returned.
@item tofu+pgp
@opindex trust-model:tofu+pgp
This experimental trust model combines TOFU with the Web of Trust.
This is done
by computing the trust level for each model and then taking the
maximum trust level where the trust levels are ordered as follows:
@code{unknown < undefined < marginal < fully < ultimate < expired <
never}.
By setting @option{--tofu-default-policy=unknown}, this model can be
used to implement the web of trust with TOFU's conflict detection
algorithm, but without its assignment of positive trust values,
which some security-conscious users don't like.
@item direct
@opindex trust-model:direct
Key validity is set directly by the user and not calculated via the
Web of Trust. This model is solely based on the key and does
not distinguish user IDs. Note that when changing to another trust
model the trust values assigned to a key are transformed into
ownertrust values, which also indicate how you trust the owner of
the key to sign other keys.
@item always
@opindex trust-model:always
Skip key validation and assume that used keys are always fully
valid. You generally won't use this unless you are using some
external validation scheme. This option also suppresses the
"[uncertain]" tag printed with signature checks when there is no
evidence that the user ID is bound to the key. Note that this
trust model still does not allow the use of expired, revoked, or
disabled keys.
@item auto
@opindex trust-model:auto
Select the trust model depending on whatever the internal trust
database says. This is the default model if such a database already
exists. Note that a tofu trust model is not considered here and
must be enabled explicitly.
@end table
@item --always-trust
@opindex always-trust
Identical to @option{--trust-model always}.
@item --assert-signer @var{fpr_or_file}
@opindex assert-signer
This option checks whether at least one valid signature on a file has
been made with the specified key. The key is either specified as a
fingerprint or a file listing fingerprints. The fingerprint must be
given or listed in compact format (no colons or spaces in between).
This option can be given multiple times and each fingerprint is
checked against the signing key as well as the corresponding primary
key. If @var{fpr_or_file} specifies a file, empty lines are ignored
as well as all lines starting with a hash sign. With this option gpg
is guaranteed to return with an exit code of 0 if and only if a
signature has been encountered, is valid, and the key matches one of
the fingerprints given by this option.
@item --assert-pubkey-algo @var{algolist}
@opindex assert-pubkey-algo
During data signature verification this options checks whether the
used public key algorithm matches the algorithms given by
@var{algolist}. This option can be given multiple times to
concatenate more algorithms to the list; the delimiter of the list are
either commas or spaces.
The algorithm names given in the list may either be verbatim names
like "ed25519" with an optional leading single equal sign, or being
prefixed with ">", ">=", "<=", or "<". That prefix operator is
applied to the number part of the algorithm name; for example 2048 in
"rsa2048" or 384 in "brainpoolP384r1". If the the leading non-digits
in the name matches, the prefix operator is used to compare the number
part, a trailing suffix is ignored in this case. For example an
algorithm list ">rsa3000, >=brainpool384r1, =ed25519" allows RSA
signatures with more that 3000 bits, Brainpool curves 384 and 512,
and the ed25519 algorithm.
With this option gpg (and also gpgv) is guaranteed to return with an
exit code of 0 if and only if all valid signatures on data are made
using a matching algorithm from the given list.
@item --auto-key-locate @var{mechanisms}
@itemx --no-auto-key-locate
@opindex auto-key-locate
GnuPG can automatically locate and retrieve keys as needed using this
option. This happens when encrypting to an email address (in the
"user@@example.com" form), and there are no "user@@example.com" keys
on the local keyring. This option takes any number of the mechanisms
listed below, in the order they are to be tried. Instead of listing
the mechanisms as comma delimited arguments, the option may also be
given several times to add more mechanism. The option
@option{--no-auto-key-locate} or the mechanism "clear" resets the
list. The default is "local,wkd".
@table @asis
@item cert
Locate a key using DNS CERT, as specified in RFC-4398.
@item dane
Locate a key using DANE, as specified
in draft-ietf-dane-openpgpkey-05.txt.
@item wkd
Locate a key using the Web Key Directory protocol.
@item ldap
Using DNS Service Discovery, check the domain in question for any LDAP
keyservers to use. If this fails, attempt to locate the key using the
PGP Universal method of checking @samp{ldap://keys.(thedomain)}.
@item ntds
Locate the key using the Active Directory (Windows only). This
method also allows one to search by fingerprint using the command
@option{--locate-external-key}. Note that this mechanism is
actually a shortcut for the mechanism @samp{keyserver} but using
"ldap:///" as the keyserver.
@item keyserver
Locate a key using a keyserver. This method also allows one to search
by fingerprint using the command @option{--locate-external-key} if
any of the configured keyservers is an LDAP server.
@item keyserver-URL
In addition, a keyserver URL as used in the @command{dirmngr}
configuration may be used here to query that particular keyserver.
This method also allows one to search by fingerprint using the command
@option{--locate-external-key} if the URL specifies an LDAP server.
@item local
Locate the key using the local keyrings. This mechanism allows the user to
select the order a local key lookup is done. Thus using
@samp{--auto-key-locate local} is identical to
@option{--no-auto-key-locate}.
@item nodefault
This flag disables the standard local key lookup, done before any of the
mechanisms defined by the @option{--auto-key-locate} are tried. The
position of this mechanism in the list does not matter. It is not
required if @code{local} is also used.
@item clear
Clear all defined mechanisms. This is useful to override
mechanisms given in a config file. Note that a @code{nodefault} in
@var{mechanisms} will also be cleared unless it is given after the
@code{clear}.
@end table
@item --auto-key-import
@itemx --no-auto-key-import
@opindex auto-key-import
@opindex no-auto-key-import
This is an offline mechanism to get a missing key for signature
verification and for later encryption to this key. If this option is
enabled and a signature includes an embedded key, that key is
used to verify the signature and on verification success the key is
imported. The default is @option{--no-auto-key-import}.
On the sender (signing) site the option @option{--include-key-block}
needs to be used to put the public part of the signing key as “Key
Block subpacket” into the signature.
@item --auto-key-retrieve
@itemx --no-auto-key-retrieve
@opindex auto-key-retrieve
@opindex no-auto-key-retrieve
These options enable or disable the automatic retrieving of keys from
a keyserver when verifying signatures made by keys that are not on the
local keyring. The default is @option{--no-auto-key-retrieve}.
The order of methods tried to lookup the key is:
1. If the option @option{--auto-key-import} is set and the signatures
includes an embedded key, that key is used to verify the signature and
on verification success that key is imported.
2. If a preferred keyserver is specified in the signature and the
option @option{honor-keyserver-url} is active (which is not the
default), that keyserver is tried. Note that the creator of the
signature uses the option @option{--sig-keyserver-url} to specify the
preferred keyserver for data signatures.
3. If the signature has the Signer's UID set (e.g., using
@option{--sender} while creating the signature) a Web Key Directory
(WKD) lookup is done. This is the default configuration but can be
disabled by removing WKD from the auto-key-locate list or by using the
option @option{--disable-signer-uid}.
4. If any keyserver is configured and the Issuer Fingerprint is part
of the signature (since GnuPG 2.1.16), the configured keyservers are
tried.
Note that this option makes a "web bug" like behavior possible.
Keyserver or Web Key Directory operators can see which keys you
request, so by sending you a message signed by a brand new key (which
you naturally will not have on your local keyring), the operator can
tell both your IP address and the time when you verified the
signature.
@item --keyid-format @{none|short|0xshort|long|0xlong@}
@opindex keyid-format
Select how to display key IDs. "none" does not show the key ID at all
but shows the fingerprint in a separate line. "short" is the
traditional 8-character key ID. "long" is the more accurate (but less
convenient) 16-character key ID. Add an "0x" to either to include an
"0x" at the beginning of the key ID, as in 0x99242560. Note that this
option is ignored if the option @option{--with-colons} is used.
@item --keyserver @var{name}
@opindex keyserver
This option is deprecated --- please use the @option{--keyserver} in
@file{dirmngr.conf} instead.
Use @var{name} as your keyserver. This is the server that
@option{--receive-keys}, @option{--send-keys}, and @option{--search-keys}
will communicate with to receive keys from, send keys to, and search for
keys on. The format of the @var{name} is a URI:
`scheme:[//]keyservername[:port]' The scheme is the type of keyserver:
"hkp"/"hkps" for the HTTP (or compatible) keyservers or "ldap"/"ldaps"
for the LDAP keyservers. Note that your particular installation of
GnuPG may have other keyserver types available as well. Keyserver
schemes are case-insensitive.
Most keyservers synchronize with each other, so there is generally no
need to send keys to more than one server. The keyserver
@code{hkp://keys.gnupg.net} uses round robin DNS to give a different
keyserver each time you use it.
@item --keyserver-options @{@var{name}=@var{value}@}
@opindex keyserver-options
This is a space or comma delimited string that gives options for the
keyserver. Options can be prefixed with a `no-' to give the opposite
meaning. Valid import-options or export-options may be used here as
well to apply to importing (@option{--recv-key}) or exporting
(@option{--send-key}) a key from a keyserver. While not all options
are available for all keyserver types, some common options are:
@table @asis
@item include-revoked
When searching for a key with @option{--search-keys}, include keys that
are marked on the keyserver as revoked. Note that not all keyservers
differentiate between revoked and unrevoked keys, and for such
keyservers this option is meaningless. Note also that most keyservers do
not have cryptographic verification of key revocations, and so turning
this option off may result in skipping keys that are incorrectly marked
as revoked.
@item include-disabled
When searching for a key with @option{--search-keys}, include keys that
are marked on the keyserver as disabled. Note that this option is not
used with HKP keyservers.
@item auto-key-retrieve
This is an obsolete alias for the option @option{auto-key-retrieve}.
Please do not use it; it will be removed in future versions..
@item honor-keyserver-url
When using @option{--refresh-keys}, if the key in question has a preferred
keyserver URL, then use that preferred keyserver to refresh the key
from. In addition, if auto-key-retrieve is set, and the signature
being verified has a preferred keyserver URL, then use that preferred
keyserver to fetch the key from. Note that this option introduces a
"web bug": The creator of the key can see when the keys is
refreshed. Thus this option is not enabled by default.
@item include-subkeys
When receiving a key, include subkeys as potential targets. Note that
this option is not used with HKP keyservers, as they do not support
retrieving keys by subkey id.
@item timeout
@itemx http-proxy=@var{value}
@itemx verbose
@itemx debug
@itemx check-cert
@item ca-cert-file
These options have no more function since GnuPG 2.1. Use the
@code{dirmngr} configuration options instead.
@end table
The default list of options is: "self-sigs-only, import-clean,
repair-keys, repair-pks-subkey-bug, export-attributes". However, if
the actual used source is an LDAP server "no-self-sigs-only" is
assumed unless "self-sigs-only" has been explicitly configured.
@item --completes-needed @var{n}
@opindex compliant-needed
Number of completely trusted users to introduce a new
key signer (defaults to 1).
@item --marginals-needed @var{n}
@opindex marginals-needed
Number of marginally trusted users to introduce a new
key signer (defaults to 3)
@item --tofu-default-policy @{auto|good|unknown|bad|ask@}
@opindex tofu-default-policy
The default TOFU policy (defaults to @code{auto}). For more
information about the meaning of this option, @pxref{trust-model-tofu}.
@item --max-cert-depth @var{n}
@opindex max-cert-depth
Maximum depth of a certification chain (default is 5).
@item --no-sig-cache
@opindex no-sig-cache
Do not cache the verification status of key signatures.
Caching gives a much better performance in key listings. However, if
you suspect that your public keyring is not safe against write
modifications, you can use this option to disable the caching. It
probably does not make sense to disable it because all kind of damage
can be done if someone else has write access to your public keyring.
@item --auto-check-trustdb
@itemx --no-auto-check-trustdb
@opindex auto-check-trustdb
If GnuPG feels that its information about the Web of Trust has to be
updated, it automatically runs the @option{--check-trustdb} command
internally. This may be a time consuming
process. @option{--no-auto-check-trustdb} disables this option.
@item --use-agent
@itemx --no-use-agent
@opindex use-agent
This is dummy option. @command{@gpgname} always requires the agent.
@item --gpg-agent-info
@opindex gpg-agent-info
This is dummy option. It has no effect when used with @command{@gpgname}.
@item --agent-program @var{file}
@opindex agent-program
Specify an agent program to be used for secret key operations. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}. Note that the pipe symbol (@code{|}) is
used for a regression test suite hack and may thus not be used in the
file name.
@item --dirmngr-program @var{file}
@opindex dirmngr-program
Specify a dirmngr program to be used for keyserver access. The
default value is @file{@value{BINDIR}/dirmngr}.
@item --disable-dirmngr
Entirely disable the use of the Dirmngr.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent or the dirmngr if it has not yet been
started and its service is required. This option is mostly useful on
machines where the connection to gpg-agent has been redirected to
another machines. If dirmngr is required on the remote machine, it
may be started manually using @command{gpgconf --launch dirmngr}.
@item --lock-once
@opindex lock-once
Lock the databases the first time a lock is requested
and do not release the lock until the process
terminates.
@item --lock-multiple
@opindex lock-multiple
Release the locks every time a lock is no longer
needed. Use this to override a previous @option{--lock-once}
from a config file.
@item --lock-never
@opindex lock-never
Disable locking entirely. This option should be used only in very
special environments, where it can be assured that only one process
is accessing those files. A bootable floppy with a stand-alone
encryption system will probably use this. Improper usage of this
option may lead to data and key corruption.
@item --exit-on-status-write-error
@opindex exit-on-status-write-error
This option will cause write errors on the status FD to immediately
terminate the process. That should in fact be the default but it never
worked this way and thus we need an option to enable this, so that the
change won't break applications which close their end of a status fd
connected pipe too early. Using this option along with
@option{--enable-progress-filter} may be used to cleanly cancel long
running gpg operations.
@item --limit-card-insert-tries @var{n}
@opindex limit-card-insert-tries
With @var{n} greater than 0 the number of prompts asking to insert a
smartcard gets limited to N-1. Thus with a value of 1 gpg won't at
all ask to insert a card if none has been inserted at startup. This
option is useful in the configuration file in case an application does
not know about the smartcard support and waits ad infinitum for an
inserted card.
@item --no-random-seed-file
@opindex no-random-seed-file
GnuPG uses a file to store its internal random pool over invocations.
This makes random generation faster; however sometimes write operations
are not desired. This option can be used to achieve that with the cost of
slower random generation.
@item --no-greeting
@opindex no-greeting
Suppress the initial copyright message.
@item --no-secmem-warning
@opindex no-secmem-warning
Suppress the warning about "using insecure memory".
@item --no-permission-warning
@opindex permission-warning
Suppress the warning about unsafe file and home directory (@option{--homedir})
permissions. Note that the permission checks that GnuPG performs are
not intended to be authoritative, but rather they simply warn about
certain common permission problems. Do not assume that the lack of a
warning means that your system is secure.
Note that the warning for unsafe @option{--homedir} permissions cannot be
suppressed in the gpg.conf file, as this would allow an attacker to
place an unsafe gpg.conf file in place, and use this file to suppress
warnings about itself. The @option{--homedir} permissions warning may only be
suppressed on the command line.
@item --require-secmem
@itemx --no-require-secmem
@opindex require-secmem
Refuse to run if GnuPG cannot get secure memory. Defaults to no
(i.e., run, but give a warning).
@item --require-cross-certification
@itemx --no-require-cross-certification
@opindex require-cross-certification
When verifying a signature made from a subkey, ensure that the cross
certification "back signature" on the subkey is present and valid. This
protects against a subtle attack against subkeys that can sign.
Defaults to @option{--require-cross-certification} for
@command{@gpgname}.
@item --expert
@itemx --no-expert
@opindex expert
Allow the user to do certain nonsensical or "silly" things like
signing an expired or revoked key, or certain potentially incompatible
things like generating unusual key types. This also disables certain
warning messages about potentially incompatible actions. As the name
implies, this option is for experts only. If you don't fully
understand the implications of what it allows you to do, leave this
off. @option{--no-expert} disables this option.
@end table
@c *******************************************
@c ******** KEY RELATED OPTIONS ************
@c *******************************************
@node GPG Key related Options
@subsection Key related options
@table @gnupgtabopt
@item --recipient @var{name}
@itemx -r
@opindex recipient
Encrypt for user id @var{name}. If this option or
@option{--hidden-recipient} is not specified, GnuPG asks for the user-id
unless @option{--default-recipient} is given.
@item --hidden-recipient @var{name}
@itemx -R
@opindex hidden-recipient
Encrypt for user ID @var{name}, but hide the key ID of this user's
key. This option helps to hide the receiver of the message and is a
limited countermeasure against traffic analysis. If this option or
@option{--recipient} is not specified, GnuPG asks for the user ID unless
@option{--default-recipient} is given.
@item --recipient-file @var{file}
@itemx -f
@opindex recipient-file
This option is similar to @option{--recipient} except that it
encrypts to a key stored in the given file. @var{file} must be the
name of a file containing exactly one key. @command{@gpgname} assumes that
the key in this file is fully valid.
@item --hidden-recipient-file @var{file}
@itemx -F
@opindex hidden-recipient-file
This option is similar to @option{--hidden-recipient} except that it
encrypts to a key stored in the given file. @var{file} must be the
name of a file containing exactly one key. @command{@gpgname} assumes that
the key in this file is fully valid.
@item --encrypt-to @var{name}
@opindex encrypt-to
Same as @option{--recipient} but this one is intended for use in the
options file and may be used with your own user-id as an
"encrypt-to-self". It is suggested to use a fingerprint or at least a
long keyID for @var{name}. These keys are only used when there are
other recipients given either by use of @option{--recipient} or by the
asked user id. No trust checking is performed for these user ids and
even disabled keys can be used.
@item --hidden-encrypt-to @var{name}
@opindex hidden-encrypt-to
Same as @option{--hidden-recipient} but this one is intended for use
in the options file and may be used with your own user-id as a hidden
"encrypt-to-self". It is suggested to use a fingerprint or at least a
long keyID for @var{name}. These keys are only used when there are
other recipients given either by use of @option{--recipient} or by the
asked user id. No trust checking is performed for these user ids and
even disabled keys can be used.
@item --no-encrypt-to
@opindex no-encrypt-to
Disable the use of all @option{--encrypt-to} and
@option{--hidden-encrypt-to} keys.
@item --group @{@var{name}=@var{value}@}
@opindex group
Sets up a named group, which is similar to aliases in email programs.
Any time the group name is a recipient (@option{-r} or
@option{--recipient}), it will be expanded to the values
specified. Multiple groups with the same name are automatically merged
into a single group.
The values are @code{key IDs} or fingerprints, but any key description
is accepted. Note that a value with spaces in it will be treated as
two different values. Note also there is only one level of expansion
--- you cannot make an group that points to another group. When used
from the command line, it may be necessary to quote the argument to
this option to prevent the shell from treating it as multiple
arguments.
@item --ungroup @var{name}
@opindex ungroup
Remove a given entry from the @option{--group} list.
@item --no-groups
@opindex no-groups
Remove all entries from the @option{--group} list.
@item --local-user @var{name}
@itemx -u
@opindex local-user
Use @var{name} as the key to sign with. Note that this option overrides
@option{--default-key}.
@item --sender @var{mbox}
@opindex sender
This option has two purposes. @var{mbox} must either be a complete
user ID containing a proper mail address or just a plain mail address.
The option can be given multiple times.
When creating a signature this option tells gpg the signing key's user
id used to make the signature and embeds that user ID into the created
signature (using OpenPGP's ``Signer's User ID'' subpacket). If the
option is given multiple times a suitable user ID is picked. However,
if the signing key was specified directly by using a mail address
(i.e., not by using a fingerprint or key ID) this option is used and
the mail address is embedded in the created signature.
When verifying a signature @var{mbox} is used to restrict the
information printed by the TOFU code to matching user IDs. If the
option is used and the signature contains a ``Signer's User ID''
subpacket that information is is also used to restrict the printed
information. Note that GnuPG considers only the mail address part of
a User ID.
If this option or the said subpacket is available the TRUST lines as
printed by option @option{status-fd} correspond to the corresponding
User ID; if no User ID is known the TRUST lines are computed directly
on the key and do not give any information about the User ID. In the
latter case it his highly recommended to scripts and other frontends
to evaluate the VALIDSIG line, retrieve the key and print all User IDs
along with their validity (trust) information.
@item --try-secret-key @var{name}
@opindex try-secret-key
For hidden recipients GPG needs to know the keys to use for trial
decryption. The key set with @option{--default-key} is always tried
first, but this is often not sufficient. This option allows setting more
keys to be used for trial decryption. Although any valid user-id
specification may be used for @var{name} it makes sense to use at least
the long keyid to avoid ambiguities. Note that gpg-agent might pop up a
pinentry for a lot keys to do the trial decryption. If you want to stop
all further trial decryption you may use close-window button instead of
the cancel button.
@item --try-all-secrets
@opindex try-all-secrets
Don't look at the key ID as stored in the message but try all secret
keys in turn to find the right decryption key. This option forces the
behaviour as used by anonymous recipients (created by using
@option{--throw-keyids} or @option{--hidden-recipient}) and might come
handy in case where an encrypted message contains a bogus key ID.
@item --skip-hidden-recipients
@itemx --no-skip-hidden-recipients
@opindex skip-hidden-recipients
@opindex no-skip-hidden-recipients
During decryption skip all anonymous recipients. This option helps in
the case that people use the hidden recipients feature to hide their
own encrypt-to key from others. If one has many secret keys this
may lead to a major annoyance because all keys are tried in turn to
decrypt something which was not really intended for it. The drawback
of this option is that it is currently not possible to decrypt a
message which includes real anonymous recipients.
@end table
@c *******************************************
@c ******** INPUT AND OUTPUT ***************
@c *******************************************
@node GPG Input and Output
@subsection Input and Output
@table @gnupgtabopt
@item --armor
@itemx -a
@opindex armor
Create ASCII armored output. The default is to create the binary
OpenPGP format.
@item --no-armor
@opindex no-armor
Assume the input data is not in ASCII armored format.
@item --output @var{file}
@itemx -o @var{file}
@opindex output
Write output to @var{file}. To write to stdout use @code{-} as the
filename.
@item --max-output @var{n}
@opindex max-output
This option sets a limit on the number of bytes that will be generated
when processing a file. Since OpenPGP supports various levels of
compression, it is possible that the plaintext of a given message may be
significantly larger than the original OpenPGP message. While GnuPG
works properly with such messages, there is often a desire to set a
maximum file size that will be generated before processing is forced to
stop by the OS limits. Defaults to 0, which means "no limit".
@item --chunk-size @var{n}
@opindex chunk-size
The AEAD encryption mode encrypts the data in chunks so that a
receiving side can check for transmission errors or tampering at the
end of each chunk and does not need to delay this until all data has
been received. The used chunk size is 2^@var{n} byte. The lowest
allowed value for @var{n} is 6 (64 byte) and the largest is the
default of 22 which creates chunks not larger than 4 MiB.
@item --input-size-hint @var{n}
@opindex input-size-hint
This option can be used to tell GPG the size of the input data in
bytes. @var{n} must be a positive base-10 number. This option is
only useful if the input is not taken from a file. GPG may use this
hint to optimize its buffer allocation strategy. It is also used by
the @option{--status-fd} line ``PROGRESS'' to provide a value for
``total'' if that is not available by other means.
@item --key-origin @var{string}[,@var{url}]
@opindex key-origin
gpg can track the origin of a key. Certain origins are implicitly
known (e.g., keyserver, web key directory) and set. For a standard
import the origin of the keys imported can be set with this option.
To list the possible values use "help" for @var{string}. Some origins
can store an optional @var{url} argument. That URL can appended to
@var{string} after a comma.
@item --import-options @var{parameters}
@opindex import-options
This is a space or comma delimited string that gives options for
importing keys. Options can be prepended with a `no-' to give the
opposite meaning. The options are:
@table @asis
@item import-local-sigs
Allow importing key signatures marked as "local". This is not
generally useful unless a shared keyring scheme is being used.
Defaults to no.
@item keep-ownertrust
Normally possible still existing ownertrust values of a key are
cleared if a key is imported. This is in general desirable so that
a formerly deleted key does not automatically gain an ownertrust
values merely due to import. On the other hand it is sometimes
necessary to re-import a trusted set of keys again but keeping
already assigned ownertrust values. This can be achieved by using
this option.
@item repair-pks-subkey-bug
During import, attempt to repair the damage caused by the PKS keyserver
bug (pre version 0.9.6) that mangles keys with multiple subkeys. Note
that this cannot completely repair the damaged key as some crucial data
is removed by the keyserver, but it does at least give you back one
subkey. Defaults to no for regular @option{--import} and to yes for
keyserver @option{--receive-keys}.
@item import-show
@itemx show-only
Show a listing of the key as imported right before it is stored.
This can be combined with the option @option{--dry-run} to only look
at keys; the option @option{show-only} is a shortcut for this
combination. The command @option{--show-keys} is another shortcut
for this. Note that suffixes like '#' for "sec" and "sbb" lines
may or may not be printed.
@item import-export
Run the entire import code but instead of storing the key to the
local keyring write it to the output. The export option
@option{export-dane} affect the output. This option can for example
be used to remove all invalid parts from a key without the
need to store it.
@item merge-only
During import, allow key updates to existing keys, but do not allow
any new keys to be imported. Defaults to no.
@item import-clean
After import, compact (remove all signatures except the
self-signature) any user IDs from the new key that are not usable.
Then, remove any signatures from the new key that are not usable.
This includes signatures that were issued by keys that are not present
on the keyring. This option is the same as running the @option{--edit-key}
command "clean" after import. Defaults to no.
@item self-sigs-only
Accept only self-signatures while importing a key. All other key
signatures are skipped at an early import stage. This option can be
used with @code{keyserver-options} to mitigate attempts to flood a
key with bogus signatures from a keyserver. The drawback is that
all other valid key signatures, as required by the Web of Trust are
also not imported. Note that when using this option along with
import-clean it suppresses the final clean step after merging the
imported key into the existing key.
@item ignore-attributes
Ignore all attribute user IDs (photo IDs) and their signatures while
importing a key.
@item repair-keys
After import, fix various problems with the
keys. For example, this reorders signatures, and strips duplicate
signatures. Defaults to yes.
@item bulk-import
When used the keyboxd (option @option{use-keyboxd} in @file{common.conf})
does the import within a single
transaction.
@item import-minimal
Import the smallest key possible. This removes all signatures except
the most recent self-signature on each user ID. This option is the
same as running the @option{--edit-key} command "minimize" after import.
Defaults to no.
@item restore
@itemx import-restore
Import in key restore mode. This imports all data which is usually
skipped during import; including all GnuPG specific data. All other
contradicting options are overridden.
@end table
@item --import-filter @{@var{name}=@var{expr}@}
@itemx --export-filter @{@var{name}=@var{expr}@}
@opindex import-filter
@opindex export-filter
These options define an import/export filter which are applied to the
imported/exported keyblock right before it will be stored/written.
@var{name} defines the type of filter to use, @var{expr} the
expression to evaluate. The option can be used several times which
then appends more expression to the same @var{name}.
@noindent
The available filter types are:
@table @asis
@item keep-uid
This filter will keep a user id packet and its dependent packets in
the keyblock if the expression evaluates to true.
@item drop-subkey
This filter drops the selected subkeys.
Currently only implemented for @option{--export-filter}.
@item drop-sig
This filter drops the selected key signatures on user ids.
Self-signatures are not considered.
Currently only implemented for @option{--import-filter}.
@item select
This filter is only implemented by @option{--list-filter}. All
property names may be used.
@end table
For the syntax of the expression see the chapter "FILTER EXPRESSIONS".
The property names for the expressions depend on the actual filter
type and are indicated in the following table. Note that all property
names may also be used by @option{--list-filter}.
Property names may be prefix with a scope delimited by a slash. Valid
scopes are "pub" for public and secret primary keys, "sub" for public
and secret subkeys, "uid" for for user-ID packets, and "sig" for
signature packets. Invalid scopes are currently ignored.
The available properties are:
@table @asis
@item uid
A string with the user id. (keep-uid)
@item mbox
The addr-spec part of a user id with mailbox or the empty string.
(keep-uid)
@item algostr
A string with the key algorithm description. For example "rsa3072"
or "ed25519".
@item key_algo
A number with the public key algorithm of a key or subkey packet.
(drop-subkey)
@item key_size
A number with the effective key size of a key or subkey packet.
(drop-subkey)
@item key_created
@itemx key_created_d
The first is the timestamp a public key or subkey packet was
created. The second is the same but given as an ISO string,
e.g., "2016-08-17". (drop-subkey)
@item key_expires
@itemx key_expires_d
The expiration time of a public key or subkey or 0 if it does not
expire. The second is the same but given as an ISO date string or
an empty string e.g., "2038-01-19".
@item fpr
The hexified fingerprint of the current subkey or primary key.
(drop-subkey)
@item primary
Boolean indicating whether the user id is the primary one. (keep-uid)
@item expired
Boolean indicating whether a user id (keep-uid), a key (drop-subkey), or a
signature (drop-sig) expired.
@item revoked
Boolean indicating whether a user id (keep-uid) or a key (drop-subkey) has
been revoked.
@item disabled
Boolean indicating whether a primary key is disabled.
@item secret
Boolean indicating whether a key or subkey is a secret one.
(drop-subkey)
@item usage
A string indicating the usage flags for the subkey, from the
sequence ``ecsa?''. For example, a subkey capable of just signing
and authentication would be an exact match for ``sa''. (drop-subkey)
@item sig_created
@itemx sig_created_d
The first is the timestamp a signature packet was created. The
second is the same but given as an ISO date string,
e.g., "2016-08-17". (drop-sig)
@item sig_expires
@itemx sig_expires_d
The expiration time of a signature packet or 0 if it does not
expire. The second is the same but given as an ISO date string or
an empty string e.g. "2038-01-19".
@item sig_algo
A number with the public key algorithm of a signature packet. (drop-sig)
@item sig_digest_algo
A number with the digest algorithm of a signature packet. (drop-sig)
@item origin
A string with the key origin or a question mark. For example the
string ``wkd'' is used if a key originated from a Web Key Directory
lookup.
@item lastupd
The timestamp the key was last updated from a keyserver or the Web
Key Directory.
@item url
- A string with the the URL associated wit the last key lookup.
+ A string with the the URL associated with the last key lookup.
@end table
@item --export-options @var{parameters}
@opindex export-options
This is a space or comma delimited string that gives options for
exporting keys. Options can be prepended with a `no-' to give the
opposite meaning. The options are:
@table @asis
@item export-local-sigs
Allow exporting key signatures marked as "local". This is not
generally useful unless a shared keyring scheme is being used.
Defaults to no.
@item export-attributes
Include attribute user IDs (photo IDs) while exporting. Not
including attribute user IDs is useful to export keys that are going
to be used by an OpenPGP program that does not accept attribute user
IDs. Defaults to yes.
@item export-sensitive-revkeys
Include designated revoker information that was marked as
"sensitive". Defaults to no.
@c Since GnuPG 2.1 gpg-agent manages the secret key and thus the
@c export-reset-subkey-passwd hack is not anymore justified. Such use
@c cases may be implemented using a specialized secret key export
@c tool.
@c @item export-reset-subkey-passwd
@c When using the @option{--export-secret-subkeys} command, this option resets
@c the passphrases for all exported subkeys to empty. This is useful
@c when the exported subkey is to be used on an unattended machine where
@c a passphrase doesn't necessarily make sense. Defaults to no.
@item backup
@itemx export-backup
Export for use as a backup. The exported data includes all data
which is needed to restore the key or keys later with GnuPG. The
format is basically the OpenPGP format but enhanced with GnuPG
specific data. All other contradicting options are overridden.
@item export-clean
Compact (remove all signatures from) user IDs on the key being
exported if the user IDs are not usable. Also, do not export any
signatures that are not usable. This includes signatures that were
issued by keys that are not present on the keyring. This option is
the same as running the @option{--edit-key} command "clean" before export
except that the local copy of the key is not modified. Defaults to
no.
@item export-minimal
Export the smallest key possible. This removes all signatures except the
most recent self-signature on each user ID. This option is the same as
running the @option{--edit-key} command "minimize" before export except
that the local copy of the key is not modified. Defaults to no.
@item export-revocs
Export only standalone revocation certificates of the key. This
option does not export revocations of 3rd party certificate
revocations.
@item export-dane
Instead of outputting the key material output OpenPGP DANE records
suitable to put into DNS zone files. An ORIGIN line is printed before
each record to allow diverting the records to the corresponding zone
file.
@item mode1003
Enable the use of a new secret key export format. This format
avoids the re-encryption as required with the current OpenPGP format
and also improves the security of the secret key if it has been
protected with a passphrase. Note that an unprotected key is
exported as-is and thus not secure; the general rule to convey
secret keys in an OpenPGP encrypted file still applies with this
mode. Versions of GnuPG before 2.4.0 are not able to import such a
secret file.
@end table
@item --with-colons
@opindex with-colons
Print key listings delimited by colons. Note that the output will be
encoded in UTF-8 regardless of any @option{--display-charset} setting. This
format is useful when GnuPG is called from scripts and other programs
as it is easily machine parsed. The details of this format are
documented in the file @file{doc/DETAILS}, which is included in the GnuPG
source distribution.
@item --fixed-list-mode
@opindex fixed-list-mode
Do not merge primary user ID and primary key in @option{--with-colon}
listing mode and print all timestamps as seconds since 1970-01-01.
Since GnuPG 2.0.10, this mode is always used and thus this option is
obsolete; it does not harm to use it though.
@item --legacy-list-mode
@opindex legacy-list-mode
Revert to the pre-2.1 public key list mode. This only affects the
human readable output and not the machine interface
(i.e., @code{--with-colons}). Note that the legacy format does not
convey suitable information for elliptic curves.
@item --with-fingerprint
@opindex with-fingerprint
Same as the command @option{--fingerprint} but changes only the format
of the output and may be used together with another command.
@item --with-subkey-fingerprint
@itemx --without-subkey-fingerprint
@opindex with-subkey-fingerprint
@opindex without-subkey-fingerprint
If a fingerprint is printed for the primary key, this option forces
printing of the fingerprint for all subkeys. This could also be
achieved by using the @option{--with-fingerprint} twice but by using
this option along with the default keyid-format "none" a compact
fingerprint is printed. Since version 2.6.0 this option is active by
default; use the ``without'' variant to disable it.
@item --with-v5-fingerprint
@opindex with-v5-fingerprint
In a colon mode listing emit "fp2" lines for version 4 OpenPGP keys
having a v5 style fingerprint of the key.
@item --with-icao-spelling
@opindex with-icao-spelling
Print the ICAO spelling of the fingerprint in addition to the hex digits.
@item --with-keygrip
@opindex with-keygrip
Include the keygrip in the key listings. In @code{--with-colons} mode
this is implicitly enable for secret keys.
@item --with-key-origin
@opindex with-key-origin
Include the locally held information on the origin and last update of
a key in a key listing. In @code{--with-colons} mode this is always
printed. This data is currently experimental and shall not be
considered part of the stable API.
@item --with-wkd-hash
@opindex with-wkd-hash
Print a Web Key Directory identifier along with each user ID in key
listings. This is an experimental feature and semantics may change.
@item --with-secret
@opindex with-secret
Include info about the presence of a secret key in public key listings
done with @code{--with-colons}.
@end table
@c *******************************************
@c ******** OPENPGP OPTIONS ****************
@c *******************************************
@node OpenPGP Options
@subsection OpenPGP protocol specific options
@table @gnupgtabopt
@item --force-ocb
@itemx --force-aead
@opindex force-ocb
@opindex force-aead
Force the use of AEAD encryption over MDC encryption. AEAD is a
modern and faster way to do authenticated encryption than the old MDC
method. @option{--force-aead} is an alias and deprecated. See also
option @option{--chunk-size}.
@item --force-mdc
@itemx --disable-mdc
@opindex force-mdc
@opindex disable-mdc
These options are obsolete and have no effect since GnuPG 2.2.8. The
MDC is always used unless the keys indicate that an AEAD algorithm can
be used in which case AEAD is used. But note: If the creation of a
legacy non-MDC message is exceptionally required, the option
@option{--rfc2440} allows for this.
@item --disable-signer-uid
@opindex disable-signer-uid
By default the user ID of the signing key is embedded in the data signature.
As of now this is only done if the signing key has been specified with
@option{local-user} using a mail address, or with @option{sender}. This
information can be helpful for verifier to locate the key; see option
@option{--auto-key-retrieve}.
@item --include-key-block
@itemx --no-include-key-block
@opindex include-key-block
@opindex no-include-key-block
This option is used to embed the actual signing key into a data
signature. The embedded key is stripped down to a single user id and
includes only the signing subkey used to create the signature as well
as as valid encryption subkeys. All other info is removed from the
key to keep it and thus the signature small. This option is the
OpenPGP counterpart to the @command{gpgsm} option
@option{--include-certs} and allows the recipient of a signed message
to reply encrypted to the sender without using any online directories
to lookup the key. The default is @option{--no-include-key-block}.
See also the option @option{--auto-key-import}.
@item --personal-cipher-preferences @var{string}
@opindex personal-cipher-preferences
Set the list of personal cipher preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked cipher in this list is also
used for the @option{--symmetric} encryption command.
@item --personal-digest-preferences @var{string}
@opindex personal-digest-preferences
Set the list of personal digest preferences to @var{string}. Use
@command{@gpgname --version} to get a list of available algorithms,
and use @code{none} to set no preference at all. This allows the user
to safely override the algorithm chosen by the recipient key
preferences, as GPG will only select an algorithm that is usable by
all recipients. The most highly ranked digest algorithm in this list
is also used when signing without encryption
(e.g., @option{--clear-sign} or @option{--sign}).
@item --personal-compress-preferences @var{string}
@opindex personal-compress-preferences
Set the list of personal compression preferences to @var{string}.
Use @command{@gpgname --version} to get a list of available
algorithms, and use @code{none} to set no preference at all. This
allows the user to safely override the algorithm chosen by the
recipient key preferences, as GPG will only select an algorithm that
is usable by all recipients. The most highly ranked compression
algorithm in this list is also used when there are no recipient keys
to consider (e.g., @option{--symmetric}).
@item --s2k-cipher-algo @var{name}
@opindex s2k-cipher-algo
Use @var{name} as the cipher algorithm for symmetric encryption with
a passphrase if @option{--personal-cipher-preferences} and
@option{--cipher-algo} are not given. The default is @value{GPGSYMENCALGO}.
@item --s2k-digest-algo @var{name}
@opindex s2k-digest-algo
Use @var{name} as the digest algorithm used to mangle the passphrases
for symmetric encryption. The default is SHA-1.
@item --s2k-mode @var{n}
@opindex s2k-mode
Selects how passphrases for symmetric encryption are mangled. If
@var{n} is 0 a plain passphrase (which is in general not recommended)
will be used, a 1 adds a salt (which should not be used) to the
passphrase and a 3 (the default) iterates the whole process a number
of times (see @option{--s2k-count}).
@item --s2k-count @var{n}
@opindex s2k-count
Specify how many times the passphrases mangling for symmetric
encryption is repeated. This value may range between 1024 and
65011712 inclusive. The default is inquired from gpg-agent. Note
that not all values in the 1024--65011712 range are legal and if an
illegal value is selected, GnuPG will round up to the nearest legal
value. This option is only meaningful if @option{--s2k-mode} is set
to the default of 3.
@end table
@c ***************************
@c ******* Compliance ********
@c ***************************
@node Compliance Options
@subsection Compliance options
These options control what GnuPG is compliant to. Only one of these
options may be active at a time. Note that the default setting of
this is nearly always the correct one. See the INTEROPERABILITY WITH
OTHER OPENPGP PROGRAMS section below before using one of these
options.
@table @gnupgtabopt
@item --gnupg
@opindex gnupg
Use standard GnuPG behavior. This is essentially OpenPGP behavior (see
@option{--openpgp}), but with extension from the proposed update to
OpenPGP and with some additional workarounds for common compatibility
problems in different versions of PGP. This is the default option, so
it is not generally needed, but it may be useful to override a
different compliance option in the gpg.conf file.
@item --openpgp
@opindex openpgp
Reset all packet, cipher and digest options to strict OpenPGP
behavior. This option implies @option{--allow-old-cipher-algos}. Use
this option to reset all previous options like @option{--s2k-*},
@option{--cipher-algo}, @option{--digest-algo} and
@option{--compress-algo} to OpenPGP compliant values. All PGP
workarounds are disabled.
@item --rfc4880
@opindex rfc4880
Reset all packet, cipher and digest options to strict RFC-4880
behavior. This option implies @option{--allow-old-cipher-algos}.
Note that this is currently the same thing as @option{--openpgp}.
@item --rfc4880bis
@opindex rfc4880bis
Reset all packet, cipher and digest options to strict according to the
proposed updates of RFC-4880.
@item --rfc2440
@opindex rfc2440
Reset all packet, cipher and digest options to strict RFC-2440
behavior. Note that by using this option encryption packets are
created in a legacy mode without MDC protection. This is dangerous
and should thus only be used for experiments. This option implies
@option{--allow-old-cipher-algos}. See also option
@option{--ignore-mdc-error}.
@item --pgp6
@opindex pgp6
This option is obsolete; it is handled as an alias for @option{--pgp7}
@item --pgp7
@opindex pgp7
Set up all options to be as PGP 7 compliant as possible. This allowed
the ciphers IDEA, 3DES, CAST5,AES128, AES192, AES256, and TWOFISH.,
the hashes MD5, SHA1 and RIPEMD160, and the compression algorithms
none and ZIP. This option implies @option{--escape-from-lines} and
disables @option{--throw-keyids},
@item --pgp8
@opindex pgp8
Set up all options to be as PGP 8 compliant as possible. PGP 8 is a lot
closer to the OpenPGP standard than previous versions of PGP, so all
this does is disable @option{--throw-keyids} and set
@option{--escape-from-lines}. All algorithms are allowed except for the
SHA224, SHA384, and SHA512 digests.
@item --compliance @var{string}
@opindex compliance
This option can be used instead of one of the options above. Valid
values for @var{string} are the above option names (without the double
dash) and possibly others as shown when using "help" for @var{string}.
@item --min-rsa-length @var{n}
@opindex min-rsa-length
This option adjusts the compliance mode "de-vs" for stricter key size
requirements. For example, a value of 3000 turns rsa2048 and dsa2048
keys into non-VS-NfD compliant keys.
@item --require-pqc-encryption
@opindex require-pqc-encryption
This option forces the use of quantum-resistant encryption algorithms.
If not all public keys are quantum-resistant the encryption will fail.
On decryption a warning is printed for all non-quantum-resistant keys.
As of now the Kyber (ML-KEM768 and ML-KEM1024) algorithms are
considered quantum-resistant; Kyber is always used in a composite
scheme along with a classic ECC algorithm.
@item --require-compliance
@opindex require-compliance
To check that data has been encrypted according to the rules of the
current compliance mode, a gpg user needs to evaluate the status
lines. This is allows frontends to handle compliance check in a more
flexible way. However, for scripted use the required evaluation of
the status-line requires quite some effort; this option can be used
instead to make sure that the gpg process exits with a failure if the
compliance rules are not fulfilled. Note that this option has
currently an effect only in "de-vs" mode.
@end table
@c *******************************************
@c ******** ESOTERIC OPTIONS ***************
@c *******************************************
@node GPG Esoteric Options
@subsection Doing things one usually doesn't want to do
@table @gnupgtabopt
@item -n
@itemx --dry-run
@opindex dry-run
Don't make any changes (this is not completely implemented).
@item --list-only
@opindex list-only
Changes the behaviour of some commands. This is like @option{--dry-run} but
different in some cases. The semantic of this option may be extended in
the future. Currently it only skips the actual decryption pass and
therefore enables a fast listing of the encryption keys.
@item -i
@itemx --interactive
@opindex interactive
Prompt before overwriting any files.
@item --compatibility-flags @var{flags}
@opindex compatibility-flags
Set compatibility flags to work around problems due to non-compliant
keys or data. The @var{flags} are given as a comma separated
list of flag names and are OR-ed together. The special flag "none"
clears the list and allows one to start over with an empty list. To get a
list of available flags the sole word "help" can be used.
@item --debug-level @var{level}
@opindex debug-level
Select the debug level for investigating problems. @var{level} may be
a numeric value or by a keyword:
@table @code
@item none
No debugging at all. A value of less than 1 may be used instead of
the keyword.
@item basic
Some basic debug messages. A value between 1 and 2 may be used
instead of the keyword.
@item advanced
More verbose debug messages. A value between 3 and 5 may be used
instead of the keyword.
@item expert
Even more detailed messages. A value between 6 and 8 may be used
instead of the keyword.
@item guru
All of the debug messages you can get. A value greater than 8 may be
used instead of the keyword. The creation of hash tracing files is
only enabled if the keyword is used.
@end table
How these messages are mapped to the actual debugging flags is not
specified and may change with newer releases of this program. They are
however carefully selected to best aid in debugging.
@item --debug @var{flags}
@opindex debug
Set debug flags. All flags are or-ed and @var{flags} may be given
in C syntax (e.g., 0x0042) or as a comma separated list of flag names.
To get a list of all supported flags the single word "help" can be
used. This option is only useful for debugging and the behavior may
change at any time without notice.
@item --debug-all
@opindex debug-all
Set all useful debugging flags.
@item --debug-iolbf
@opindex debug-iolbf
Set stdout into line buffered mode. This option is only honored when
given on the command line.
@item --debug-set-iobuf-size @var{n}
@opindex debug-iolbf
Change the buffer size of the IOBUFs to @var{n} kilobyte. Using 0
prints the current size. Note well: This is a maintainer only option
and may thus be changed or removed at any time without notice.
@item --debug-allow-large-chunks
@opindex debug-allow-large-chunks
To facilitate software tests and experiments this option allows one to
specify a limit of up to 4 EiB (@code{--chunk-size 62}).
@item --debug-ignore-expiration
@opindex debug-ignore-expiration
This option tries to override certain key expiration dates. It is
only useful for certain regression tests.
@item --faked-system-time @var{epoch}
@opindex faked-system-time
This option is only useful for testing; it sets the system time back
or forth to @var{epoch} which is the number of seconds elapsed since
the year 1970. Alternatively @var{epoch} may be given as a full ISO
time string (e.g., "20070924T154812").
If you suffix @var{epoch} with an exclamation mark (!), the system time
will appear to be frozen at the specified time.
@item --full-timestrings
@opindex full-timestrings
Change the format of printed creation and expiration times from just
the date to the date and time. This is in general not useful and the
same information is anyway available in @option{--with-colons} mode.
These longer strings are also not well aligned with other printed
data.
@item --enable-progress-filter
@opindex enable-progress-filter
Enable certain PROGRESS status outputs. This option allows frontends
to display a progress indicator while gpg is processing larger files.
There is a slight performance overhead using it.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}.
See the file DETAILS in the documentation for a listing of them.
@item --status-file @var{file}
@opindex status-file
Same as @option{--status-fd}, except the status data is written to file
@var{file}.
@item --logger-fd @var{n}
@opindex logger-fd
Write log output to file descriptor @var{n} and not to STDERR.
@item --log-file @var{file}
@itemx --logger-file @var{file}
@opindex log-file
Same as @option{--logger-fd}, except the logger data is written to
file @var{file}. Use @file{socket://} to log to s socket.
@item --log-time
@opindex log-time
Prefix all log output with a timestamp even if no log file is used.
@item --attribute-fd @var{n}
@opindex attribute-fd
Write attribute subpackets to the file descriptor @var{n}. This is most
useful for use with @option{--status-fd}, since the status messages are
needed to separate out the various subpackets from the stream delivered
to the file descriptor.
@item --attribute-file @var{file}
@opindex attribute-file
Same as @option{--attribute-fd}, except the attribute data is written to
file @var{file}.
@item --comment @var{string}
@itemx --no-comments
@opindex comment
Use @var{string} as a comment string in cleartext signatures and ASCII
armored messages or keys (see @option{--armor}). The default behavior is
not to use a comment string. @option{--comment} may be repeated multiple
times to get multiple comment strings. @option{--no-comments} removes
all comments. It is a good idea to keep the length of a single comment
below 60 characters to avoid problems with mail programs wrapping such
lines. Note that comment lines, like all other header lines, are not
protected by the signature.
@item --emit-version
@itemx --no-emit-version
@opindex emit-version
Force inclusion of the version string in ASCII armored output. If
given once only the name of the program and the major number is
emitted, given twice the minor is also emitted, given thrice
the micro is added, and given four times an operating system identification
is also emitted. @option{--no-emit-version} (default) disables the version
line.
@item --sig-notation @{@var{name}=@var{value}@}
@itemx --cert-notation @{@var{name}=@var{value}@}
@itemx -N, --set-notation @{@var{name}=@var{value}@}
@opindex sig-notation
@opindex cert-notation
@opindex set-notation
Put the name value pair into the signature as notation data.
@var{name} must consist only of printable characters or spaces, and
must contain a '@@' character in the form keyname@@domain.example.com
(substituting the appropriate keyname and domain name, of course). This
is to help prevent pollution of the IETF reserved notation
namespace. The @option{--expert} flag overrides the '@@'
check. @var{value} may be any printable string; it will be encoded in
UTF-8, so you should check that your @option{--display-charset} is set
correctly. If you prefix @var{name} with an exclamation mark (!), the
notation data will be flagged as critical
(rfc4880:5.2.3.16). @option{--sig-notation} sets a notation for data
signatures. @option{--cert-notation} sets a notation for key signatures
(certifications). @option{--set-notation} sets both.
There are special codes that may be used in notation names. "%k" will
be expanded into the key ID of the key being signed, "%K" into the
long key ID of the key being signed, "%f" into the fingerprint of the
key being signed, "%s" into the key ID of the key making the
signature, "%S" into the long key ID of the key making the signature,
"%g" into the fingerprint of the key making the signature (which might
be a subkey), "%p" into the fingerprint of the primary key of the key
making the signature, "%c" into the signature count from the OpenPGP
smartcard, and "%%" results in a single "%". %k, %K, and %f are only
meaningful when making a key signature (certification), and %c is only
meaningful when using the OpenPGP smartcard.
@item --known-notation @var{name}
@opindex known-notation
Adds @var{name} to a list of known critical signature notations. The
effect of this is that gpg will not mark a signature with a critical
signature notation of that name as bad. Note that gpg already knows
by default about a few critical signatures notation names.
@item --sig-policy-url @var{string}
@itemx --cert-policy-url @var{string}
@itemx --set-policy-url @var{string}
@opindex sig-policy-url
@opindex cert-policy-url
@opindex set-policy-url
Use @var{string} as a Policy URL for signatures (rfc4880:5.2.3.20). If
you prefix it with an exclamation mark (!), the policy URL packet will
be flagged as critical. @option{--sig-policy-url} sets a policy url for
data signatures. @option{--cert-policy-url} sets a policy url for key
signatures (certifications). @option{--set-policy-url} sets both.
The same %-expandos used for notation data are available here as well.
@item --sig-keyserver-url @var{string}
@opindex sig-keyserver-url
Use @var{string} as a preferred keyserver URL for data signatures. If
you prefix it with an exclamation mark (!), the keyserver URL packet
will be flagged as critical.
The same %-expandos used for notation data are available here as well.
@item --set-filename @var{string}
@opindex set-filename
Use @var{string} as the filename which is stored inside messages.
This overrides the default, which is to use the actual filename of the
file being encrypted. Using the empty string for @var{string}
effectively removes the filename from the output.
@item --for-your-eyes-only
@itemx --no-for-your-eyes-only
@opindex for-your-eyes-only
Set the `for your eyes only' flag in the message. This causes GnuPG to
refuse to save the file unless the @option{--output} option is given,
and PGP to use a "secure viewer" with a claimed Tempest-resistant font
to display the message. This option overrides @option{--set-filename}.
@option{--no-for-your-eyes-only} disables this option.
@item --use-embedded-filename
@itemx --no-use-embedded-filename
@opindex use-embedded-filename
Try to create a file with a name as embedded in the data. This can be
a dangerous option as it enables overwriting files by giving the
sender control on how to store files. Defaults to no.
Note that the option @option{--output} overrides this option.
A better approach than using this option is to decrypt to a temporary
filename and then rename that file to the embedded file name after
checking that the embedded filename is harmless. When using the
@option{--status-fd} option gpg tells the filename as part of the
PLAINTEXT status message. If the filename is important, the use of
@command{gpgtar} is another option because gpgtar will never overwrite
a file but decrypt the files to a new directory.
Note also that unless a modern version 5 signature is used the
embedded filename is not part of the signed data.
@item --cipher-algo @var{name}
@opindex cipher-algo
Use @var{name} as cipher algorithm. Running the program with the
command @option{--version} yields a list of supported algorithms. If
this is not used the cipher algorithm is selected from the preferences
stored with the key. In general, you do not want to use this option as
it allows you to violate the OpenPGP standard. The option
@option{--personal-cipher-preferences} is the safe way to accomplish the
same thing.
@item --digest-algo @var{name}
@opindex digest-algo
Use @var{name} as the message digest algorithm. Running the program
with the command @option{--version} yields a list of supported
algorithms. In general, you do not want to use this option as it
allows you to violate the OpenPGP standard. The option
@option{--personal-digest-preferences} is the safe way to accomplish
the same thing.
@item --compress-algo @var{name}
@opindex compress-algo
Use compression algorithm @var{name}. "zlib" is RFC-1950 ZLIB
compression. "zip" is RFC-1951 ZIP compression which is used by PGP.
"bzip2" is a more modern compression scheme that can compress some
things better than zip or zlib, but at the cost of more memory used
during compression and decompression. "uncompressed" or "none"
disables compression. If this option is not used, the default
behavior is to examine the recipient key preferences to see which
algorithms the recipient supports. If all else fails, ZIP is used for
maximum compatibility.
ZLIB may give better compression results than ZIP, as the compression
window size is not limited to 8k. BZIP2 may give even better
compression results than that, but will use a significantly larger
amount of memory while compressing and decompressing. This may be
significant in low memory situations. Note, however, that PGP (all
versions) only supports ZIP compression. Using any algorithm other
than ZIP or "none" will make the message unreadable with PGP. In
general, you do not want to use this option as it allows you to
violate the OpenPGP standard. The option
@option{--personal-compress-preferences} is the safe way to accomplish
the same thing.
@item --cert-digest-algo @var{name}
@opindex cert-digest-algo
Use @var{name} as the message digest algorithm used when signing a
key. Running the program with the command @option{--version} yields a
list of supported algorithms. Be aware that if you choose an
algorithm that GnuPG supports but other OpenPGP implementations do
not, then some users will not be able to use the key signatures you
make, or quite possibly your entire key. Note also that a public key
algorithm must be compatible with the specified digest algorithm; thus
selecting an arbitrary digest algorithm may result in error messages
from lower crypto layers or lead to security flaws.
@item --disable-cipher-algo @var{name}
@opindex disable-cipher-algo
Never allow the use of @var{name} as cipher algorithm.
The given name will not be checked so that a later loaded algorithm
will still get disabled.
@item --disable-pubkey-algo @var{name}
@opindex disable-pubkey-algo
Never allow the use of @var{name} as public key algorithm.
The given name will not be checked so that a later loaded algorithm
will still get disabled.
@item --throw-keyids
@itemx --no-throw-keyids
@opindex throw-keyids
Do not put the recipient key IDs into encrypted messages. This helps to
hide the receivers of the message and is a limited countermeasure
against traffic analysis.@footnote{Using a little social engineering
anyone who is able to decrypt the message can check whether one of the
other recipients is the one he suspects.} On the receiving side, it may
slow down the decryption process because all available secret keys must
be tried. @option{--no-throw-keyids} disables this option. This option
is essentially the same as using @option{--hidden-recipient} for all
recipients.
@item --not-dash-escaped
@opindex not-dash-escaped
This option changes the behavior of cleartext signatures
so that they can be used for patch files. You should not
send such an armored file via email because all spaces
and line endings are hashed too. You can not use this
option for data which has 5 dashes at the beginning of a
line, patch files don't have this. A special armor header
line tells GnuPG about this cleartext signature option.
@item --escape-from-lines
@itemx --no-escape-from-lines
@opindex escape-from-lines
Because some mailers change lines starting with "From " to ">From " it
is good to handle such lines in a special way when creating cleartext
signatures to prevent the mail system from breaking the signature. Note
that all other PGP versions do it this way too. Enabled by
default. @option{--no-escape-from-lines} disables this option.
@item --passphrase-repeat @var{n}
@opindex passphrase-repeat
Specify how many times @command{@gpgname} will request a new
passphrase be repeated. This is useful for helping memorize a
passphrase. Defaults to 1 repetition; can be set to 0 to disable any
passphrase repetition. Note that a @var{n} greater than 1 will pop up
the pinentry window @var{n}+1 times even if a modern pinentry with
two entry fields is used.
@item --passphrase-fd @var{n}
@opindex passphrase-fd
Read the passphrase from file descriptor @var{n}. Only the first line
will be read from file descriptor @var{n}. If you use 0 for @var{n},
the passphrase will be read from STDIN. This can only be used if only
one passphrase is supplied.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --passphrase-file @var{file}
@opindex passphrase-file
Read the passphrase from file @var{file}. Only the first line will
be read from file @var{file}. This can only be used if only one
passphrase is supplied. Obviously, a passphrase stored in a file is
of questionable security if other users can read this file. Don't use
this option if you can avoid it.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --passphrase @var{string}
@opindex passphrase
Use @var{string} as the passphrase. This can only be used if only one
passphrase is supplied. Obviously, this is of very questionable
security on a multi-user system. Don't use this option if you can
avoid it.
Note that since Version 2.0 this passphrase is only used if the
option @option{--batch} has also been given. Since Version 2.1
the @option{--pinentry-mode} also needs to be set to @code{loopback}.
@item --pinentry-mode @var{mode}
@opindex pinentry-mode
Set the pinentry mode to @var{mode}. Allowed values for @var{mode}
are:
@table @asis
@item default
Use the default of the agent, which is @code{ask}.
@item ask
Force the use of the Pinentry.
@item cancel
Emulate use of Pinentry's cancel button.
@item error
Return a Pinentry error (``No Pinentry'').
@item loopback
Redirect Pinentry queries to the caller. Note that in contrast to
Pinentry the user is not prompted again if he enters a bad password.
@end table
@item --no-symkey-cache
@opindex no-symkey-cache
Disable the passphrase cache used for symmetrical en- and decryption.
This cache is based on the message specific salt value
(cf.@: @option{--s2k-mode}).
@item --request-origin @var{origin}
@opindex request-origin
Tell gpg to assume that the operation ultimately originated at
@var{origin}. Depending on the origin certain restrictions are applied
and the Pinentry may include an extra note on the origin. Supported
values for @var{origin} are: @code{local} which is the default,
@code{remote} to indicate a remote origin or @code{browser} for an
operation requested by a web browser.
@item --command-fd @var{n}
@opindex command-fd
This is a replacement for the deprecated shared-memory IPC mode.
If this option is enabled, user input on questions is not expected
from the TTY but from the given file descriptor. It should be used
together with @option{--status-fd}. See the file doc/DETAILS in the source
distribution for details on how to use it.
@item --command-file @var{file}
@opindex command-file
Same as @option{--command-fd}, except the commands are read out of file
@var{file}
@item --allow-non-selfsigned-uid
@itemx --no-allow-non-selfsigned-uid
@opindex allow-non-selfsigned-uid
Allow the import and use of keys with user IDs which are not
self-signed. This is not recommended, as a non self-signed user ID is
trivial to forge. @option{--no-allow-non-selfsigned-uid} disables.
@item --allow-freeform-uid
@opindex allow-freeform-uid
Disable all checks on the form of the user ID while generating a new
one. This option should only be used in very special environments as
it does not ensure the de-facto standard format of user IDs.
@item --ignore-time-conflict
@opindex ignore-time-conflict
GnuPG normally checks that the timestamps associated with keys and
signatures have plausible values. However, sometimes a signature
seems to be older than the key due to clock problems. This option
makes these checks just a warning. See also @option{--ignore-valid-from} for
timestamp issues on subkeys.
@item --ignore-valid-from
@opindex ignore-valid-from
GnuPG normally does not select and use subkeys created in the future.
This option allows the use of such keys and thus exhibits the
pre-1.0.7 behaviour. You should not use this option unless there
is some clock problem. See also @option{--ignore-time-conflict} for timestamp
issues with signatures.
@item --ignore-crc-error
@opindex ignore-crc-error
The ASCII armor used by OpenPGP is protected by a CRC checksum against
transmission errors. Occasionally the CRC gets mangled somewhere on
the transmission channel but the actual content (which is protected by
the OpenPGP protocol anyway) is still okay. This option allows GnuPG
to ignore CRC errors.
@item --ignore-mdc-error
@opindex ignore-mdc-error
This option changes a MDC integrity protection failure into a warning.
It is required to decrypt old messages which did not use an MDC. It
may also be useful if a message is partially garbled, but it is
necessary to get as much data as possible out of that garbled message.
Be aware that a missing or failed MDC can be an indication of an
attack. Use with great caution; see also option @option{--rfc2440}.
@item --allow-old-cipher-algos
@opindex allow-old-cipher-algos
Old cipher algorithms like 3DES, IDEA, or CAST5 encrypt data using
blocks of 64 bits; modern algorithms use blocks of 128 bit instead.
To avoid certain attack on these old algorithms it is suggested not to
encrypt more than 150 MiByte using the same key. For this reason gpg
does not allow the use of 64 bit block size algorithms for encryption
unless this option is specified.
@item --allow-weak-digest-algos
@opindex allow-weak-digest-algos
Signatures made with known-weak digest algorithms are normally
rejected with an ``invalid digest algorithm'' message. This option
allows the verification of signatures made with such weak algorithms.
MD5 is the only digest algorithm considered weak by default. See also
@option{--weak-digest} to reject other digest algorithms.
@item --weak-digest @var{name}
@opindex weak-digest
Treat the specified digest algorithm as weak. Signatures made over
weak digests algorithms are normally rejected. This option can be
supplied multiple times if multiple algorithms should be considered
weak. See also @option{--allow-weak-digest-algos} to disable
rejection of weak digests. MD5 is always considered weak, and does
not need to be listed explicitly.
@item --allow-weak-key-signatures
@opindex allow-weak-key-signatures
To avoid a minor risk of collision attacks on third-party key
signatures made using SHA-1, those key signatures are considered
invalid. This options allows one to override this restriction.
@item --override-compliance-check
This was a temporary introduced option and has no more effect.
@item --no-default-keyring
@opindex no-default-keyring
Do not add the default keyring to the list of keyrings. Note that
GnuPG needs for almost all operations a keyring. Thus if you use this
option and do not provide alternate keyrings via @option{--keyring},
then GnuPG will still use the default keyring.
Note that if the option @option{use-keyboxd} is enabled in
@file{common.conf}, no keyrings are used at all and keys are all
maintained by the keyboxd process in its own database.
@item --no-keyring
@opindex no-keyring
Do not use any keyring at all. This overrides the default and all
options which specify keyrings.
@item --skip-verify
@opindex skip-verify
Skip the signature verification step. This may be
used to make the decryption faster if the signature
verification is not needed.
@item --with-key-data
@opindex with-key-data
Print key listings delimited by colons (like @option{--with-colons}) and
print the public key data.
@item --list-signatures
@opindex list-signatures
@itemx --list-sigs
@opindex list-sigs
Same as @option{--list-keys}, but the signatures are listed too. This
command has the same effect as using @option{--list-keys} with
@option{--with-sig-list}. Note that in contrast to
@option{--check-signatures} the key signatures are not verified. This
command can be used to create a list of signing keys missing in the
local keyring; for example:
@example
gpg --list-sigs --with-colons USERID | \
awk -F: '$1=="sig" && $2=="?" @{if($13)@{print $13@}else@{print $5@}@}'
@end example
@item --fast-list-mode
@opindex fast-list-mode
Changes the output of the list commands to work faster; this is achieved
by leaving some parts empty. Some applications don't need the user ID
and the trust information given in the listings. By using this options
they can get a faster listing. The exact behaviour of this option may
change in future versions. If you are missing some information, don't
use this option.
@item --no-literal
@opindex no-literal
This is not for normal use. Use the source to see for what it might be useful.
@item --set-filesize
@opindex set-filesize
This is not for normal use. Use the source to see for what it might be useful.
@item --show-session-key
@opindex show-session-key
Display the session key used for one message. See
@option{--override-session-key} for the counterpart of this option.
We think that Key Escrow is a Bad Thing; however the user should have
the freedom to decide whether to go to prison or to reveal the content
of one specific message without compromising all messages ever
encrypted for one secret key.
You can also use this option if you receive an encrypted message which
is abusive or offensive, to prove to the administrators of the
messaging system that the ciphertext transmitted corresponds to an
inappropriate plaintext so they can take action against the offending
user.
@item --override-session-key @var{string}
@itemx --override-session-key-fd @var{fd}
@opindex override-session-key
Don't use the public key but the session key @var{string} respective
the session key taken from the first line read from file descriptor
@var{fd}. The format of this string is the same as the one printed by
@option{--show-session-key}. This option is normally not used but
comes handy in case someone forces you to reveal the content of an
encrypted message; using this option you can do this without handing
out the secret key. Note that using @option{--override-session-key}
may reveal the session key to all local users via the global process
table. Often it is useful to combine this option with
@option{--no-keyring}.
@item --ask-sig-expire
@itemx --no-ask-sig-expire
@opindex ask-sig-expire
When making a data signature, prompt for an expiration time. If this
option is not specified, the expiration time set via
@option{--default-sig-expire} is used. @option{--no-ask-sig-expire}
disables this option.
@item --default-sig-expire
@opindex default-sig-expire
The default expiration time to use for signature expiration. Valid
values are "0" for no expiration, a number followed by the letter d
(for days), w (for weeks), m (for months), or y (for years) (for
example "2m" for two months, or "5y" for five years), or an absolute
date in the form YYYY-MM-DD. Defaults to "0".
@item --ask-cert-expire
@itemx --no-ask-cert-expire
@opindex ask-cert-expire
When making a key signature, prompt for an expiration time. If this
option is not specified, the expiration time set via
@option{--default-cert-expire} is used. @option{--no-ask-cert-expire}
disables this option.
@item --default-cert-expire
@opindex default-cert-expire
The default expiration time to use for key signature expiration.
Valid values are "0" for no expiration, a number followed by the
letter d (for days), w (for weeks), m (for months), or y (for years)
(for example "2m" for two months, or "5y" for five years), or an
absolute date in the form YYYY-MM-DD. Defaults to "0".
@item --default-new-key-algo @var{string}
@opindex default-new-key-algo @var{string}
This option can be used to change the default algorithms for key
generation. The @var{string} is similar to the arguments required for
the command @option{--quick-add-key} but slightly different. For
example the current default of @code{"rsa2048/cert,sign+rsa2048/encr"}
(or @code{"rsa3072"}) can be changed to the value of what we currently
call future default, which is @code{"ed25519/cert,sign+cv25519/encr"}.
You need to consult the source code to learn the details. Note that
the advanced key generation commands can always be used to specify a
key algorithm directly.
@item --no-auto-trust-new-key
@opindex no-auto-trust-new-key
When creating a new key the ownertrust of the new key is set to
ultimate. This option disables this and the user needs to manually
assign an ownertrust value.
@item --force-sign-key
@opindex force-sign-key
This option modifies the behaviour of the commands
@option{--quick-sign-key}, @option{--quick-lsign-key}, and the "sign"
sub-commands of @option{--edit-key} by forcing the creation of a key
signature, even if one already exists.
@item --forbid-gen-key
@opindex forbid-gen-key
This option is intended for use in the global config file to disallow
the use of generate key commands. Those commands will then fail with
the error code for Not Enabled.
@item --allow-secret-key-import
@opindex allow-secret-key-import
This is an obsolete option and is not used anywhere.
@item --allow-multiple-messages
@item --no-allow-multiple-messages
These are obsolete options; they have no more effect since GnuPG 2.2.8.
@item --enable-special-filenames
@opindex enable-special-filenames
This option enables a mode in which filenames of the form
@file{-&n}, where n is a non-negative decimal number,
refer to the file descriptor n and not to a file with that name.
@item --disable-fd-translation
@opindex disable-fd-translation
This option changes the behaviour for all following options to expect
libc file descriptors instead of HANDLE values on the command line.
The option has an effect only on Windows.
@item --no-expensive-trust-checks
@opindex no-expensive-trust-checks
Experimental use only.
@item --preserve-permissions
@opindex preserve-permissions
Don't change the permissions of a secret keyring back to user
read/write only. Use this option only if you really know what you are doing.
@item --default-preference-list @var{string}
@opindex default-preference-list
Set the list of default preferences to @var{string}. This preference
list is used for new keys and becomes the default for "setpref" in the
@option{--edit-key} menu.
@item --default-keyserver-url @var{name}
@opindex default-keyserver-url
Set the default keyserver URL to @var{name}. This keyserver will be
used as the keyserver URL when writing a new self-signature on a key,
which includes key generation and changing preferences.
@item --list-config
@opindex list-config
Display various internal configuration parameters of GnuPG. This option
is intended for external programs that call GnuPG to perform tasks, and
is thus not generally useful. See the file @file{doc/DETAILS} in the
source distribution for the details of which configuration items may be
listed. @option{--list-config} is only usable with
@option{--with-colons} set.
@item --list-gcrypt-config
@opindex list-gcrypt-config
Display various internal configuration parameters of Libgcrypt.
@item --gpgconf-list
@opindex gpgconf-list
This command is similar to @option{--list-config} but in general only
internally used by the @command{gpgconf} tool.
@item --gpgconf-test
@opindex gpgconf-test
This is more or less dummy action. However it parses the configuration
file and returns with failure if the configuration file would prevent
@command{@gpgname} from startup. Thus it may be used to run a syntax check
on the configuration file.
@c @item --use-only-openpgp-card
@c @opindex use-only-openpgp-card
@c Only access OpenPGP card's and no other cards. This is a hidden
@c option which could be used in case an old use case required the
@c OpenPGP card while several cards are available. This option might be
@c removed if it turns out that nobody requires it.
@item --chuid @var{uid}
@opindex chuid
Change the current user to @var{uid} which may either be a number or a
name. This can be used from the root account to run gpg for
another user. If @var{uid} is not the current UID a standard PATH is
set and the envvar GNUPGHOME is unset. To override the latter the
option @option{--homedir} can be used. This option has only an effect
when used on the command line. This option has currently no effect at
all on Windows.
@end table
@c *******************************
@c ******* Deprecated ************
@c *******************************
@node Deprecated Options
@subsection Deprecated options
@table @gnupgtabopt
@item -t, --textmode
@itemx --no-textmode
@opindex textmode
Treat input files as text and store them in the OpenPGP canonical text
form with standard "CRLF" line endings. This also sets the necessary
flags to inform the recipient that the encrypted or signed data is text
and may need its line endings converted back to whatever the local
system uses. This option was useful when communicating between two
platforms with different line ending conventions (UNIX-like to Mac,
Mac to Windows, etc). @option{--no-textmode} disables this option, and
is the default. Note that this is a legacy option which should not
anymore be used by any modern software.
@item --force-v3-sigs
@itemx --no-force-v3-sigs
@item --force-v4-certs
@itemx --no-force-v4-certs
These options are obsolete and have no effect since GnuPG 2.1.
@item --show-photos
@itemx --no-show-photos
@opindex show-photos
Causes @option{--list-keys}, @option{--list-signatures},
@option{--list-public-keys}, @option{--list-secret-keys}, and verifying
a signature to also display the photo ID attached to the key, if
any. See also @option{--photo-viewer}. These options are deprecated. Use
@option{--list-options [no-]show-photos} and/or @option{--verify-options
[no-]show-photos} instead.
@item --show-keyring
@opindex show-keyring
Display the keyring name at the head of key listings to show which
keyring a given key resides on. This option is deprecated: use
@option{--list-options [no-]show-keyring} instead.
@item --show-notation
@itemx --no-show-notation
@opindex show-notation
Show signature notations in the @option{--list-signatures} or @option{--check-signatures} listings
as well as when verifying a signature with a notation in it. These
options are deprecated. Use @option{--list-options [no-]show-notation}
and/or @option{--verify-options [no-]show-notation} instead.
@item --show-policy-url
@itemx --no-show-policy-url
@opindex show-policy-url
Show policy URLs in the @option{--list-signatures} or @option{--check-signatures}
listings as well as when verifying a signature with a policy URL in
it. These options are deprecated. Use @option{--list-options
[no-]show-policy-url} and/or @option{--verify-options
[no-]show-policy-url} instead.
@item --personal-aead-preferences @var{string}
@opindex personal-aead-preferences
This option is deprecated and has no more effect since version 2.3.9.
@item --aead-algo @var{name}
This option is deprecated and has no more effect since version 2.3.9.
@end table
@c *******************************************
@c *************** ****************
@c *************** FILES ****************
@c *************** ****************
@c *******************************************
@mansect files
@node GPG Configuration
@section Configuration files
There are a few configuration files to control certain aspects of
@command{@gpgname}'s operation. Unless noted, they are expected in the
current home directory (@pxref{option --homedir}).
@table @file
@item gpg.conf
@efindex gpg.conf
This is the standard configuration file read by @command{@gpgname} on
startup. It may contain any valid long option; the leading two dashes
may not be entered and the option may not be abbreviated. This default
name may be changed on the command line (@pxref{gpg-option --options}).
You should backup this file.
@item common.conf
@efindex common.conf
This is an optional configuration file read by @command{@gpgname} on
startup. It may contain options pertaining to all components of
GnuPG. Its current main use is for the "use-keyboxd" option. If
the default home directory @file{~/.gnupg} does not exist, GnuPG creates
this directory and a @file{common.conf} file with "use_keyboxd".
@end table
Note that on larger installations, it is useful to put predefined files
into the directory @file{@value{SYSCONFSKELDIR}} so that
newly created users start up with a working configuration.
For existing users a small
helper script is provided to create these files (@pxref{addgnupghome}).
For internal purposes @command{@gpgname} creates and maintains a few other
files; They all live in the current home directory (@pxref{option
--homedir}). Only the @command{@gpgname} program may modify these files.
@table @file
@item ~/.gnupg
@efindex ~/.gnupg
This is the default home directory which is used if neither the
environment variable @code{GNUPGHOME} nor the option
@option{--homedir} is given.
@item ~/.gnupg/pubring.gpg
@efindex pubring.gpg
The public keyring using a legacy format. You should backup this file.
If this file is not available, @command{gpg} defaults to the new
keybox format and creates a file @file{pubring.kbx} unless that file
already exists in which case that file will also be used for OpenPGP
keys.
Note that in the case that both files, @file{pubring.gpg} and
@file{pubring.kbx} exists but the latter has no OpenPGP keys, the
legacy file @file{pubring.gpg} will be used. Take care: GnuPG
versions before 2.1 will always use the file @file{pubring.gpg}
because they do not know about the new keybox format. In the case
that you have to use GnuPG 1.4 to decrypt archived data you should
keep this file.
@item ~/.gnupg/pubring.gpg.lock
The lock file for the public keyring.
@item ~/.gnupg/pubring.kbx
@efindex pubring.kbx
The public keyring using the new keybox format. This file is shared
with @command{gpgsm}. You should backup this file. See above for
the relation between this file and it predecessor.
To convert an existing @file{pubring.gpg} file to the keybox format, you
first backup the ownertrust values, then rename @file{pubring.gpg} to
@file{publickeys.backup}, so it won’t be recognized by any GnuPG version,
run import, and finally restore the ownertrust values:
@example
$ cd ~/.gnupg
$ gpg --export-ownertrust >otrust.lst
$ mv pubring.gpg publickeys.backup
$ gpg --import-options restore --import publickeys.backup
$ gpg --import-ownertrust otrust.lst
@end example
@item ~/.gnupg/pubring.kbx.lock
The lock file for @file{pubring.kbx}.
@item ~/.gnupg/secring.gpg
@efindex secring.gpg
The legacy secret keyring as used by GnuPG versions before 2.1. It is not
used by GnuPG 2.1 and later. You may want to keep it in case you
have to use GnuPG 1.4 to decrypt archived data.
@item ~/.gnupg/secring.gpg.lock
The lock file for the legacy secret keyring.
@item ~/.gnupg/.gpg-v21-migrated
@efindex .gpg-v21-migrated
File indicating that a migration to GnuPG 2.1 has been done.
@item ~/.gnupg/trustdb.gpg
@efindex trustdb.gpg
The trust database. There is no need to backup this file; it is better
to backup the ownertrust values (@pxref{option --export-ownertrust}).
@item ~/.gnupg/trustdb.gpg.lock
The lock file for the trust database.
@item ~/.gnupg/random_seed
@efindex random_seed
A file used to preserve the state of the internal random pool.
@item ~/.gnupg/openpgp-revocs.d/
@efindex openpgp-revocs.d
This is the directory where gpg stores pre-generated revocation
certificates. The file name corresponds to the OpenPGP fingerprint of
the respective key. It is suggested to backup those certificates and
if the primary private key is not stored on the disk to move them to
an external storage device. Anyone who can access these files is
able to revoke the corresponding key. You may want to print them out.
You should backup all files in this directory and take care to keep
this backup closed away.
@end table
Operation is further controlled by a few environment variables:
@table @asis
@item HOME
@efindex HOME
Used to locate the default home directory.
@item GNUPGHOME
@efindex GNUPGHOME
If set directory used instead of "~/.gnupg".
@item GPG_AGENT_INFO
This variable is obsolete; it was used by GnuPG versions before 2.1.
@item PINENTRY_USER_DATA
@efindex PINENTRY_USER_DATA
This value is passed via gpg-agent to pinentry. It is useful to convey
extra information to a custom pinentry.
@item COLUMNS
@itemx LINES
@efindex COLUMNS
@efindex LINES
Used to size some displays to the full size of the screen.
@item LANGUAGE
@efindex LANGUAGE
Apart from its use by GNU, it is used in the W32 version to override the
language selection done through the Registry. If used and set to a
valid and available language name (@var{langid}), the file with the
translation is loaded from
@code{@var{gpgdir}/gnupg.nls/@var{langid}.mo}. Here @var{gpgdir} is the
directory out of which the gpg binary has been loaded. If it can't be
loaded the Registry is tried and as last resort the native Windows
locale system is used.
@item GNUPG_BUILD_ROOT
@efindex GNUPG_BUILD_ROOT
This variable is only used by the regression test suite as a helper
under operating systems without proper support to figure out the
name of a process' text file.
@item GNUPG_EXEC_DEBUG_FLAGS
@efindex GNUPG_EXEC_DEBUG_FLAGS
This variable allows one to enable diagnostics for process management.
A numeric decimal value is expected. Bit 0 enables general
diagnostics, bit 1 enables certain warnings on Windows.
@end table
When calling the gpg-agent component @command{@gpgname} sends a set of
environment variables to gpg-agent. The names of these variables can
be listed using the command:
@example
gpg-connect-agent 'getinfo std_env_names' /bye | awk '$1=="D" @{print $2@}'
@end example
@c *******************************************
@c *************** ****************
@c *************** EXAMPLES ****************
@c *************** ****************
@c *******************************************
@mansect examples
@node GPG Examples
@section Examples
@table @asis
@item gpg -se -r @code{Bob} @code{file}
sign and encrypt for user Bob
@item gpg --clear-sign @code{file}
make a cleartext signature
@item gpg -sb @code{file}
make a detached signature
@item gpg -u 0x12345678 -sb @code{file}
make a detached signature with the key 0x12345678
@item gpg --list-keys @code{user_ID}
show keys
@item gpg --fingerprint @code{user_ID}
show fingerprint
@item gpg --verify @code{pgpfile}
@itemx gpg --verify @code{sigfile} [@code{datafile}]
Verify the signature of the file but do not output the data unless
requested. The second form is used for detached signatures, where
@code{sigfile} is the detached signature (either ASCII armored or
binary) and @code{datafile} are the signed data; if this is not given, the name of the
file holding the signed data is constructed by cutting off the
extension (".asc" or ".sig") of @code{sigfile} or by asking the user
for the filename. If the option @option{--output} is also used the
signed data is written to the file specified by that option; use
@code{-} to write the signed data to stdout.
@end table
@c *******************************************
@c *************** ****************
@c *************** USER ID ****************
@c *************** ****************
@c *******************************************
@mansect how to specify a user id
@ifset isman
@include specify-user-id.texi
@end ifset
@mansect filter expressions
@chapheading FILTER EXPRESSIONS
The options @option{--import-filter} and @option{--export-filter} use
expressions with this syntax (square brackets indicate an optional
part and curly braces a repetition, white space between the elements
are allowed):
@c man:.RS
@example
[lc] @{[@{flag@}] PROPNAME op VALUE [lc]@}
@end example
@c man:.RE
The name of a property (@var{PROPNAME}) may only consist of letters,
digits and underscores. The description for the filter type
describes which properties are defined. If an undefined property is
used it evaluates to the empty string. Unless otherwise noted, the
@var{VALUE} must always be given and may not be the empty string. No
quoting is defined for the value, thus the value may not contain the
strings @code{&&} or @code{||}, which are used as logical connection
operators. The flag @code{--} can be used to remove this restriction.
Numerical values are computed as long int; standard C notation
applies. @var{lc} is the logical connection operator; either
@code{&&} for a conjunction or @code{||} for a disjunction. A
conjunction is assumed at the begin of an expression. Conjunctions
have higher precedence than disjunctions. If @var{VALUE} starts with
one of the characters used in any @var{op} a space after the
@var{op} is required.
@noindent
The supported operators (@var{op}) are:
@table @asis
@item =~
Substring must match.
@item !~
Substring must not match.
@item =
The full string must match.
@item <>
The full string must not match.
@item ==
The numerical value must match.
@item !=
The numerical value must not match.
@item <=
The numerical value of the field must be LE than the value.
@item <
The numerical value of the field must be LT than the value.
@item >
The numerical value of the field must be GT than the value.
@item >=
The numerical value of the field must be GE than the value.
@item -le
The string value of the field must be less or equal than the value.
@item -lt
The string value of the field must be less than the value.
@item -gt
The string value of the field must be greater than the value.
@item -ge
The string value of the field must be greater or equal than the value.
@item -n
True if value is not empty (no value allowed).
@item -z
True if value is empty (no value allowed).
@item -t
Alias for "PROPNAME != 0" (no value allowed).
@item -f
Alias for "PROPNAME == 0" (no value allowed).
@end table
@noindent
Values for @var{flag} must be space separated. The supported flags
are:
@table @asis
@item --
@var{VALUE} spans to the end of the expression.
@item -c
The string match in this part is done case-sensitive.
@item -t
Leading and trailing spaces are not removed from @var{VALUE}.
The optional single space after @var{op} is here required.
@end table
The filter options concatenate several specifications for a filter of
the same type. For example the four options in this example:
@c man:.RS
@example
--import-filter keep-uid="uid =~ Alfa"
--import-filter keep-uid="&& uid !~ Test"
--import-filter keep-uid="|| uid =~ Alpha"
--import-filter keep-uid="uid !~ Test"
@end example
@c man:.RE
@noindent
which is equivalent to
@c man:.RS
@example
--import-filter \
keep-uid="uid =~ Alfa" && uid !~ Test" || uid =~ Alpha" && "uid !~ Test"
@end example
@c man:.RE
imports only the user ids of a key containing the strings "Alfa"
or "Alpha" but not the string "test".
@mansect trust values
@ifset isman
@include trust-values.texi
@end ifset
@mansect return value
@chapheading RETURN VALUE
The program returns 0 if there are no severe errors, 1 if at least a
signature was bad, and other error codes for fatal errors.
Note that signature verification requires exact knowledge of what has
been signed and by whom it has been signed. Using only the return code
is thus not an appropriate way to verify a signature by a script.
Either make proper use or the status codes or use the @command{gpgv}
tool which has been designed to make signature verification easy for
scripts.
@mansect warnings
@chapheading WARNINGS
Use a good password for your user account and make sure that all
security issues are always fixed on your machine. Also employ
diligent physical protection to your machine. Consider to use a good
passphrase as a last resort protection to your secret key in the case
your machine gets stolen. It is important that your secret key is
never leaked. Using an easy to carry around token or smartcard with
the secret key is often a advisable.
If you are going to verify detached signatures, make sure that the
program knows about it; either give both filenames on the command line
or use @samp{-} to specify STDIN.
For scripted or other unattended use of @command{gpg} make sure to use
the machine-parseable interface and not the default interface which is
intended for direct use by humans. The machine-parseable interface
provides a stable and well documented API independent of the locale or
future changes of @command{gpg}. To enable this interface use the
options @option{--with-colons} and @option{--status-fd}. For certain
operations the option @option{--command-fd} may come handy too. See
this man page and the file @file{DETAILS} for the specification of the
interface. Note that the GnuPG ``info'' pages as well as the PDF
version of the GnuPG manual features a chapter on unattended use of
GnuPG. As an alternative the library @command{GPGME} can be used as a
high-level abstraction on top of that interface.
@mansect interoperability
@chapheading INTEROPERABILITY WITH OTHER OPENPGP PROGRAMS
GnuPG tries to be a very flexible implementation of the OpenPGP
standard. In particular, GnuPG implements many of the optional parts
of the standard, such as the SHA-512 hash, and the ZLIB and BZIP2
compression algorithms. It is important to be aware that not all
OpenPGP programs implement these optional algorithms and that by
forcing their use via the @option{--cipher-algo},
@option{--digest-algo}, @option{--cert-digest-algo}, or
@option{--compress-algo} options in GnuPG, it is possible to create a
perfectly valid OpenPGP message, but one that cannot be read by the
intended recipient.
There are dozens of variations of OpenPGP programs available, and each
supports a slightly different subset of these optional algorithms.
For example, until recently, no (unhacked) version of PGP supported
the BLOWFISH cipher algorithm. A message using BLOWFISH simply could
not be read by a PGP user. By default, GnuPG uses the standard
OpenPGP preferences system that will always do the right thing and
create messages that are usable by all recipients, regardless of which
OpenPGP program they use. Only override this safe default if you
really know what you are doing.
If you absolutely must override the safe default, or if the preferences
on a given key are invalid for some reason, you are far better off using
the @option{--pgp6}, @option{--pgp7}, or @option{--pgp8} options. These
options are safe as they do not force any particular algorithms in
violation of OpenPGP, but rather reduce the available algorithms to a
"PGP-safe" list.
@mansect bugs
@chapheading BUGS
On older systems this program should be installed as setuid(root). This
is necessary to lock memory pages. Locking memory pages prevents the
operating system from writing memory pages (which may contain
passphrases or other sensitive material) to disk. If you get no
warning message about insecure memory your operating system supports
locking without being root. The program drops root privileges as soon
as locked memory is allocated.
Note also that some systems (especially laptops) have the ability to
``suspend to disk'' (also known as ``safe sleep'' or ``hibernate'').
This writes all memory to disk before going into a low power or even
powered off mode. Unless measures are taken in the operating system
to protect the saved memory, passphrases or other sensitive material
may be recoverable from it later.
Before you report a bug you should first search the mailing list
archives for similar problems and second check whether such a bug has
already been reported to our bug tracker at @url{https://bugs.gnupg.org}.
@c *******************************************
@c *************** **************
@c *************** UNATTENDED **************
@c *************** **************
@c *******************************************
@mansect notes
@node Unattended Usage of GPG
@section Unattended Usage
@command{@gpgname} is often used as a backend engine by other software. To help
with this a machine interface has been defined to have an unambiguous
way to do this. The options @option{--status-fd} and @option{--batch}
are almost always required for this.
@menu
* Programmatic use of GnuPG:: Programmatic use of GnuPG
* Ephemeral home directories:: Ephemeral home directories
* The quick key manipulation interface:: The quick key manipulation interface
* Unattended GPG key generation:: Unattended key generation
@end menu
@node Programmatic use of GnuPG
@subsection Programmatic use of GnuPG
Please consider using GPGME instead of calling @command{@gpgname}
directly. GPGME offers a stable, backend-independent interface for
many cryptographic operations. It supports OpenPGP and S/MIME, and
also allows interaction with various GnuPG components.
GPGME provides a C-API, and comes with bindings for C++, Qt, and
Python. Bindings for other languages are available.
@node Ephemeral home directories
@subsection Ephemeral home directories
Sometimes you want to contain effects of some operation, for example
you want to import a key to inspect it, but you do not want this key
to be added to your keyring. In earlier versions of GnuPG, it was
possible to specify alternate keyring files for both public and secret
keys. In modern GnuPG versions, however, we changed how secret keys
are stored in order to better protect secret key material, and it was
not possible to preserve this interface.
The preferred way to do this is to use ephemeral home directories.
This technique works across all versions of GnuPG.
Create a temporary directory, create (or copy) a configuration that
meets your needs, make @command{@gpgname} use this directory either
using the environment variable @var{GNUPGHOME}, or the option
@option{--homedir}. GPGME supports this too on a per-context basis,
by modifying the engine info of contexts. Now execute whatever
operation you like, import and export key material as necessary. Once
finished, you can delete the directory. All GnuPG backend services
that were started will detect this and shut down.
@node The quick key manipulation interface
@subsection The quick key manipulation interface
Recent versions of GnuPG have an interface to manipulate keys without
using the interactive command @option{--edit-key}. This interface was
added mainly for the benefit of GPGME (please consider using GPGME,
see the manual subsection ``Programmatic use of GnuPG''). This
interface is described in the subsection ``How to manage your keys''.
@node Unattended GPG key generation
@subsection Unattended key generation
The command @option{--generate-key} may be used along with the option
@option{--batch} for unattended key generation. This is the most
flexible way of generating keys, but it is also the most complex one.
Consider using the quick key manipulation interface described in the
previous subsection ``The quick key manipulation interface''.
The parameters for the key are either read from stdin or given as a
file on the command line. The format of the parameter file is as
follows: Text only, line length is limited to about 1000 characters.
UTF-8 encoding must be used to specify non-ASCII characters. Empty
lines are ignored. Leading and trailing white space is ignored. A
hash sign as the first non white space character indicates a comment
line. Control statements are indicated by a leading percent sign,
their arguments are separated by white space from the keyword.
Parameters are specified by a keyword, followed by a colon; arguments
are separated by white space. The first parameter must be
@samp{Key-Type} but control statements may be placed anywhere. The
order of the parameters does not matter except for @samp{Key-Type}.
The parameters are only used for the generated keyblock (primary and
subkeys); parameters from previous sets are not used. Some syntax
checks may be performed. Key commences when either the end of the
parameter file is reached, the next @samp{Key-Type} parameter is
encountered, or the control statement @samp{%commit} is encountered.
@noindent
Control statements:
@table @asis
@item %echo @var{text}
Print @var{text} as diagnostic.
@item %dry-run
Suppress actual key generation (useful for syntax checking).
@item %commit
Perform the key generation. Note that an implicit commit is done at
the next @asis{Key-Type} parameter.
@item %pubring @var{filename}
Do not write the key to the default or commandline given keyring but
to @var{filename}. This must be given before the first commit to take
place, duplicate specification of the same filename is ignored, the
last filename before a commit is used. The filename is used until a
new filename is used (at commit points) and all keys are written to
that file. If a new filename is given, this file is created (and
overwrites an existing one).
See the previous subsection ``Ephemeral home directories'' for a more
robust way to contain side-effects.
@item %secring @var{filename}
This option is a no-op for GnuPG 2.1 and later.
See the previous subsection ``Ephemeral home directories''.
@item %ask-passphrase
@itemx %no-ask-passphrase
This option is a no-op since GnuPG version 2.1.
@item %no-protection
Using this option allows the creation of keys without any passphrase
protection. This option is mainly intended for regression tests.
@item %transient-key
If given the keys are created using a faster and a somewhat less
secure random number generator. This option may be used for keys
which are only used for a short time and do not require full
cryptographic strength. It takes only effect if used together with
the control statement @samp{%no-protection}.
@end table
@noindent
General Parameters:
@table @asis
@item Key-Type: @var{algo}
Starts a new parameter block by giving the type of the primary
key. The algorithm must be capable of signing. This is a required
parameter. @var{algo} may either be an OpenPGP algorithm number or a
string with the algorithm name. The special value @samp{default} may
be used for @var{algo} to create the default key type; in this case a
@samp{Key-Usage} shall not be given and @samp{default} also be used
for @samp{Subkey-Type}.
@item Key-Length: @var{nbits}
The requested length of the generated key in bits. The default is
returned by running the command @samp{@gpgname --gpgconf-list}.
For ECC keys this parameter is ignored.
@item Key-Curve: @var{curve}
The requested elliptic curve of the generated key. This is a required
parameter for ECC keys. It is ignored for non-ECC keys.
@item Key-Grip: @var{hexstring}
This is optional and used to generate a CSR or certificate for an
already existing key. Key-Length will be ignored when given.
@item Key-Usage: @var{usage-list}
Space or comma delimited list of key usages. Allowed values are
@samp{encrypt}, @samp{sign}, and @samp{auth}. This is used to
generate the key flags. Please make sure that the algorithm is
capable of this usage. Note that OpenPGP requires that all primary
keys are capable of certification, so no matter what usage is given
here, the @samp{cert} flag will be on. If no @samp{Key-Usage} is
specified and the @samp{Key-Type} is not @samp{default}, all allowed
usages for that particular algorithm are used; if it is not given but
@samp{default} is used the usage will be @samp{sign}.
@item Subkey-Type: @var{algo}
This generates a secondary key (subkey). Currently only one subkey
can be handled. See also @samp{Key-Type} above.
@item Subkey-Length: @var{nbits}
Length of the secondary key (subkey) in bits. The default is returned
by running the command @samp{@gpgname --gpgconf-list}.
@item Subkey-Curve: @var{curve}
Key curve for a subkey; similar to @samp{Key-Curve}.
@item Subkey-Usage: @var{usage-list}
Key usage lists for a subkey; similar to @samp{Key-Usage}.
@item Passphrase: @var{string}
If you want to specify a passphrase for the secret key, enter it here.
Default is to use the Pinentry dialog to ask for a passphrase.
@item Name-Real: @var{name}
@itemx Name-Comment: @var{comment}
@itemx Name-Email: @var{email}
The three parts of a user name. Remember to use UTF-8 encoding here.
If you don't give any of them, no user ID is created.
@item Expire-Date: @var{iso-date}|(@var{number}[d|w|m|y])
Set the expiration date for the key (and the subkey). It may either
be entered in ISO date format (e.g., "20000815T145012") or as number of
days, weeks, month or years after the creation date. The special
notation "seconds=N" is also allowed to specify a number of seconds
since creation. Without a letter days are assumed. Note that there
is no check done on the overflow of the type used by OpenPGP for
timestamps. Thus you better make sure that the given value make
sense. Although OpenPGP works with time intervals, GnuPG uses an
absolute value internally and thus the last year we can represent is
2105.
@item Creation-Date: @var{iso-date}
Set the creation date of the key as stored in the key information and
which is also part of the fingerprint calculation. Either a date like
"1986-04-26" or a full timestamp like "19860426T042640" may be used.
The time is considered to be UTC. The special notation "seconds=N"
may be used to directly specify a the number of seconds since Epoch
(Unix time). If it is not given the current time is used.
@item Preferences: @var{string}
Set the cipher, hash, and compression preference values for this key.
This expects the same type of string as the sub-command @samp{setpref}
in the @option{--edit-key} menu.
@item Revoker: @var{algo}:@var{fpr} [sensitive]
Add a designated revoker to the generated key. Algo is the public key
algorithm of the designated revoker (i.e. RSA=1, DSA=17, etc.)
@var{fpr} is the fingerprint of the designated revoker. @var{fpr} may
not contain spaces or colons. The optional @samp{sensitive} flag
marks the designated revoker as sensitive information. Only v4 and v5
keys may be designated revokers.
@item Keyserver: @var{string}
This is an optional parameter that specifies the preferred keyserver
URL for the key.
@item Handle: @var{string}
This is an optional parameter only used with the status lines
KEY_CREATED and KEY_NOT_CREATED. @var{string} may be up to 100
characters and should not contain spaces. It is useful for batch key
generation to associate a key parameter block with a status line.
@end table
@noindent
Here is an example on how to create a key in an ephemeral home directory:
@smallexample
$ export GNUPGHOME="$(mktemp -d)"
$ cat >foo <<EOF
%echo Generating a basic OpenPGP key
Key-Type: DSA
Key-Length: 1024
Subkey-Type: ELG-E
Subkey-Length: 1024
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: joe@@foo.bar
Expire-Date: 0
Passphrase: abc
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
EOF
$ @gpgname --batch --generate-key foo
[...]
$ @gpgname --list-secret-keys
/tmp/tmp.0NQxB74PEf/pubring.kbx
-------------------------------
sec dsa1024 2016-12-16 [SCA]
768E895903FC1C44045C8CB95EEBDB71E9E849D0
uid [ultimate] Joe Tester (with stupid passphrase) <joe@@foo.bar>
ssb elg1024 2016-12-16 [E]
@end smallexample
@noindent
If you want to create a key with the default algorithms you would use
these parameters:
@smallexample
%echo Generating a default key
Key-Type: default
Subkey-Type: default
Name-Real: Joe Tester
Name-Comment: with stupid passphrase
Name-Email: joe@@foo.bar
Expire-Date: 0
Passphrase: abc
# Do a commit here, so that we can later print "done" :-)
%commit
%echo done
@end smallexample
@mansect see also
@ifset isman
@command{gpgv}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1)
@end ifset
@include see-also-note.texi
diff --git a/doc/help.txt b/doc/help.txt
index 917175004..3528aa778 100644
--- a/doc/help.txt
+++ b/doc/help.txt
@@ -1,441 +1,441 @@
# help.txt - English GnuPG online help
# Copyright (C) 2007 Free Software Foundation, Inc.
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
# Note that this help file needs to be UTF-8 encoded. When looking
# for a help item, GnuPG scans the help files in the following order
# (assuming a GNU or Unix system):
#
# /etc/gnupg/help.LL_TT.txt
# /etc/gnupg/help.LL.txt
# /etc/gnupg/help.txt
# /usr/share/gnupg/help.LL_TT.txt
# /usr/share/gnupg/help.LL.txt
# /usr/share/gnupg/help.txt
#
# Here LL_TT denotes the full name of the current locale with the
# territory (.e.g. "de_DE"), LL denotes just the locale name
# (e.g. "de"). The first matching item is returned. To put a dot or
# a hash mark at the beginning of a help text line, it needs to be
# prefixed with ". ". A single dot may be used to terminated a help
# entry.
.#pinentry.qualitybar.tooltip
# [remove the hash mark from the key to enable this text]
# This entry is just an example on how to customize the tooltip shown
# when hovering over the quality bar of the pinentry. We don't
# install this text so that the hardcoded translation takes
# precedence. An administrator should write up a short help to tell
# the users about the configured passphrase constraints and save that
# to /etc/gnupg/help.txt. The help text should not be longer than
# about 800 characters.
This bar indicates the quality of the passphrase entered above.
As long as the bar is shown in red, GnuPG considers the passphrase too
weak to accept. Please ask your administrator for details about the
configured passphrase constraints.
.
.#pinentry.constraints.hint.short
# [remove the hash mark from the key to enable this hint]
# This entry is used by some pinentries to display a hint about
# enabled passphrase constraints. These constraints are configurable
# and the admin may give a hint about them by using this help entry.
Please use letters and digits.
.
.#pinentry.constraints.hint.long
# [remove the hash mark from the key to enable this hint]
# This entry is used by some pinentries to show a tooltip with more
# information about the configured passphrase constraints.
Please use letters and digits.
Extra constraints are enforced, for example
the use of common car number plates.
.
.#pinentry.formatted_passphrase.hint",
# [remove the hash mark from the key to enable this hint]
# If this entry is not set a standard text is shown
Note: The blanks are not part of the passphrase.
.
.gnupg.agent-problem
# There was a problem accessing or starting the agent.
It was either not possible to connect to a running Gpg-Agent or a
communication problem with a running agent occurred.
The system uses a background process, called Gpg-Agent, for processing
private keys and to ask for passphrases. The agent is usually started
when the user logs in and runs as long the user is logged in. In case
that no agent is available, the system tries to start one on the fly
but that version of the agent is somewhat limited in functionality and
thus may lead to little problems.
You probably need to ask your administrator on how to solve the
problem. As a workaround you might try to log out and in to your
session and see whether this helps. If this helps please tell the
administrator anyway because this indicates a bug in the software.
.
.gnupg.dirmngr-problem
# There was a problen accessing the dirmngr.
It was either not possible to connect to a running Dirmngr or a
communication problem with a running Dirmngr occurred.
To lookup certificate revocation lists (CRLs), performing OCSP
validation and to lookup keys through LDAP servers, the system uses an
external service program named Dirmngr. The Dirmngr is usually running
as a system service (daemon) and does not need any attention by the
user. In case of problems the system might start its own copy of the
Dirmngr on a per request base; this is a workaround and yields limited
performance.
If you encounter this problem, you should ask your system
administrator how to proceed. As an interim solution you may try to
disable CRL checking in gpgsm's configuration.
.
.gpg.edit_ownertrust.value
-# The help identies prefixed with "gpg." used to be hard coded in gpg
+# The help entries prefixed with "gpg." used to be hard coded in gpg
# but may now be overridden by help texts from this file.
It's up to you to assign a value here; this value will never be exported
to any 3rd party. We need it to implement the web-of-trust; it has nothing
to do with the (implicitly created) web-of-certificates.
.
.gpg.edit_ownertrust.set_ultimate.okay
To build the Web-of-Trust, GnuPG needs to know which keys are
ultimately trusted - those are usually the keys for which you have
access to the secret key. Answer "yes" to set this key to
ultimately trusted.
.gpg.untrusted_key.override
If you want to use this untrusted key anyway, answer "yes".
.
.gpg.pklist.user_id.enter
Enter the user ID of the addressee to whom you want to send the message.
.
.gpg.keygen.algo
Select the algorithm to use.
DSA (aka DSS) is the Digital Signature Algorithm and can only be used
for signatures.
Elgamal is an encrypt-only algorithm.
RSA may be used for signatures or encryption.
The first (primary) key must always be a key which is capable of signing.
.
.gpg.keygen.algo.rsa_se
In general it is not a good idea to use the same key for signing and
encryption. This algorithm should only be used in certain domains.
Please consult your security expert first.
.
.gpg.keygen.cardkey
Select which key from the card shall be used.
The listing shows the selection index, the keygrip (a string of hex
digits), the card specific key reference, the algorithm used for this
key, and in parentheses the usage of the key (cert, sign, auth, encr).
If known the standard usage for a key is marked with an asterisk.
.
.gpg.keygen.keygrip
Enter the keygrip of the key to add.
The keygrip is a string of 40 hex digits that identifies a key. It
must belong to a secret key or a secret subkey stored in your keyring.
.
.gpg.keygen.flags
Toggle the capabilities of the key.
It is only possible to toggle those capabilities which are possible
for the selected algorithm.
To quickly set the capabilities all at once it is possible to enter a
'=' as first character followed by a list of letters indicating the
capability to set: 's' for signing, 'e' for encryption, and 'a' for
authentication. Invalid letters and impossible capabilities are
ignored. This submenu is immediately closed after using this
shortcut.
.
.gpg.keygen.size
Enter the size of the key.
The suggested default is usually a good choice.
If you want to use a large key size, for example 4096 bit, please
think again whether it really makes sense for you. You may want
to view the web page https://www.xkcd.com/538/ .
.
.gpg.keygen.size.huge.okay
Answer "yes" or "no".
.
.gpg.keygen.size.large.okay
Answer "yes" or "no".
.
.gpg.keygen.valid
Enter the required value as shown in the prompt.
It is possible to enter an ISO date (YYYY-MM-DD) but you won't
get a good error response - instead the system tries to interpret
the given value as an interval.
.
.gpg.keygen.valid.okay
Answer "yes" or "no".
.
.gpg.keygen.name
Enter the name of the key holder.
The characters "<" and ">" are not allowed.
Example: Heinrich Heine
.
.gpg.keygen.email
Please enter an optional but highly suggested email address.
Example: heinrichh@duesseldorf.de
.
.gpg.keygen.comment
Please enter an optional comment.
The characters "(" and ")" are not allowed.
In general there is no need for a comment.
.
.gpg.keygen.userid.cmd
# (Keep a leading empty line)
N to change the name.
C to change the comment.
E to change the email address.
O to continue with key generation.
Q to quit the key generation.
.
.gpg.keygen.sub.okay
Answer "yes" (or just "y") if it is okay to generate the sub key.
.
.gpg.sign_uid.okay
Answer "yes" or "no".
.
.gpg.sign_uid.class
When you sign a user ID on a key, you should first verify that the key
belongs to the person named in the user ID. It is useful for others to
know how carefully you verified this.
"0" means you make no particular claim as to how carefully you verified the
key.
"1" means you believe the key is owned by the person who claims to own it
but you could not, or did not verify the key at all. This is useful for
a "persona" verification, where you sign the key of a pseudonymous user.
"2" means you did casual verification of the key. For example, this could
mean that you verified the key fingerprint and checked the user ID on the
key against a photo ID.
"3" means you did extensive verification of the key. For example, this could
mean that you verified the key fingerprint with the owner of the key in
person, and that you checked, by means of a hard to forge document with a
photo ID (such as a passport) that the name of the key owner matches the
name in the user ID on the key, and finally that you verified (by exchange
of email) that the email address on the key belongs to the key owner.
Note that the examples given above for levels 2 and 3 are *only* examples.
In the end, it is up to you to decide just what "casual" and "extensive"
mean to you when you sign other keys.
If you don't know what the right answer is, answer "0".
.
.gpg.change_passwd.empty.okay
Answer "yes" or "no".
.
.gpg.keyedit.save.okay
Answer "yes" or "no".
.
.gpg.keyedit.cancel.okay
Answer "yes" or "no".
.
.gpg.keyedit.sign_all.okay
Answer "yes" if you want to sign ALL the user IDs.
.
.gpg.keyedit.remove.uid.okay
Answer "yes" if you really want to delete this user ID.
All certificates are then also lost!
.
.gpg.keyedit.remove.subkey.okay
Answer "yes" if it is okay to delete the subkey.
.
.gpg.keyedit.delsig.valid
This is a valid signature on the key; you normally don't want
to delete this signature because it may be important to establish a
trust connection to the key or another key certified by this key.
.
.gpg.keyedit.delsig.unknown
This signature can't be checked because you don't have the
corresponding key. You should postpone its deletion until you
know which key was used because this signing key might establish
a trust connection through another already certified key.
.
.gpg.keyedit.delsig.invalid
The signature is not valid. It does make sense to remove it from
your keyring.
.
.gpg.keyedit.delsig.selfsig
This is a signature which binds the user ID to the key. It is
usually not a good idea to remove such a signature. Actually
GnuPG might not be able to use this key anymore. So do this
only if this self-signature is for some reason not valid and
a second one is available.
.
.gpg.keyedit.updpref.okay
Change the preferences of all user IDs (or just of the selected ones)
to the current list of preferences. The timestamp of all affected
self-signatures will be advanced by one second.
.
.gpg.passphrase.enter
# (keep a leading empty line)
Please enter the passphrase; this is a secret sentence.
.
.gpg.passphrase.repeat
Please repeat the last passphrase, so you are sure what you typed in.
.
.gpg.detached_signature.filename
Give the name of the file to which the signature applies.
.
.gpg.openfile.overwrite.okay
# openfile.c (overwrite_filep)
Answer "yes" if it is okay to overwrite the file.
.
.gpg.openfile.askoutname
# openfile.c (ask_outfile_name)
Please enter a new filename. If you just hit RETURN the default
file (which is shown in brackets) will be used.
.
.gpg.ask_revocation_reason.code
# revoke.c (ask_revocation_reason)
You should specify a reason for the revocation. Depending on the
context you have the ability to choose from this list:
"Key has been compromised"
Use this if you have a reason to believe that unauthorized persons
got access to your secret key.
"Key is superseded"
Use this if you have replaced this key with a newer one.
"Key is no longer used"
Use this if you have retired this key.
"User ID is no longer valid"
Use this to state that the user ID should not longer be used;
this is normally used to mark an email address invalid.
.
.gpg.ask_revocation_reason.text
# revoke.c (ask_revocation_reason)
If you like, you can enter a text describing why you issue this
revocation certificate. Please keep this text concise.
An empty line ends the text.
.
.gpg.tofu.conflict
# tofu.c
TOFU has detected another key with the same (or a very similar) email
address. It might be that the user created a new key. In this case,
you can safely trust the new key (but, confirm this by asking the
person). However, it could also be that the key is a forgery or there
is an active Man-in-the-Middle (MitM) attack. In this case, you
should mark the key as being bad, so that it is untrusted. Marking a
key as being untrusted means that any signatures will be considered
bad and attempts to encrypt to the key will be flagged. If you are
unsure and can't currently check, you should select either accept once
or reject once.
.
.gpgsm.root-cert-not-trusted
# This text gets displayed by the audit log if
# a root certificates was not trusted.
The root certificate (the trust-anchor) is not trusted. Depending on
the configuration you may have been prompted to mark that root
certificate as trusted or you need to manually tell GnuPG to trust that
certificate. Trusted certificates are configured in the file
trustlist.txt in GnuPG's home directory. If you are in doubt, ask
your system administrator whether you should trust this certificate.
.gpgsm.crl-problem
# This text is displayed by the audit log for problems with
# the CRL or OCSP checking.
Depending on your configuration a problem retrieving the CRL or
performing an OCSP check occurred. There are a great variety of
reasons why this did not work. Check the manual for possible
solutions.
# Local variables:
# mode: default-generic
# coding: utf-8
# End:
diff --git a/doc/ldap/README.ldap b/doc/ldap/README.ldap
index 7095d6d10..33ffe3702 100644
--- a/doc/ldap/README.ldap
+++ b/doc/ldap/README.ldap
@@ -1,586 +1,586 @@
# README.ldap -*- org -*-
#+TITLE: How to use LDAP with GnuPG
#+AUTHOR: GnuPG.com
#+DATE: 2021-09-01
#
# The following comment lines are for use by Org-mode.
#+EXPORT_FILE_NAME: gnupg-and-ldap
#+LANGUAGE: en
#+OPTIONS: H:3 num:t toc:t \n:nil @:t ::t |:t ^:{} -:t f:t *:t TeX:t LaTeX:t skip:nil d:(HIDE) tags:not-in-toc
#+HTML_HEAD: <link rel="stylesheet" type="text/css" href="https://gnupg.org/share/site.css" />
#+LATEX_CLASS: article
#+LATEX_CLASS_OPTIONS: [a4paper,11pt]
#+LATEX_HEADER: \usepackage{a4wide}
#+LATEX_HEADER_EXTRA: \parindent0mm
#+STARTUP: showall
* How to use LDAP with GnuPG
In GnuPG the handling of LDAP is done by its Dirmngr component. This
is due to the architecture of the system where Dirmngr is the sole
process responsible for network related tasks. Network access is
required for:
- CRL fetching and caching for S/MIME
- OCSP checking
- S/MIME (X.509) certificate search via LDAP
- OpenPGP keyserver access (HTTP, LDAP, etc.)
- Checking for software updates (if enabled)
In the following we describe how S/MIME and OpenPGP certificate search
is implemented. If you want to skip this background information feel
free to continue with the next section where LDAP installation and
configuration is described. In any case we need to explain a few
terms used with LDAP:
- DIT :: /Directory Information Tree/ also known as /naming context/.
This is is often referred to as the /LDAP directory/. It is
where the data for a single organization described by a DNS
name is stored (e.g. "example.org").
- DN :: /Distinguished Name/ is the key for an entry in the DIT. It
is a similar concept as used in the DNS system.
- RDN :: /Relative Distinguished Name/ is a component or part of a
DN. For example the DN "cn=admin,dc=example,dc=com" consist
of the 3 RDNs "cn=admin", "dc=example", and "dc=com". Each
RDN has a name (e.g. "cn" for /common name/ or "dc" for
/domain component/) and a values (e.g. "admin").
- LDIF :: /LDAP Data Interchange Format/ is a description for the
human readable data exchange format used with LDAP.
** OpenPGP
To serve OpenPGP certificates via LDAP a dedicated schema needs to be
installed. The schema supported by GnuPG was originally defined by
PGP Inc. in the end of the 1990ies. This is today still the schema
installed on LDAP servers for access by PGP or GnuPG. However, this
schema has a couple of deficits which need to be fixed. For that
reason we have defined additional attributes. These new attributes
eventually allow to lookup certificates by their fingerprints and not
just by the shorter and thus non-unique Key-ID. The new schema also
supports storing of information on the subkeys and the UTF-8 encoded
mail addresses. Current versions of GnuPG do not yet make use of
these new attributes but for new LDAP installations it is highly
recommended to use the new schema so that a future version of the
software can make use if these attributes.
Note that the OpenPGP certificates are stored in the DIT under a
separate organizational unit using the long Key-ID to distinguish
them. An example for such an DN is:
: pgpCertID=63113AE866587D0A,ou=GnuPG Keys,dc=example,dc=com
or for Active Directory
: cn=C312[...]0A,cn=GnuPG Keys,dc=example,dc=com
This design means that entries stored under "GnuPG Keys" are not
connected to the users commonly found on an LDAP server. This allows
to store arbitrary OpenPGP certificates in the directory and is
commonly used to make the certificates of external communication
partners easily available.
** S/MIME
Standard X.509 LDAP semantics apply for S/MIME certificate search.
The current version of Dirmngr (2.2.23) supports 3 pattern formats
which are translated from GnuPG's User-ID syntax, as given to the gpg
and gpgsm commands, to the LDAP syntax:
- Mail :: Indicated by a leading left angle and translated to the
query:
: "<ADDRSPEC>" -> "mail=ADDRSPEC"
- Subject DN :: Indicated by a leading slash. The DN is formatted
according to RFC-2253 rules and thus directly usable
for an LDAP query.
- Substring search :: If no other syntax matches or the pattern is
prefixed with an asterisk the User-ID is translated to:
: "USERID" -> "(|(sn=*USERID*)(|(cn=*USERID*)(mail=*USERID*)))"
or in other word a substring search on the serial-number, the
common-name, and the mail attribute is done.
The result is expected to be in one of the attributes
"userCertificate", "cACertificate", or "x509caCert". In cases where
we are looking for the issuer certificate only "cACertificate" is
used. "ObjectClass=*" is always used a filter.
Note: The attribute "mail" with the OID 0.9.2342.19200300.100.1.3 was
originally defined with this OID under the name "rfc822Mailbox" using
a different although similar syntax. Take care: This is not an UTF-8
encoded mail address and in theory GnuPG should use IDN mapping here.
However, it is questionable whether any real world installation
would be able to handle such a mapping.
* How to install OpenLDAP
To install a standard LDAP server to provide S/MIME certificate lookup
follow the instructions of your OS vendor. For example on Debian
based systems this is:
: apt-get install slapd ldap-utils libsasl2-modules
Follow the prompts during installation, set an initial admin password,
and, most important, the domain you want to serve. Note that we use
"example.com" in following. If you ever need to change the
configuration on a Debian based system you can do so by running
: dpkg-reconfigure slapd
Serving LDAP requests for S/MIME (X.509) certificates will then work
out of the box. Use your standard tools to maintain these
entries. Some hints on how to manually add certificates can be found
below in the section "Useful LDAP Commands".
Please read on if you want to serve also OpenPGP certificates.
** Installation of the OpenPGP Schema
Assuming a standard OpenLDAP installation, it is easy to add a new
schema to store OpenPGP certificate. We describe this now step by
step.
First you need to download the two LDIF files
- https://gnupg.org/misc/gnupg-ldap-schema.ldif
- https://gnupg.org/misc/gnupg-ldap-init.ldif.
As administrator (root) on your LDAP server use the command
: ldapadd -v -Y EXTERNAL -H ldapi:/// -f ./gnupg-ldap-schema.ldif
to install the schema. The options given to the ldapadd tool are:
- -v :: Given some diagnostic output (be verbose). To be even more
verbose you may use =-vv= or =-vvv=. The diagnostics are
written to stdout.
- -Y :: Specify the authentication mechanism. Here we use =EXTERN=
which is in this case local socket based authentication
(ldapi).
- -H :: The URL to access the LDAP server. Only scheme, host, and
port are allowed. In our case we use =ldapi:///= to request
a connection on the standard OpenLDAP socket (usually this is
=/var/run/slapd/ldapi=).
- -f :: Specify a file with data to add to the directory. The file
used here is the specification of the keyserver schema. If
this option is not used ldapadd expects this data on stdin.
The new schema should now be installed. Check this by using this
command:
: ldapsearch -Q -Y EXTERNAL -L -H ldapi:/// \
: -b 'cn=schema,cn=config' cn | grep cn:
(on Unix the backslash indicates that the line is continued with the
next line)
The options not used by ldapsearch which have not yet been explained
above are:
- -Q :: Be quiet about authentication and never prompt.
- -b :: Specify the search base. In this case we want the internal
OpenLDAP schema which stores the server's own configuration.
The final argument =cn= restricts the output to the DN and the CN
attribute; the grep then shows only the latter. With a freshly
installed OpenLDAP system you should get an output like:
#+begin_example
cn: schema
cn: {0}core
cn: {1}cosine
cn: {2}nis
cn: {3}inetorgperson
cn: {4}gnupg-keyserver
#+end_example
This tells you that the keyserver schema has been installed under (in
this case) the index "{4}".
The next step is to connect the new schema with your DIT. This means
that entries to actually store the certificates and meta data are
created. This way GnuPG will be able to find the data. For this you
need to edit the downloaded file =gnupg-ldap-init.ldif= and replace
all the RDNs with name "dc" with your own. For example, in our own
LDAP we would change
: dn: cn=PGPServerInfo,dc=example,dc=com
to
: dn: cn=PGPServerInfo,dc=gnupg,dc=com
and do that also for the other 3 appearances of the "dc" RDNs. In case
you use a 3-level domain, add another "dc" in the same way you did when
setting up OpenLDAP. With that modified file run
: ldapadd -v -x -H ldapi:/// -D 'cn=admin,dc=example,dc=com' \
: -W -f ./gnupg-ldap-init.ldif
Remember to change the "dc" RDNs also here to what you actually use.
We use simple authentication by means of these options:
- -x :: Use simple authentication
- -D :: The Bind-DN used to bind to the LDAP directory
- -W :: Ask for the admin's passphrase. You may also use a lowercase
=-w= followed by the passphrase but that would reveal the
passphrase in the shell's history etc.
All users with access right to the LDAP server may now retrieve
OpenPGP certificates. But wait, we also need a user allowed to insert
or update OpenPGP certificates. Choose a useful name for that user
and create a file =newuser.ldif=. In our example domain we name that
user "LordPrivySeal" and thus the file is:
#+begin_src
dn: uid=LordPrivySeal,ou=GnuPG Users,dc=example,dc=com
objectClass: inetOrgPerson
objectClass: uidObject
sn: Lord Keeper of the Privy Seal
cn: Lord Privy Seal
userPassword: {SSHA}u6oxl9ulaS57RPyjApyPcE7mNECNK1Tg
#+end_src
The =userPassword= has been created by running
: /usr/sbin/slappasswd
entering the password, and paste the output into the file (the
password used in the above example is "abc").
Now run
: ldapadd -v -x -H ldapi:/// -D 'cn=admin,dc=example,dc=com' \
: -W -f ./newuser.ldif
On the password prompt enter the admin's password (not the one of the
new user). Note that the user is created below the "GnuPG Users"
organizational unit and not in the standard name space. Thus this is
a dedicated user for OpenPGP certificates.
See below how you can list the entire DIT. With
a fresh install you should see these DNs:
#+begin_example
dn: dc=example,dc=com
dn: cn=admin,dc=example,dc=com
dn: cn=PGPServerInfo,dc=example,dc=com
dn: ou=GnuPG Keys,dc=example,dc=com
dn: ou=GnuPG Users,dc=example,dc=com
dn: uid=LordPrivySeal,ou=GnuPG Users,dc=example,dc=com
#+end_example
Finally we need to give all users read access to the server's database
and allow an authenticated user to modify the database. To do this
you need to figure out the used database; run the command
: ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b 'cn=config' dn | grep olcDatabase=
which should give you a list like this:
#+begin_example
dn: olcDatabase={-1}frontend,cn=config
dn: olcDatabase={0}config,cn=config
dn: olcDatabase={1}mdb,cn=config
#+end_example
The first two databases are for internal purposes, the last one is our
database. Now create a file =grantaccess.ldif= with this content:
#+begin_example
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcAccess
olcAccess: {0} to dn.subtree="dc=example,dc=com"
by dn.regex="^uid=LordPrivySeal,ou=GnuPG Users,dc=example,dc=com" write
by * read
#+end_example
As usual replace all "dc=example,dc=com" accordingly. Take care not
to insert a blank line anywhere. The first line needs to give the DN
-of the database as determined above. Excute the rules from that file
+of the database as determined above. Execute the rules from that file
using the command:
: ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f grantaccess.ldif
Now all users have read access and the user LordPrivySeal has write
access. In case you want to give several users permissions to update the
keys replace the regex line in =grantaccess.ldif= with
: by dn.regex="^uid=([^,]+),ou=GnuPG Users,dc=example,dc=com" write
(take care to insert two spaces at the begin of the line.) Then
create those users below the RDN "ou=GnuPG Users".
That's all you need to do at the server.
** Configuration for GnuPG
The easiest way to enable LDAP for S/MIME is to put
#+begin_src
keyserver ldap.example.com::::dc=example,dc=com:
#+end_src
into =gpgsm.conf=. If you prefer to use a dedicated configuration
file you can do this with dirmngr by adding a line
: ldap.example.com::::dc=example,dc=com:
to =dirmngr_ldapservers.conf=.
Assuming you want to use the machine running the LDAP server also to
maintain OpenPGP certificates, put the following line into the
=dirmngr.conf= configuration of a dedicated user for this task:
#+begin_src
keyserver ldapi:///????bindname=uid=LordPrivySeal
%2Cou=GnuPG%20Users%2Cdc=example%2Cdc=com,password=abc
#+end_src
(Enter this all on one line; "%2C" directly at the end of "Seal")
That is a pretty long line with weird escaping rules. Just enter it
verbatim but replace the "dc" RDNs accordingly. Remember that =ldapi=
uses local socket connection instead of TCP to connect to the server.
The password given in that file is the password of the OpenPGP
maintainer (LordPrivySeal). Use appropriate permissions for that
file to make it not too easy to access that password. See the GnuPG
manual for other ways to configure an LDAP keyserver.
With that configuration in place you may add arbitrary OpenPGP keys to
your LDAP. For example user "joe@example.org" sends you a key and
asks to insert that key. If you feel comfortable with that you should
first check the key, import it into your local keyring, and then send
it off to your LDAP server:
: gpg --show-key < file-with-joes-key.asc
Looks good? Note the fingerprint of the key and run
: gpg --import < file-with-joes-key.asc
: gpg --send-keys FINGERPRINT
That's all. If you want to work from a different machine or use the
Kleopatra GUI you need to make sure that ldaps has been correctly
configured (for example on the machine =ldap.example.org=) and you
need to use this keyserver line:
#+begin_src
keyserver ldaps://ldap.example.com/????bindname=uid=LordPrivySeal
%2Cou=GnuPG%20Users%2Cdc=example%2Cdc=com,password=abc
#+end_src
(Enter this all on one line; "%2C" directly at the end of "Seal")
The easier case is the configuration line for anonymous users which is
a mere
#+begin_src
keyserver ldaps://ldap.example.com
#+end_src
This assumes that you have a valid TLS server certificate for that
domain and ldaps is enabled on the server.
* Useful OpenLDAP Commands
** List the entire DIT
To list the entire DIT for the domain "example.com" use this command:
: ldapsearch -Q -Y EXTERNAL -LLL -H ldapi:/// -b dc=example,dc=com dn
This lists just the DNs. If you need the entire content of the DIT
leave out the "dn" argument. The option "-LLL" selects useful
formatting options for the output.
-** Insert X.509 Certficate
+** Insert X.509 Certificate
If you don't have a handy tool to insert a certificate via LDAP you
can do it manually. First put the certificate in binary (DER) format
into a file. For example using gpgsm:
: gpgsm --export berta.boss@example.com >berta.crt
Then create a file =addcert.ldif=:
#+begin_example
dn: CN=Berta Boss,dc=example,dc=com
objectclass: inetOrgPerson
cn: Berta Boss
sn: Boss
gn: Berta
uid: berta
mail: berta.boss@example.com
usercertificate;binary:< file:///home/admin/berta.crt
#+end_example
(Note that an absolute file name is required.)
Finally run
: ldapadd -x -H ldapi:/// -D 'cn=admin,dc=example,dc=com' -W -f adduser.ldif
** Setup TLS certificates
Create a file =tlscerts.ldif=:
#+begin_example
dn: cn=config
changetype: modify
replace: olcTLSCACertificateFile
olcTLSCACertificateFile: /etc/ssl/certs/Example.com-Root-CA.pem
-
replace: olcTLSCertificateFile
olcTLSCertificateFile: /etc/ssl/mycerts/ldap.example.com.pem
-
replace: olcTLSCertificateKeyFile
olcTLSCertificateKeyFile: /etc/ssl/private/ldap.example.com.key
#+end_example
Make sure that the user under which slapd is running has access to all
these files. The key file should only be readable by that user or
group. Then run
: ldapmodify -v -H ldapi:// -Y EXTERNAL -f tlscerts.ldif
In case you run into a the error message “Other (e.g., implementation
specific) error (80)” check the file permissions, restart slapd so
that it takes up a group modification you did, check that the order of
the item is exactly as given above.
For a quick test whether this works use this command:
: LDAPTLS_CACERT=/etc/ssl/certs/Example.com-Root-CA.pem \
: ldapwhoami -v -H ldap://ldap.example.com -ZZ -x
(-ZZ enforces the use of STARTTLS)
# Note: To enable the legacy ldap-over-tls put "ldaps:///" into the
# list of URLs give to the slapd option -h. For example:
#
# slapd -h "ldap:/// ldaps:/// ldapi:///" ...
#
# To test this use
#
# LDAPTLS_CACERT=/etc/ssl/certs/Example.com-Root-CA.pem \
# ldapwhoami -v -H ldaps://ldap.example.com -x
#
If you use a custom Root-CA certificate you need to copy it to all
clients as well. On a Debian system you would do this:
: cp Example.com-Root-CA.pem \
: /usr/local/share/ca-certificates/Example.com-Root-CA.crt
: update-ca-certificates
Note that Debian expects the suffix ".crt" even though the certificate
needs to be in PEM format. To check whether the certificate is usable
and you have installed GnuPG 2.3 you may use
: gpgsm --show-certs /etc/ssl/certsca-certificates.crt | less
** Change RootDN Password:
Create temporary file named =passwd.ldif=:
#+begin_src
dn: olcDatabase={1}mdb,cn=config
changetype: modify
replace: olcRootPW
olcRootPW: XXXX
#+end_src
For XXXX insert the output of slappasswd and run
: ldapmodify -Q -Y EXTERNAL -H ldapi:/// -f passwd.ldif
followed by
: ldappasswd -x -D cn=admin,dc=example,dc=com -W -S
and enter the new and old password again.
** Show ACLs
: ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b 'cn=config' olcAccess
** Show a list of databases
: ldapsearch -Q -Y EXTERNAL -H ldapi:/// -b 'cn=config' | grep ^olcDatabase:
** Change the log level
To debug access problems, it is useful to change the log level:
: printf "dn: cn=config\nchangetype: %s\nreplace: %s\n%s: %s\n" \
: modify olcLogLevel olcLogLevel ACL | ldapadd -Q -Y EXTERNAL -H ldapi:///
to revert replace "ACL" by "none".
* How to use with Active Directory
** Extending the AD Schema
The Active Directory on Windows is actually an LDAP server but
configuration differs from OpenLDAP. The used schema is the same but
-the data objects are slighly different. To extend the schema the
+the data objects are slightly different. To extend the schema the
LDIF format is used but with variants of the files used for OpenLDAP.
Thus please download these two files:
- [[https://gnupg.org/misc/gnupg-ldap-ad-schema.ldif]]
- [[https://gnupg.org/misc/gnupg-ldap-ad-init.ldif]].
*Important*: Backup your Active Directory before you extend the
schema. There are *no ways to revert changes* made to a schema. You
should also first try this all on a test system and not on a
production system.
-To extend the schema become Adminstrator on your Primary Domain
+To extend the schema become Administrator on your Primary Domain
Controller and open a shell (Command Prompt). Copy the above
mentioned ldif files to your working directory and run the following
command:
: ldifde -i -f gnupg-ldap-ad-schema.ldif
: -c "DC=EXAMPLEDC" "#configurationNamingContext"
Note that this is a single line (for an LDS installation you need to
add more options like =-s localhost=). If the command succeeds the
schema has been extended to store OpenPGP keys at a well known
location. The next step is to provide information and space in the
tree. This is done similar to the above, namely:
: ldifde -i -v -f gnupg-ldap-ad-init.ldif
: -c "DC=EXAMPLEDC" "#defaultNamingContext"
You may now check your work with ADSI (enter "adsiedit"). Compare
with this [[https://gnupg.org/blog/img/ad-with-gnupg-schema.png][screenshot]] and notice the two marked entries.
The last step is to setup permissions. This depends on your
policy. Here we assume that all authenticated users get read access
to all OpenPGP keys and only certain users may insert or update those
keys.
What you need to do in all cases is to give the group /Everyone/ read
access to the =CN=PGPServerInfo= object. This allows the
clients to notice that the schema has been installed and where to look
further.
The actual keys will be stored under =CN=GnuPG Keys=. Thus give all
users of the /AuthenticatedUsers/ group read access and use the
Advanced button to set /Applies to/ to /This object and all descendant
objects/.
To insert and update keys, use a group or users and give them
permissions for =CN=GnuPG Keys= to /Read/, /Write/, /Create all child
objects/, and /Delete all child objects/. As above make sure
that these permissions apply to /This object and all descendant
objects/.
In case you want to access the keys also from non-Windows boxes, it is
probably best to create a dedicated guest user for read access.
** Using GnuPG with AD
Using the Active Directory is really easy since GnuPG 2.2.26: You only
need to put
: keyserver ldap:///
into =dirmngr.conf= and Windows takes care of authentication. Note
that we use 3 slashes and not ldaps because AD takes care of
protecting the traffic. If you use an LDS configure this
: keyserver ldap://mykeyserver.example.org/????gpgNtds=1
this will use the LDS at the given server (add a port if required) and
uses the AD for authentication.
GnuPG can also be advised to consult this configured AD or LDS similar
to a Web Key Directory (WKD). For this put
: auto-key-locate local,ntds,wkd
into =gpg.conf= so that a missing key is first looked up in the AD or
LDS before a WKD query is done.
diff --git a/doc/ldap/gnupg-ldap-ad-schema.ldif b/doc/ldap/gnupg-ldap-ad-schema.ldif
index cf07744d7..5ee5ccb13 100644
--- a/doc/ldap/gnupg-ldap-ad-schema.ldif
+++ b/doc/ldap/gnupg-ldap-ad-schema.ldif
@@ -1,343 +1,343 @@
# gnupg-ldap-ad-scheme.ldif -*- conf -*-
#
-# Schema for an OpenPGP LDAP keyserver. This is a slighly enhanced
+# Schema for an OpenPGP LDAP keyserver. This is a slightly enhanced
# version of the original LDAP schema used for PGP keyservers as
# installed at quite some sites.
# Revision: 2021-09-01 v1
# Some notes:
# - Backup your AD! It is not possible to revert changes of the schema.
# - Try it first on a test system.
# - To import the new attributes and classes use:
# ldifde -i -v -f gnupg-ldap-ad-schema.ldif
# -c "DC=EXAMPLEDC" "#configurationNamingContext"
# (the above command is given as one line)
-# - The schema does not get its own distingished name as done with OpenLDAP.
+# - The schema does not get its own distinguished name as done with OpenLDAP.
# - The first GUID we use is f406e7a5-a5ea-411e-9ddd-2e4e66899800
# and incremented for each attribute.
#
# - Some OIDs, oMSyntax, and original OIDs:
# 2.5.5.1 (127) Object (DS-DN) (1.3.6.1.4.1.1466.115.121.1.12)
# 2.5.5.3 (27) Case-sensitive string
# 2.5.5.9 (2) 32 bit signed integer
# 2.5.5.10 (4) Octet string (1.3.6.1.4.1.1466.115.121.1.26)
# 2.5.5.11 (23) UTC-Time string
# 2.5.5.12 (64) Case-insensitive Unicode string
# 2.5.5.12 (64) Directory String in UTF-8 (1.3.6.1.4.1.1466.115.121.1.15)
# 2.5.5.16 (65) 64 bit signed integer
# The base DN for the PGP key space by querying the
# pgpBaseKeySpaceDN attribute (This is normally
# 'ou=GnuPG Keys,dc=example,dc=com').
dn: CN=pgpBaseKeySpaceDN,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.8
lDAPDisplayName: pgpBaseKeySpaceDN
description: Points to DN of the object that will store the PGP keys.
attributeSyntax: 2.5.5.1
oMSyntax: 127
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAA==
# See gnupg-ldap-init.ldif for a description of this attribute
dn: CN=pgpSoftware,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.9
lDAPDisplayName: pgpSoftware
description: 'Origin of the GnuPG keyserver schema'
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAQ==
# See gnupg-ldap-init.ldif for a description of this attribute
dn: CN=pgpVersion,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.10
lDAPDisplayName: pgpVersion
description: Version of this schema
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAg==
# The attribute holding the OpenPGP keyblock.
# The legacy PGP LDAP server used pgpKeyV2 instead.
dn: CN=pgpKey,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.11
lDAPDisplayName: pgpKey
description: OpenPGP public key block
attributeSyntax: 2.5.5.10
oMSyntax: 4
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYAw==
# The long key-ID
dn: CN=pgpCertID,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.12
lDAPDisplayName: pgpCertID
description: OpenPGP long key id
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBA==
# A flag to temporary disable a keyblock
dn: CN=pgpDisabled,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.13
lDAPDisplayName: pgpDisabled
description: pgpDisabled attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBQ==
# The short key id. This is actually not required and should thus not
# be used by client software.
dn: CN=pgpKeyID,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.14
lDAPDisplayName: pgpKeyID
description: OpenPGP short key id
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBg==
# The algorithm of the key. Used to be "RSA" or "DSS/DH".
dn: CN=pgpKeyType,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.15
lDAPDisplayName: pgpKeyType
description: pgpKeyType attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYBw==
# The User-ID. GnuPG maps its user-ID classes this way:
# exact: (pgpUserID=%s)
# substr: (pgpUserID=*%s*)
# mail: (pgpUserID=*<%s>*)
# mailsub: (pgpUserID=*<*%s*>*)
# mailend: (pgpUserID=*<*%s>*)
dn: CN=pgpUserID,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.16
lDAPDisplayName: pgpUserID
description: User ID(s) associated with the key
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCA==
# The creation time of the primary key.
# Stored in ISO format: "20201231 120000"
dn: CN=pgpKeyCreateTime,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.17
lDAPDisplayName: pgpKeyCreateTime
description: Primary key creation time
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCQ==
# SignerIDs are not used
dn: CN=pgpSignerID,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.18
lDAPDisplayName: pgpSignerID
description: pgpSignerID attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCg==
# A value of 1 indicates that the keyblock has been revoked
dn: CN=pgpRevoked,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.19
lDAPDisplayName: pgpRevoked
description: pgpRevoked attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYCw==
# The Subkey key ids (16 hex digits)
dn: CN=pgpSubKeyID,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.20
lDAPDisplayName: pgpSubKeyID
description: Sub-key ID(s) of the PGP key
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDA==
# A hint on the keysize.
dn: CN=pgpKeySize,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.21
lDAPDisplayName: pgpKeySize
description: pgpKeySize attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDQ==
# Expiration time of the primary key.
# Stored in ISO format: "20201231 120000"
dn: CN=pgpKeyExpireTime,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.3401.8.2.22
lDAPDisplayName: pgpKeyExpireTime
description: pgpKeyExpireTime attribute for PGP
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDg==
# The hex encoded fingerprint of the primary key.
dn: CN=gpgFingerprint,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.11591.2.4.1.1
lDAPDisplayName: gpgFingerprint
description: Fingerprint of the primary key
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: TRUE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYDw==
# A list of hex encoded fingerprints of the subkeys.
dn: CN=gpgSubFingerprint,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.11591.2.4.1.2
lDAPDisplayName: gpgSubFingerprint
description: Fingerprints of the secondary keys
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEA==
# A list of utf8 encoded addr-spec used instead of mail/rfc822Mailbox
dn: CN=gpgMailbox,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: attributeSchema
attributeID: 1.3.6.1.4.1.11591.2.4.1.3
lDAPDisplayName: gpgMailbox
description: The utf8 encoded addr-spec of a mailbox
attributeSyntax: 2.5.5.12
oMSyntax: 64
isSingleValued: FALSE
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYEQ==
# Unused GUIDs:
# 9AbnpaXqQR6d3S5OZomYEw==
# 9AbnpaXqQR6d3S5OZomYFA==
# 9AbnpaXqQR6d3S5OZomYFQ==
# 9AbnpaXqQR6d3S5OZomYFg==
# 9AbnpaXqQR6d3S5OZomYFw==
# 9AbnpaXqQR6d3S5OZomYGA==
# 9AbnpaXqQR6d3S5OZomYGQ==
# 9AbnpaXqQR6d3S5OZomYGg==
# 9AbnpaXqQR6d3S5OZomYGw==
# 9AbnpaXqQR6d3S5OZomYHA==
# 9AbnpaXqQR6d3S5OZomYHQ==
# 9AbnpaXqQR6d3S5OZomYHg==
# 9AbnpaXqQR6d3S5OZomYHw==
# Sync the schema cache
DN:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-
#
# Used by regular LDAP servers to indicate pgp support.
# (structural class)
#
dn: CN=pgpServerInfo,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: classSchema
governsID: 1.3.6.1.4.1.3401.8.2.23
lDAPDisplayName: pgpServerInfo
description: An OpenPGP public keyblock store
subClassOf: top
objectClassCategory: 1
mustContain: cn
mustContain: pgpBaseKeySpaceDN
mayContain: pgpSoftware
mayContain: pgpVersion
systemPossSuperiors: domainDNS
systemPossSuperiors: container
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYIA==
# The original PGP key object extended with a few extra attributes.
# All new software should set them but this is not enforced for
# backward compatibility of client software.
# (structural class, writable)
dn: CN=pgpKeyInfo,CN=Schema,DC=EXAMPLEDC
changetype: ntdsSchemaAdd
objectClass: classSchema
governsID: 1.3.6.1.4.1.3401.8.2.24
lDAPDisplayName: pgpKeyInfo
description: An OpenPGP public keyblock
subClassOf: top
objectClassCategory: 1
instanceType: 4
mustContain: pgpCertID
mustContain: pgpKey
mayContain: pgpDisabled
mayContain: pgpKeyID
mayContain: pgpKeyType
mayContain: pgpUserID
mayContain: pgpKeyCreateTime
mayContain: pgpSignerID
mayContain: pgpRevoked
mayContain: pgpSubKeyID
mayContain: pgpKeySize
mayContain: pgpKeyExpireTime
mayContain: gpgFingerprint
mayContain: gpgSubFingerprint
mayContain: gpgMailbox
systemPossSuperiors: container
schemaIDGUID:: 9AbnpaXqQR6d3S5OZomYIQ==
# Sync the schema cache
DN:
changetype: modify
add: schemaUpdateNow
schemaUpdateNow: 1
-
#
# end-of-file
#
diff --git a/doc/ldap/gnupg-ldap-schema.ldif b/doc/ldap/gnupg-ldap-schema.ldif
index be6a4646a..cec7ad084 100644
--- a/doc/ldap/gnupg-ldap-schema.ldif
+++ b/doc/ldap/gnupg-ldap-schema.ldif
@@ -1,205 +1,205 @@
# gnupg-ldap-scheme.ldif -*- conf -*-
#
-# Schema for an OpenPGP LDAP keyserver. This is a slighly enhanced
+# Schema for an OpenPGP LDAP keyserver. This is a slightly enhanced
# version of the original LDAP schema used for PGP keyservers as
# installed at quite some sites.
# Revision: 2020-10-07
# Note: The index 1000 is just a high number so that OpenLDAP assigns
# the next available number.
dn: cn={1000}gnupg-keyserver,cn=schema,cn=config
objectClass: olcSchemaConfig
# The base DN for the PGP key space by querying the
# pgpBaseKeySpaceDN attribute (This is normally
# 'ou=PGP Keys,dc=example,dc=com').
olcAttributeTypes: {0}(
1.3.6.1.4.1.3401.8.2.8
NAME 'pgpBaseKeySpaceDN'
DESC 'Points to DN of the object that will store the PGP keys.'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.12
SINGLE-VALUE )
# See gnupg-ldap-init.ldif for a description of the next two attributes
olcAttributeTypes: {1}(
1.3.6.1.4.1.3401.8.2.9
NAME 'pgpSoftware'
DESC 'Origin of the schema'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
olcAttributeTypes: {2}(
1.3.6.1.4.1.3401.8.2.10
NAME 'pgpVersion'
DESC 'Version of this schema'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
#
# The attribute holding the OpenPGP keyblock.
# The legacy PGP LDAP server used pgpKeyV2 instead.
olcAttributeTypes: {3}(
1.3.6.1.4.1.3401.8.2.11
NAME 'pgpKey'
DESC 'OpenPGP public key block'
SYNTAX 1.3.6.1.4.1.1466.115.121.1.26
SINGLE-VALUE )
# The long key-ID
olcAttributeTypes: {4}(
1.3.6.1.4.1.3401.8.2.12
NAME 'pgpCertID'
DESC 'OpenPGP long key id'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# A flag to temporary disable a keyblock
olcAttributeTypes: {5}(
1.3.6.1.4.1.3401.8.2.13
NAME 'pgpDisabled'
DESC 'pgpDisabled attribute for PGP'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# The short key id. This is actually not required and should thus not
-# be used by cleint software.
+# be used by client software.
olcAttributeTypes: {6}(
1.3.6.1.4.1.3401.8.2.14
NAME 'pgpKeyID'
DESC 'OpenPGP short key id'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# The algorithm of the key. Used to be "RSA" or "DSS/DH".
olcAttributeTypes: {7}(
1.3.6.1.4.1.3401.8.2.15
NAME 'pgpKeyType'
DESC 'pgpKeyType attribute for PGP'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# The User-ID. GnuPG maps its user-ID classes this way:
# exact: (pgpUserID=%s)
# substr: (pgpUserID=*%s*)
# mail: (pgpUserID=*<%s>*)
# mailsub: (pgpUserID=*<*%s*>*)
# mailend: (pgpUserID=*<*%s>*)
olcAttributeTypes: {8}(
1.3.6.1.4.1.3401.8.2.16
NAME 'pgpUserID'
DESC 'User ID(s) associated with the key'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
# The creation time of the primary key.
# Stored in ISO format: "20201231 120000"
olcAttributeTypes: {9}(
1.3.6.1.4.1.3401.8.2.17
NAME 'pgpKeyCreateTime'
DESC 'Primary key creation time'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# Not used
olcAttributeTypes: {10}(
1.3.6.1.4.1.3401.8.2.18
NAME 'pgpSignerID'
DESC 'pgpSignerID attribute for PGP'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
# A value of 1 indicated that the keyblock has been revoked
olcAttributeTypes: {11}(
1.3.6.1.4.1.3401.8.2.19
NAME 'pgpRevoked'
DESC 'pgpRevoked attribute for PGP'
EQUALITY caseIgnoreMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# Note that there is no short subkeyid despite that the name
# is similar to the name of short keyid of the primary key.
olcAttributeTypes: {12}(
1.3.6.1.4.1.3401.8.2.20
NAME 'pgpSubKeyID'
DESC 'OpenPGP long Subkey ID(s) of the PGP key.'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
# A hint on the keysize.
olcAttributeTypes: {13}(
1.3.6.1.4.1.3401.8.2.21
NAME 'pgpKeySize'
DESC 'pgpKeySize attribute for PGP'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
# Expiration time of the primary key.
# Stored in ISO format: "20201231 120000"
olcAttributeTypes: {14}(
1.3.6.1.4.1.3401.8.2.22
NAME 'pgpKeyExpireTime'
DESC 'pgpKeyExpireTime attribute for PGP'
EQUALITY caseIgnoreMatch
ORDERING caseIgnoreOrderingMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
#
# The hex encoded fingerprint of the primary key.
olcAttributeTypes: {15}(
1.3.6.1.4.1.11591.2.4.1.1
NAME 'gpgFingerprint'
DESC 'Fingerprint of the primary key'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15
SINGLE-VALUE )
# A list of hex encoded fingerprints of the subkeys.
olcAttributeTypes: {16}(
1.3.6.1.4.1.11591.2.4.1.2
NAME 'gpgSubFingerprint'
DESC 'Fingerprints of the secondary keys'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
# A list of utf8 encoded addr-spec used instead of mail/rfc822Mailbox
olcAttributeTypes: {17}(
1.3.6.1.4.1.11591.2.4.1.3
NAME 'gpgMailbox'
DESC 'The utf8 encoded addr-spec of a mailbox'
EQUALITY caseIgnoreMatch
SUBSTR caseIgnoreSubstringsMatch
SYNTAX 1.3.6.1.4.1.1466.115.121.1.15 )
#
# Note: OID 1.3.6.1.4.1.11591.2.4.1.4 is reserved
# because it was used for short time during development.
#
#
# Used by regular LDAP servers to indicate pgp support.
#
olcObjectClasses: {0}(
1.3.6.1.4.1.3401.8.2.23
NAME 'pgpServerInfo'
DESC 'An OpenPGP public keyblock store'
SUP top
STRUCTURAL MUST ( cn $ pgpBaseKeySpaceDN )
MAY ( pgpSoftware $ pgpVersion ) )
#
# The original PGP key object extended with a few extra attributes.
# All new software should set them but this is not enforced for
# backward compatibility
olcObjectClasses: {1}(
1.3.6.1.4.1.3401.8.2.24
NAME 'pgpKeyInfo'
DESC 'An OpenPGP public keyblock'
SUP top
STRUCTURAL MUST ( pgpCertID $ pgpKey )
MAY ( pgpDisabled $ pgpKeyID $ pgpKeyType $
pgpUserID $ pgpKeyCreateTime $ pgpSignerID $
pgpRevoked $ pgpSubKeyID $ pgpKeySize $
pgpKeyExpireTime $ gpgFingerprint $
gpgSubFingerprint $ gpgMailbox ) )
#
# end-of-file
#
diff --git a/doc/tools.texi b/doc/tools.texi
index 9ce0e6bb8..efd1c4902 100644
--- a/doc/tools.texi
+++ b/doc/tools.texi
@@ -1,2258 +1,2258 @@
@c Copyright (C) 2004, 2008 Free Software Foundation, Inc.
@c This is part of the GnuPG manual.
@c For copying conditions, see the file GnuPG.texi.
@include defs.inc
@node Helper Tools
@chapter Helper Tools
GnuPG comes with a couple of smaller tools:
@menu
* watchgnupg:: Read logs from a socket.
* gpgv:: Verify OpenPGP signatures.
* addgnupghome:: Create .gnupg home directories.
* gpgconf:: Modify .gnupg home directories.
* applygnupgdefaults:: Run gpgconf for all users.
* gpg-preset-passphrase:: Put a passphrase into the cache.
* gpg-connect-agent:: Communicate with a running agent.
* dirmngr-client:: How to use the Dirmngr client tool.
* gpgparsemail:: Parse a mail message into an annotated format
* gpgtar:: Encrypt or sign files into an archive.
* gpg-check-pattern:: Check a passphrase on stdin against the patternfile.
@end menu
@c
@c WATCHGNUPG
@c
@manpage watchgnupg.1
@node watchgnupg
@section Read logs from a socket
@ifset manverb
.B watchgnupg
\- Read and print logs from a socket
@end ifset
@mansect synopsis
@ifset manverb
.B watchgnupg
.RB [ \-\-force ]
.RB [ \-\-verbose ]
.I socketname
@end ifset
@mansect description
Most of the main utilities are able to write their log files to a Unix
Domain socket if configured that way. @command{watchgnupg} is a simple
listener for such a socket. It ameliorates the output with a time stamp
and makes sure that long lines are not interspersed with log output from
other utilities. This tool is not available for Windows.
@noindent
@command{watchgnupg} is commonly invoked as
@example
watchgnupg
@end example
which is a shorthand for
@example
watchgnupg --force $(gpgconf --list-dirs socketdir)/S.log
@end example
To watch GnuPG running with a different home directory, use
@example
watchgnupg --homedir DIR
@end example
@manpause
@noindent
This starts it on the current terminal for listening on the standard
logging socket (this is commonly @file{/var/run/user/UID/gnupg/S.log}
or if no such user directory hierarchy exists @file{~/.gnupg/S.log}).
@mansect options
@noindent
@command{watchgnupg} understands these options:
@table @gnupgtabopt
@item --force
@opindex force
Delete an already existing socket file. This option is implicitly used
if no socket name has been given on the command line.
@item --homedir @var{DIR}
If no socket name is given on the command line, pass @var{DIR} to
gpgconf so that the socket for a GnuPG running with DIR has its home
directory is used. Note that the environment variable @var{GNUPGHOME}
is ignored by watchgnupg.
@anchor{option watchgnupg --tcp}
@item --tcp @var{n}
Instead of reading from a local socket, listen for connects on TCP
port @var{n}. A Unix domain socket can optionally also be given as a
second source. This option does not use a default socket name.
@item --time-only
@opindex time-only
Do not print the date part of the timestamp.
@item --verbose
@opindex verbose
Enable extra informational output.
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@noindent
@mansect examples
@chapheading Examples
@example
$ watchgnupg --time-only
@end example
This waits for connections on the local socket
(e.g., @file{/var/run/user/1234/gnupg/S.log}) and shows all log
entries. To make this work the option @option{log-file} needs to be
used with all modules which logs are to be shown. The suggested entry
for the configuration files is:
@example
log-file socket://
@end example
If the default socket as given above and returned by @code{"echo $(gpgconf
--list-dirs socketdir)/S.log"} is not desired an arbitrary socket name
can be specified, for example @file{socket:///home/foo/bar/mysocket}.
For debugging purposes it is also possible to do remote logging. Take
care if you use this feature because the information is send in the
clear over the network. Use this syntax in the conf files:
@example
log-file tcp://192.168.1.1:4711
@end example
You may use any port and not just 4711 as shown above; only IP
addresses are supported (v4 and v6) and no host names. You need to
start @command{watchgnupg} with the @option{tcp} option. Note that
under Windows the registry entry
@var{HKCU\Software\GNU\GnuPG:DefaultLogFile} can be used to change the
default log output from @code{stderr} to whatever is given by that
entry. However the only useful entry is a TCP name for remote
debugging.
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1)
@end ifset
@include see-also-note.texi
@c
@c GPGV
@c
@include gpgv.texi
@c
@c ADDGNUPGHOME
@c
@manpage addgnupghome.8
@node addgnupghome
@section Create .gnupg home directories
@ifset manverb
.B addgnupghome
\- Create .gnupg home directories
@end ifset
@mansect synopsis
@ifset manverb
.B addgnupghome
.I account_1
.IR account_2 ... account_n
@end ifset
@mansect description
If GnuPG is installed on a system with existing user accounts, it is
sometimes required to populate the GnuPG home directory with existing
files. Especially a @file{trustlist.txt} and a keybox with some
initial certificates are often desired. This script helps to do this
by copying all files from @file{/etc/skel/.gnupg} to the home
directories of the accounts given on the command line. It takes care
not to overwrite existing GnuPG home directories.
@noindent
@command{addgnupghome} is invoked by root as:
@example
addgnupghome account1 account2 ... accountn
@end example
@c
@c GPGCONF
@c
@manpage gpgconf.1
@node gpgconf
@section Modify .gnupg home directories
@ifset manverb
.B gpgconf
\- Modify .gnupg home directories
@end ifset
@mansect synopsis
@ifset manverb
.B gpgconf
.RI [ options ]
.B \-\-list-components
.br
.B gpgconf
.RI [ options ]
.B \-\-list-options
.I component
.br
.B gpgconf
.RI [ options ]
.B \-\-change-options
.I component
@end ifset
@mansect description
The @command{gpgconf} is a utility to automatically and reasonable
safely query and modify configuration files in the @file{.gnupg} home
directory. It is designed not to be invoked manually by the user, but
automatically by graphical user interfaces (GUI).@footnote{Please note
that currently no locking is done, so concurrent access should be
avoided. There are some precautions to avoid corruption with
concurrent usage, but results may be inconsistent and some changes may
get lost. The stateless design makes it difficult to provide more
guarantees.}
@command{gpgconf} provides access to the configuration of one or more
components of the GnuPG system. These components correspond more or
less to the programs that exist in the GnuPG framework, like GPG,
GPGSM, DirMngr, etc. But this is not a strict one-to-one
relationship. Not all configuration options are available through
@command{gpgconf}. @command{gpgconf} provides a generic and abstract
method to access the most important configuration options that can
feasibly be controlled via such a mechanism.
@command{gpgconf} can be used to gather and change the options
available in each component, and can also provide their default
values. @command{gpgconf} will give detailed type information that
can be used to restrict the user's input without making an attempt to
commit the changes.
@command{gpgconf} provides the backend of a configuration editor. The
configuration editor would usually be a graphical user interface
program that displays the current options, their default
values, and allows the user to make changes to the options. These
changes can then be made active with @command{gpgconf} again. Such a
program that uses @command{gpgconf} in this way will be called GUI
throughout this section.
@menu
* Invoking gpgconf:: List of all commands and options.
* Format conventions:: Formatting conventions relevant for all commands.
* Listing components:: List all gpgconf components.
* Checking programs:: Check all programs known to gpgconf.
* Listing options:: List all options of a component.
* Changing options:: Changing options of a component.
* Listing global options:: List all global options.
* Querying versions:: Get and compare software versions.
* Files used by gpgconf:: What files are used by gpgconf.
@end menu
@manpause
@node Invoking gpgconf
@subsection Invoking gpgconf
@mansect commands
One of the following commands must be given:
@table @gnupgtabopt
@item --list-components
List all components. This is the default command used if none is
specified.
@item --check-programs
List all available backend programs and test whether they are runnable.
@item --list-options @var{component}
List all options of the component @var{component}.
@item --change-options @var{component}
Change the options of the component @var{component}.
@item --check-options @var{component}
Check the options for the component @var{component}.
@item --apply-profile @var{file}
Apply the configuration settings listed in @var{file} to the
configuration files. If @var{file} has no suffix and no slashes the
command first tries to read a file with the suffix @code{.prf} from
the data directory (@code{gpgconf --list-dirs datadir}) before it
reads the file verbatim. A profile is divided into sections using the
bracketed component name. Each section then lists the option which
shall go into the respective configuration file.
@item --apply-defaults
Update all configuration files with values taken from the global
configuration file (usually @file{/etc/gnupg/gpgconf.conf}).
Note: This is a legacy mechanism. Please use global configuration
files instead.
@item --list-dirs [@var{names}]
@itemx -L
Lists the directories used by @command{gpgconf}. One directory is
listed per line, and each line consists of a colon-separated list where
the first field names the directory type (for example @code{sysconfdir})
and the second field contains the percent-escaped directory. Although
they are not directories, the socket file names used by
@command{gpg-agent} and @command{dirmngr} are printed as well. Note
that the socket file names and the @code{homedir} lines are the default
names and they may be overridden by command line switches. If
@var{names} are given only the directories or file names specified by
the list names are printed without any escaping.
@item --list-config [@var{filename}]
List the global configuration file in a colon separated format. If
@var{filename} is given, check that file instead.
@item --check-config [@var{filename}]
Run a syntax check on the global configuration file. If @var{filename}
is given, check that file instead.
@item --query-swdb @var{package_name} [@var{version_string}]
Returns the current version for @var{package_name} and if
@var{version_string} is given also an indicator on whether an update
is available. The actual file with the software version is
automatically downloaded and checked by @command{dirmngr}.
@command{dirmngr} uses a thresholds to avoid download the file too
often and it does this by default only if it can be done via Tor. To
force an update of that file this command can be used:
@example
gpg-connect-agent --dirmngr 'loadswdb --force' /bye
@end example
@item --reload [@var{component}]
@itemx -R
@opindex reload
Reload all or the given component. This is basically the same as
sending a SIGHUP to the component. Components which don't support
reloading are ignored. Without @var{component} or by using "all" for
@var{component} all components which are daemons are reloaded.
@item --launch [@var{component}]
@opindex launch
If the @var{component} is not already running, start it.
@command{component} must be a daemon. This is in general not required
because the system starts these daemons as needed. However, external
software making direct use of @command{gpg-agent} or @command{dirmngr}
may use this command to ensure that they are started. Using "all" for
@var{component} launches all components which are daemons.
@item --kill [@var{component}]
@itemx -K
@opindex kill
Kill the given component that runs as a daemon, including
@command{gpg-agent}, @command{dirmngr}, and @command{scdaemon}. A
@command{component} which does not run as a daemon will be ignored.
Using "all" for @var{component} kills all components running as
daemons. Note that as of now reload and kill have the same effect for
@command{scdaemon}.
@item --create-socketdir
@opindex create-socketdir
Create a directory for sockets below /run/user or /var/run/user. This
is command is only required if a non default home directory is used
and the /run based sockets shall be used. For the default home
directory GnuPG creates a directory on the fly.
@item --remove-socketdir
@opindex remove-socketdir
Remove a directory created with command @option{--create-socketdir}.
@item --unlock @var{name}
@itemx --lock @var{name}
Remove a stale lock file hold for @file{file}. The file is
expected in the current GnuPG home directory. This command is usually
not required because GnuPG is able to detect and remove stale lock
files. Before using the command make sure that the file protected by
the lock file is actually not in use. The lock command may be used to
lock an accidentally removed lock file. Note that the commands have no
effect on Windows because the mere existence of a lock file does not
mean that the lock is active.
@end table
@mansect options
The following options may be used:
@table @gnupgtabopt
@item -o @var{file}
@itemx --output @var{file}
Write output to @var{file}. Default is to write to stdout.
@item -v
@itemx --verbose
Outputs additional information while running. Specifically, this
extends numerical field values by human-readable descriptions.
@item -q
@itemx --quiet
@opindex quiet
Try to be as quiet as possible.
@include opt-homedir.texi
@item --chuid @var{uid}
@opindex chuid
Change the current user to @var{uid} which may either be a number or a
name. This can be used from the root account to get information on
the GnuPG environment of the specified user or to start or kill
daemons. If @var{uid} is not the current UID a standard PATH is set
and the envvar GNUPGHOME is unset. To override the latter the option
@option{--homedir} can be used. This option has currently no effect
on Windows.
@item -n
@itemx --dry-run
Do not actually change anything. This is currently only implemented
for @code{--change-options} and can be used for testing purposes.
@item -r
@itemx --runtime
Only used together with @code{--change-options}. If one of the
modified options can be changed in a running daemon process, signal
the running daemon to ask it to reparse its configuration file after
changing.
This means that the changes will take effect at run-time, as far as
this is possible. Otherwise, they will take effect at the next start
of the respective backend programs.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}. This
program returns the status messages SUCCESS or FAILURE which are
helpful when the caller uses a double fork approach and can't easily
get the return code of the process.
@manpause
@end table
@node Format conventions
@subsection Format conventions
Some lines in the output of @command{gpgconf} contain a list of
colon-separated fields. The following conventions apply:
@itemize @bullet
@item
The GUI program is required to strip off trailing newline and/or
carriage return characters from the output.
@item
@command{gpgconf} will never leave out fields. If a certain version
provides a certain field, this field will always be present in all
@command{gpgconf} versions from that time on.
@item
Future versions of @command{gpgconf} might append fields to the list.
New fields will always be separated from the previously last field by
a colon separator. The GUI should be prepared to parse the last field
it knows about up until a colon or end of line.
@item
Not all fields are defined under all conditions. You are required to
ignore the content of undefined fields.
@end itemize
There are several standard types for the content of a field:
@table @asis
@item verbatim
Some fields contain strings that are not escaped in any way. Such
fields are described to be used @emph{verbatim}. These fields will
never contain a colon character (for obvious reasons). No de-escaping
or other formatting is required to use the field content. This is for
easy parsing of the output, when it is known that the content can
never contain any special characters.
@item percent-escaped
Some fields contain strings that are described to be
@emph{percent-escaped}. Such strings need to be de-escaped before
their content can be presented to the user. A percent-escaped string
is de-escaped by replacing all occurrences of @code{%XY} by the byte
that has the hexadecimal value @code{XY}. @code{X} and @code{Y} are
from the set @code{0-9a-f}.
@item localized
Some fields contain strings that are described to be @emph{localized}.
Such strings are translated to the active language and formatted in
the active character set.
@item @w{unsigned number}
Some fields contain an @emph{unsigned number}. This number will
always fit into a 32-bit unsigned integer variable. The number may be
followed by a space, followed by a human readable description of that
value (if the verbose option is used). You should ignore everything
in the field that follows the number.
@item @w{signed number}
Some fields contain a @emph{signed number}. This number will always
fit into a 32-bit signed integer variable. The number may be followed
by a space, followed by a human readable description of that value (if
the verbose option is used). You should ignore everything in the
field that follows the number.
@item @w{boolean value}
Some fields contain a @emph{boolean value}. This is a number with
either the value 0 or 1. The number may be followed by a space,
followed by a human readable description of that value (if the verbose
option is used). You should ignore everything in the field that follows
the number; checking just the first character is sufficient in this
case.
@item option
Some fields contain an @emph{option} argument. The format of an
option argument depends on the type of the option and on some flags:
@table @asis
@item no argument
The simplest case is that the option does not take an argument at all
(@var{type} @code{0}). Then the option argument is an unsigned number
that specifies how often the option occurs. If the @code{list} flag
is not set, then the only valid number is @code{1}. Options that do
not take an argument never have the @code{default} or @code{optional
arg} flag set.
@item number
If the option takes a number argument (@var{alt-type} is @code{2} or
@code{3}), and it can only occur once (@code{list} flag is not set),
then the option argument is either empty (only allowed if the argument
is optional), or it is a number. A number is a string that begins
with an optional minus character, followed by one or more digits. The
number must fit into an integer variable (unsigned or signed,
depending on @var{alt-type}).
@item number list
If the option takes a number argument and it can occur more than once,
then the option argument is either empty, or it is a comma-separated
list of numbers as described above.
@item string
If the option takes a string argument (@var{alt-type} is 1), and it
can only occur once (@code{list} flag is not set) then the option
argument is either empty (only allowed if the argument is optional),
or it starts with a double quote character (@code{"}) followed by a
percent-escaped string that is the argument value. Note that there is
only a leading double quote character, no trailing one. The double
quote character is only needed to be able to differentiate between no
value and the empty string as value.
@item string list
If the option takes a string argument and it can occur more than once,
then the option argument is either empty, or it is a comma-separated
list of string arguments as described above.
@end table
@end table
The active language and character set are currently determined from
the locale environment of the @command{gpgconf} program.
@c FIXME: Document the active language and active character set. Allow
@c to change it via the command line?
@mansect usage
@node Listing components
@subsection Listing components
The command @code{--list-components} will list all components that can
be configured with @command{gpgconf}. Usually, one component will
correspond to one GnuPG-related program and contain the options of
that program's configuration file that can be modified using
@command{gpgconf}. However, this is not necessarily the case. A
component might also be a group of selected options from several
programs, or contain entirely virtual options that have a special
effect rather than changing exactly one option in one configuration
file.
A component is a set of configuration options that semantically belong
together. Furthermore, several changes to a component can be made in
an atomic way with a single operation. The GUI could for example
provide a menu with one entry for each component, or a window with one
tabulator sheet per component.
The command @code{--list-components} lists all available
components, one per line. The format of each line is:
@code{@var{name}:@var{description}:@var{pgmname}:}
@table @var
@item name
This field contains a name tag of the component. The name tag is used
to specify the component in all communication with @command{gpgconf}.
The name tag is to be used @emph{verbatim}. It is thus not in any
escaped format.
@item description
The @emph{string} in this field contains a human-readable description
of the component. It can be displayed to the user of the GUI for
informational purposes. It is @emph{percent-escaped} and
@emph{localized}.
@item pgmname
The @emph{string} in this field contains the absolute name of the
program's file. It can be used to unambiguously invoke that program.
It is @emph{percent-escaped}.
@end table
Example:
@example
$ gpgconf --list-components
gpg:GPG for OpenPGP:/usr/local/bin/gpg2:
gpg-agent:GPG Agent:/usr/local/bin/gpg-agent:
scdaemon:Smartcard Daemon:/usr/local/bin/scdaemon:
gpgsm:GPG for S/MIME:/usr/local/bin/gpgsm:
dirmngr:Directory Manager:/usr/local/bin/dirmngr:
@end example
@node Checking programs
@subsection Checking programs
The command @code{--check-programs} is similar to
@code{--list-components} but works on backend programs and not on
components. It runs each program to test whether it is installed and
runnable. This also includes a syntax check of all config file options
of the program.
The command @code{--check-programs} lists all available
programs, one per line. The format of each line is:
@code{@var{name}:@var{description}:@var{pgmname}:@var{avail}:@var{okay}:@var{cfgfile}:@var{line}:@var{error}:}
@table @var
@item name
This field contains a name tag of the program which is identical to the
name of the component. The name tag is to be used @emph{verbatim}. It
is thus not in any escaped format. This field may be empty to indicate
a continuation of error descriptions for the last name. The description
and pgmname fields are then also empty.
@item description
The @emph{string} in this field contains a human-readable description
of the component. It can be displayed to the user of the GUI for
informational purposes. It is @emph{percent-escaped} and
@emph{localized}.
@item pgmname
The @emph{string} in this field contains the absolute name of the
program's file. It can be used to unambiguously invoke that program.
It is @emph{percent-escaped}.
@item avail
The @emph{boolean value} in this field indicates whether the program is
installed and runnable.
@item okay
The @emph{boolean value} in this field indicates whether the program's
config file is syntactically okay.
@item cfgfile
If an error occurred in the configuration file (as indicated by a false
value in the field @code{okay}), this field has the name of the failing
configuration file. It is @emph{percent-escaped}.
@item line
If an error occurred in the configuration file, this field has the line
number of the failing statement in the configuration file.
It is an @emph{unsigned number}.
@item error
If an error occurred in the configuration file, this field has the error
text of the failing statement in the configuration file. It is
@emph{percent-escaped} and @emph{localized}.
@end table
@noindent
In the following example the @command{dirmngr} is not runnable and the
configuration file of @command{scdaemon} is not okay.
@example
$ gpgconf --check-programs
gpg:GPG for OpenPGP:/usr/local/bin/gpg2:1:1:
gpg-agent:GPG Agent:/usr/local/bin/gpg-agent:1:1:
scdaemon:Smartcard Daemon:/usr/local/bin/scdaemon:1:0:
gpgsm:GPG for S/MIME:/usr/local/bin/gpgsm:1:1:
dirmngr:Directory Manager:/usr/local/bin/dirmngr:0:0:
@end example
@noindent
The command @w{@code{--check-options @var{component}}} will verify the
configuration file in the same manner as @code{--check-programs}, but
only for the component @var{component}.
@node Listing options
@subsection Listing options
Every component contains one or more options. Options may be gathered
into option groups to allow the GUI to give visual hints to the user
about which options are related.
The command @code{@w{--list-options @var{component}}} lists
all options (and the groups they belong to) in the component
@var{component}, one per line. @var{component} must be the string in
the field @var{name} in the output of the @code{--list-components}
command.
There is one line for each option and each group. First come all
options that are not in any group. Then comes a line describing a
group. Then come all options that belong into each group. Then comes
the next group and so on. There does not need to be any group (and in
this case the output will stop after the last non-grouped option).
The format of each line is:
@code{@var{name}:@var{flags}:@var{level}:@var{description}:@var{type}:@var{alt-type}:@var{argname}:@var{default}:@var{argdef}:@var{value}}
@table @var
@item name
This field contains a name tag for the group or option. The name tag
is used to specify the group or option in all communication with
@command{gpgconf}. The name tag is to be used @emph{verbatim}. It is
thus not in any escaped format.
@item flags
The flags field contains an @emph{unsigned number}. Its value is the
OR-wise combination of the following flag values:
@table @code
@item group (1)
If this flag is set, this is a line describing a group and not an
option.
@end table
The following flag values are only defined for options (that is, if
the @code{group} flag is not used).
@table @code
@item optional arg (2)
If this flag is set, the argument is optional. This is never set for
@var{type} @code{0} (none) options.
@item list (4)
If this flag is set, the option can be given multiple times.
@item runtime (8)
If this flag is set, the option can be changed at runtime.
@item default (16)
If this flag is set, a default value is available.
@item default desc (32)
If this flag is set, a (runtime) default is available. This and the
@code{default} flag are mutually exclusive.
@item no arg desc (64)
If this flag is set, and the @code{optional arg} flag is set, then the
option has a special meaning if no argument is given.
@item no change (128)
If this flag is set, @command{gpgconf} ignores requests to change the
value. GUI frontends should grey out this option. Note, that manual
changes of the configuration files are still possible.
@end table
@item level
This field is defined for options and for groups. It contains an
@emph{unsigned number} that specifies the expert level under which
this group or option should be displayed. The following expert levels
are defined for options (they have analogous meaning for groups):
@table @code
@item basic (0)
This option should always be offered to the user.
@item advanced (1)
This option may be offered to advanced users.
@item expert (2)
This option should only be offered to expert users.
@item invisible (3)
This option should normally never be displayed, not even to expert
users.
@item internal (4)
This option is for internal use only. Ignore it.
@end table
The level of a group will always be the lowest level of all options it
contains.
@item description
This field is defined for options and groups. The @emph{string} in
this field contains a human-readable description of the option or
group. It can be displayed to the user of the GUI for informational
purposes. It is @emph{percent-escaped} and @emph{localized}.
@item type
This field is only defined for options. It contains an @emph{unsigned
number} that specifies the type of the option's argument, if any. The
following types are defined:
Basic types:
@table @code
@item none (0)
No argument allowed.
@item string (1)
An @emph{unformatted string}.
@item int32 (2)
A @emph{signed number}.
@item uint32 (3)
An @emph{unsigned number}.
@end table
Complex types:
@table @code
@item pathname (32)
A @emph{string} that describes the pathname of a file. The file does
not necessarily need to exist.
@item ldap server (33)
A @emph{string} that describes an LDAP server in the format:
@code{@var{hostname}:@var{port}:@var{username}:@var{password}:@var{base_dn}}
@item key fingerprint (34)
A @emph{string} with a 40 digit fingerprint specifying a certificate.
@item pub key (35)
A @emph{string} that describes a certificate by user ID, key ID or
fingerprint.
@item sec key (36)
A @emph{string} that describes a certificate with a key by user ID,
key ID or fingerprint.
@item alias list (37)
A @emph{string} that describes an alias list, like the one used with
gpg's group option. The list consists of a key, an equal sign and space
separated values.
@end table
More types will be added in the future. Please see the @var{alt-type}
field for information on how to cope with unknown types.
@item alt-type
This field is identical to @var{type}, except that only the types
@code{0} to @code{31} are allowed. The GUI is expected to present the
user the option in the format specified by @var{type}. But if the
argument type @var{type} is not supported by the GUI, it can still
display the option in the more generic basic type @var{alt-type}. The
GUI must support all the defined basic types to be able to display all
options. More basic types may be added in future versions. If the
GUI encounters a basic type it doesn't support, it should report an
error and abort the operation.
@item argname
This field is only defined for options with an argument type
@var{type} that is not @code{0}. In this case it may contain a
@emph{percent-escaped} and @emph{localized string} that gives a short
name for the argument. The field may also be empty, though, in which
case a short name is not known.
@item default
This field is defined only for options for which the @code{default} or
@code{default desc} flag is set. If the @code{default} flag is set,
its format is that of an @emph{option argument} (@pxref{Format
conventions}, for details). If the default value is empty, then no
default is known. Otherwise, the value specifies the default value
for this option. If the @code{default desc} flag is set, the field is
either empty or contains a description of the effect if the option is
not given.
@item argdef
This field is defined only for options for which the @code{optional
arg} flag is set. If the @code{no arg desc} flag is not set, its
format is that of an @emph{option argument} (@pxref{Format
conventions}, for details). If the default value is empty, then no
default is known. Otherwise, the value specifies the default argument
for this option. If the @code{no arg desc} flag is set, the field is
either empty or contains a description of the effect of this option if
no argument is given.
@item value
This field is defined only for options. Its format is that of an
@emph{option argument}. If it is empty, then the option is not
explicitly set in the current configuration, and the default applies
(if any). Otherwise, it contains the current value of the option.
Note that this field is also meaningful if the option itself does not
take a real argument (in this case, it contains the number of times
the option appears).
@end table
@node Changing options
@subsection Changing options
The command @w{@code{--change-options @var{component}}} will attempt
to change the options of the component @var{component} to the
specified values. @var{component} must be the string in the field
@var{name} in the output of the @code{--list-components} command. You
have to provide the options that shall be changed in the following
format on standard input:
@code{@var{name}:@var{flags}:@var{new-value}}
@table @var
@item name
This is the name of the option to change. @var{name} must be the
string in the field @var{name} in the output of the
@code{--list-options} command.
@item flags
The flags field contains an @emph{unsigned number}. Its value is the
OR-wise combination of the following flag values:
@table @code
@item default (16)
If this flag is set, the option is deleted and the default value is
used instead (if applicable).
@end table
@item new-value
The new value for the option. This field is only defined if the
@code{default} flag is not set. The format is that of an @emph{option
argument}. If it is empty (or the field is omitted), the default
argument is used (only allowed if the argument is optional for this
option). Otherwise, the option will be set to the specified value.
@end table
@noindent
The output of the command is the same as that of
@code{--check-options} for the modified configuration file.
Examples:
To set the force option, which is of basic type @code{none (0)}:
@example
$ echo 'force:0:1' | gpgconf --change-options dirmngr
@end example
To delete the force option:
@example
$ echo 'force:16:' | gpgconf --change-options dirmngr
@end example
The @code{--runtime} option can influence when the changes take
effect.
@node Listing global options
@subsection Listing global options
Some legacy applications look at the global configuration file for the
gpgconf tool itself; this is the file @file{gpgconf.conf}. Modern
applications should not use it but use per component global
configuration files which are more flexible than the
@file{gpgconf.conf}. Using both files is not suggested.
The colon separated listing format is record oriented and uses the first
field to identify the record type:
@table @code
@item k
This describes a key record to start the definition of a new ruleset for
a user/group. The format of a key record is:
@code{k:@var{user}:@var{group}:}
@table @var
@item user
This is the user field of the key. It is percent escaped. See the
definition of the gpgconf.conf format for details.
@item group
This is the group field of the key. It is percent escaped.
@end table
@item r
This describes a rule record. All rule records up to the next key record
make up a rule set for that key. The format of a rule record is:
@code{r:::@var{component}:@var{option}:@var{flag}:@var{value}:}
@table @var
@item component
This is the component part of a rule. It is a plain string.
@item option
This is the option part of a rule. It is a plain string.
@item flag
This is the flags part of a rule. There may be only one flag per rule
but by using the same component and option, several flags may be
assigned to an option. It is a plain string.
@item value
This is the optional value for the option. It is a percent escaped
string with a single quotation mark to indicate a string. The quotation
mark is only required to distinguish between no value specified and an
empty string.
@end table
@end table
@noindent
Unknown record types should be ignored. Note that there is intentionally
no feature to change the global option file through @command{gpgconf}.
@node Querying versions
@subsection Get and compare software versions.
The GnuPG Project operates a server to query the current versions of
software packages related to GnuPG. @command{gpgconf} can be used to
access this online database. To allow for offline operations, this
feature works by having @command{dirmngr} download a file from
@code{https://versions.gnupg.org}, checking the signature of that file
and storing the file in the GnuPG home directory. If
@command{gpgconf} is used and @command{dirmngr} is running, it may ask
@command{dirmngr} to refresh that file before itself uses the file.
The command @option{--query-swdb} returns information for the given
package in a colon delimited format:
@table @var
@item name
This is the name of the package as requested. Note that "gnupg" is a
special name which is replaced by the actual package implementing this
version of GnuPG. For this name it is also not required to specify a
version because @command{gpgconf} takes its own version in this case.
@item iversion
The currently installed version or an empty string. The value is
taken from the command line argument but may be provided by gpg
if not given.
@item status
The status of the software package according to this table:
@table @code
@item -
No information available. This is either because no current version
has been specified or due to an error.
@item ?
The given name is not known in the online database.
@item u
An update of the software is available.
@item c
The installed version of the software is current.
@item n
The installed version is already newer than the released version.
@end table
@item urgency
If the value (the empty string should be considered as zero) is
greater than zero an important update is available.
@item error
This returns an @command{gpg-error} error code to distinguish between
various failure modes.
@item filedate
This gives the date of the file with the version numbers in standard
ISO format (@code{yyyymmddThhmmss}). The date has been extracted by
@command{dirmngr} from the signature of the file.
@item verified
This gives the date in ISO format the file was downloaded. This value
can be used to evaluate the freshness of the information.
@item version
This returns the version string for the requested software from the
file.
@item reldate
This returns the release date in ISO format.
@item size
This returns the size of the package as decimal number of bytes.
@item hash
This returns a hexified SHA-2 hash of the package.
@end table
@noindent
More fields may be added in future to the output.
@mansect files
@node Files used by gpgconf
@subsection Files used by gpgconf
@table @file
@item gpgconf.ctl
@cindex gpgconf.ctl
Under Unix @file{gpgconf.ctl} may be used to change some of the
compiled in directories where the GnuPG components are expected. This
file is expected in the same directory as @file{gpgconf}. The
physical installation directories are evaluated and no symlinks.
Blank lines and lines starting with pound sign are ignored in the
file. The keywords must be followed by optional white space, an equal
sign, optional white space, and the value. Environment variables are
substituted in standard shell manner, the final value must start with
a slash, trailing slashes are stripped. Valid keywords are
@code{rootdir}, @code{sysconfdir}, @code{socketdir}, and
@code{.enable}. No errors are printed for unknown keywords. The
@code{.enable} keyword is special: if the keyword is used and its
value evaluates to true the entire file is ignored.
Under Windows this file is used to install GnuPG as a portable
application. An empty file named @file{gpgconf.ctl} is expected in
the same directory as the tool @file{gpgconf.exe} or the file must
have a keyword @code{portable} with the value true. The root of the
installation is then that directory; or, if @file{gpgconf.exe} has
been installed directly below a directory named @file{bin}, its parent
directory. You also need to make sure that the following directories
exist and are writable: @file{ROOT/home} for the GnuPG home and
@file{ROOT@value{LOCALCACHEDIR}} for internal cache files.
On both platforms the keyword @code{gnupg} can be used to change the
standard home directory. For example a value of "gnupg-vsd" will
change the default home directory on unix from @file{~/.gnupg} to
@file{~/.gnupg-vsd}. The socket directory is changed accordingly
unless the @code{socketdir} keyword has been used. On Windows the
Registry keys are modified as well.
@item /etc/gnupg/gpgconf.conf
@cindex gpgconf.conf
If this file exists, it is processed as a global configuration file.
This is a legacy mechanism which should not be used together with
the modern global per component configuration files. A commented
example can be found in the @file{examples} directory of the
distribution.
@item @var{GNUPGHOME}/swdb.lst
@cindex swdb.lst
A file with current software versions. @command{dirmngr} creates
this file on demand from an online resource.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1),
@command{dirmngr}(1)
@end ifset
@include see-also-note.texi
@c
@c APPLYGNUPGDEFAULTS
@c
@manpage applygnupgdefaults.8
@node applygnupgdefaults
@section Run gpgconf for all users
@ifset manverb
.B applygnupgdefaults
\- Run gpgconf --apply-defaults for all users.
@end ifset
@mansect synopsis
@ifset manverb
.B applygnupgdefaults
@end ifset
@mansect description
This is a legacy script. Modern application should use the per
component global configuration files under @file{/etc/gnupg/}.
This script is a wrapper around @command{gpgconf} to run it with the
command @code{--apply-defaults} for all real users with an existing
GnuPG home directory. Admins might want to use this script to update he
GnuPG configuration files for all users after
@file{/etc/gnupg/gpgconf.conf} has been changed. This allows enforcing
certain policies for all users. Note, that this is not a bulletproof way to
force a user to use certain options. A user may always directly edit
the configuration files and bypass gpgconf.
@noindent
@command{applygnupgdefaults} is invoked by root as:
@example
applygnupgdefaults
@end example
@c
@c GPG-PRESET-PASSPHRASE
@c
@node gpg-preset-passphrase
@section Put a passphrase into the cache
@manpage gpg-preset-passphrase.1
@ifset manverb
.B gpg-preset-passphrase
\- Put a passphrase into gpg-agent's cache
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-preset-passphrase
.RI [ options ]
.RI [ command ]
.I cache-id
@end ifset
@mansect description
The @command{gpg-preset-passphrase} is a utility to seed the internal
cache of a running @command{gpg-agent} with passphrases. It is mainly
useful for unattended machines, where the usual @command{pinentry} tool
may not be used and the passphrases for the to be used keys are given at
machine startup.
This program works with GnuPG 2 and later. GnuPG 1.x is not supported.
Passphrases set with this utility don't expire unless the
@option{--forget} option is used to explicitly clear them from the
cache --- or @command{gpg-agent} is either restarted or reloaded (by
sending a SIGHUP to it). Note that the maximum cache time as set with
@option{--max-cache-ttl} is still honored. It is necessary to allow
this passphrase presetting by starting @command{gpg-agent} with the
@option{--allow-preset-passphrase}.
@menu
* Invoking gpg-preset-passphrase:: List of all commands and options.
@end menu
@manpause
@node Invoking gpg-preset-passphrase
@subsection List of all commands and options
@mancont
@noindent
@command{gpg-preset-passphrase} is invoked this way:
@example
gpg-preset-passphrase [options] [command] @var{cacheid}
@end example
@var{cacheid} is either a 40 character keygrip of hexadecimal
characters identifying the key for which the passphrase should be set
or cleared. The keygrip is listed along with the key when running the
command: @code{gpgsm --with-keygrip --list-secret-keys}.
Alternatively an arbitrary string may be used to identify a
passphrase; it is suggested that such a string is prefixed with the
name of the application (e.g @code{foo:12346}). Scripts should always
use the option @option{--with-colons}, which provides the keygrip in a
"grp" line (cf.@: @file{doc/DETAILS})/
@noindent
One of the following command options must be given:
@table @gnupgtabopt
@item --preset
@opindex preset
Preset a passphrase. This is what you usually will
use. @command{gpg-preset-passphrase} will then read the passphrase from
@code{stdin}.
@item --forget
@opindex forget
Flush the passphrase for the given cache ID from the cache.
@end table
@noindent
The following additional options may be used:
@table @gnupgtabopt
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -P @var{string}
@itemx --passphrase @var{string}
@opindex passphrase
Instead of reading the passphrase from @code{stdin}, use the supplied
@var{string} as passphrase. Note that this makes the passphrase visible
for other users.
@end table
@mansect see also
@ifset isman
@command{gpg}(1),
@command{gpgsm}(1),
@command{gpg-agent}(1),
@command{scdaemon}(1)
@end ifset
@include see-also-note.texi
@c
@c GPG-CONNECT-AGENT
@c
@node gpg-connect-agent
@section Communicate with a running agent
@manpage gpg-connect-agent.1
@ifset manverb
.B gpg-connect-agent
\- Communicate with a running agent
@end ifset
@mansect synopsis
@ifset manverb
.B gpg-connect-agent
.RI [ options ] [commands]
@end ifset
@mansect description
The @command{gpg-connect-agent} is a utility to communicate with a
running @command{gpg-agent}. It is useful to check out the commands
@command{gpg-agent} provides using the Assuan interface. It might
also be useful for scripting simple applications. Input is expected
at stdin and output gets printed to stdout.
It is very similar to running @command{gpg-agent} in server mode; but
here we connect to a running instance.
@menu
* Invoking gpg-connect-agent:: List of all options.
* Controlling gpg-connect-agent:: Control commands.
@end menu
@manpause
@node Invoking gpg-connect-agent
@subsection List of all options
@noindent
@command{gpg-connect-agent} is invoked this way:
@example
gpg-connect-agent [options] [commands]
@end example
@mancont
@noindent
The following options may be used:
@table @gnupgtabopt
@item --dirmngr
@opindex dirmngr
Connect to a running directory manager (keyserver client) instead of
to the gpg-agent. If a dirmngr is not running, start it.
@item --keyboxd
@opindex keyboxd
Connect to a running keybox daemon instead of
to the gpg-agent. If a keyboxd is not running, start it.
@item -S
@itemx --raw-socket @var{name}
@opindex raw-socket
Connect to socket @var{name} assuming this is an Assuan style server.
Do not run any special initializations or environment checks. This may
be used to directly connect to any Assuan style socket server.
@item -E
@itemx --exec
@opindex exec
Take the rest of the command line as a program and it's arguments and
execute it as an Assuan server. Here is how you would run @command{gpgsm}:
@smallexample
gpg-connect-agent --exec gpgsm --server
@end smallexample
Note that you may not use options on the command line in this case.
@item -v
@itemx --verbose
@opindex verbose
Output additional information while running.
@item -q
@item --quiet
@opindex q
@opindex quiet
Try to be as quiet as possible.
@include opt-homedir.texi
@item --chuid @var{uid}
@opindex chuid
Change the current user to @var{uid} which may either be a number or a
name. This can be used from the root account to run gpg-connect-agent
for another user. If @var{uid} is not the current UID a standard PATH
is set and the envvar GNUPGHOME is unset. To override the latter the
option @option{--homedir} can be used. This option has only an effect
when used on the command line. This option has currently no effect at
all on Windows.
@item --no-ext-connect
@opindex no-ext-connect
When using @option{-S} or @option{--exec}, @command{gpg-connect-agent}
connects to the Assuan server in extended mode to allow descriptor
passing. This option makes it use the old mode.
@item --no-autostart
@opindex no-autostart
Do not start the gpg-agent or the dirmngr if it has not yet been
started.
@item --no-history
@opindex --no-history
In interactive mode the command line history is usually saved and
restored to and from a file below the GnuPG home directory. This
option inhibits the use of that file.
@item --agent-program @var{file}
@opindex agent-program
Specify the agent program to be started if none is running. The
default value is determined by running @command{gpgconf} with the
option @option{--list-dirs}. Note that the pipe symbol (@code{|}) is
used for a regression test suite hack and may thus not be used in the
file name.
@item --dirmngr-program @var{file}
@opindex dirmngr-program
Specify the directory manager (keyserver client) program to be started
if none is running. This has only an effect if used together with the
option @option{--dirmngr}.
@item --keyboxd-program @var{file}
@opindex keyboxd-program
Specify the keybox daemon program to be started if none is running.
This has only an effect if used together with the option
@option{--keyboxd}.
@item -r @var{file}
@itemx --run @var{file}
@opindex run
Run the commands from @var{file} at startup and then continue with the
regular input method. Note, that commands given on the command line are
executed after this file.
@item -s
@itemx --subst
@opindex subst
Run the command @code{/subst} at startup.
@item --hex
@opindex hex
Print data lines in a hex format and the ASCII representation of
non-control characters.
@item --decode
@opindex decode
Decode data lines. That is to remove percent escapes but make sure that
a new line always starts with a D and a space.
@item -u
@itemx --unbuffered
@opindex unbuffered
Set stdin and stdout into unbuffered I/O mode. This this sometimes
useful for scripting.
@end table
@mansect control commands
@node Controlling gpg-connect-agent
@subsection Control commands
While reading Assuan commands, gpg-agent also allows a few special
commands to control its operation. These control commands all start
with a slash (@code{/}).
@table @code
@item /echo @var{args}
Just print @var{args}.
@item /let @var{name} @var{value}
Set the variable @var{name} to @var{value}. Variables are only
substituted on the input if the @command{/subst} has been used.
Variables are referenced by prefixing the name with a dollar sign and
optionally include the name in curly braces. The rules for a valid name
are identically to those of the standard bourne shell. This is not yet
enforced but may be in the future. When used with curly braces no
leading or trailing white space is allowed.
If a variable is not found, it is searched in the environment and if
found copied to the table of variables.
Variable functions are available: The name of the function must be
followed by at least one space and the at least one argument. The
following functions are available:
@table @code
@item get
Return a value described by the argument. Available arguments are:
@table @code
@item cwd
The current working directory.
@item homedir
The gnupg homedir.
@item sysconfdir
GnuPG's system configuration directory.
@item bindir
GnuPG's binary directory.
@item libdir
GnuPG's library directory.
@item libexecdir
GnuPG's library directory for executable files.
@item datadir
GnuPG's data directory.
@item serverpid
The PID of the current server. Command @command{/serverpid} must
have been given to return a useful value.
@end table
@item unescape @var{args}
Remove C-style escapes from @var{args}. Note that @code{\0} and
@code{\x00} terminate the returned string implicitly. The string to be
converted are the entire arguments right behind the delimiting space of
the function name.
@item unpercent @var{args}
@itemx unpercent+ @var{args}
Remove percent style escaping from @var{args}. Note that @code{%00}
terminates the string implicitly. The string to be converted are the
entire arguments right behind the delimiting space of the function
name. @code{unpercent+} also maps plus signs to a spaces.
@item percent @var{args}
@itemx percent+ @var{args}
Escape the @var{args} using percent style escaping. Tabs, formfeeds,
linefeeds, carriage returns and colons are escaped. @code{percent+} also
maps spaces to plus signs.
@item errcode @var{arg}
@itemx errsource @var{arg}
@itemx errstring @var{arg}
Assume @var{arg} is an integer and evaluate it using @code{strtol}. Return
the gpg-error error code, error source or a formatted string with the
error code and error source.
@item +
@itemx -
@itemx *
@itemx /
@itemx %
Evaluate all arguments as long integers using @code{strtol} and apply
this operator. A division by zero yields an empty string.
@item !
@itemx |
@itemx &
Evaluate all arguments as long integers using @code{strtol} and apply
the logical operators NOT, OR or AND. The NOT operator works on the
last argument only.
@end table
@item /definq @var{name} @var{var}
Use content of the variable @var{var} for inquiries with @var{name}.
@var{name} may be an asterisk (@code{*}) to match any inquiry.
@item /definqfile @var{name} @var{file}
Use content of @var{file} for inquiries with @var{name}.
@var{name} may be an asterisk (@code{*}) to match any inquiry.
@item /definqprog @var{name} @var{prog}
Run @var{prog} for inquiries matching @var{name} and pass the
entire line to it as command line arguments.
@item /datafile @var{name}
Write all data lines from the server to the file @var{name}. The file
is opened for writing and created if it does not exists. An existing
file is first truncated to 0. The data written to the file fully
decoded. Using a single dash for @var{name} writes to stdout. The
file is kept open until a new file is set using this command or this
command is used without an argument.
@item /showdef
Print all definitions
@item /cleardef
Delete all definitions
@item /sendfd @var{file} @var{mode}
Open @var{file} in @var{mode} (which needs to be a valid @code{fopen}
mode string) and send the file descriptor to the server. This is
usually followed by a command like @code{INPUT FD} to set the
input source for other commands.
@item /recvfd
Not yet implemented.
@item /open @var{var} @var{file} [@var{mode}]
Open @var{file} and assign the file descriptor to @var{var}. Warning:
This command is experimental and might change in future versions.
@item /close @var{fd}
Close the file descriptor @var{fd}. Warning: This command is
experimental and might change in future versions.
@item /showopen
Show a list of open files.
@item /serverpid
Send the Assuan command @command{GETINFO pid} to the server and store
the returned PID for internal purposes.
@item /sleep
Sleep for a second.
@item /hex
@itemx /nohex
Same as the command line option @option{--hex}.
@item /decode
@itemx /nodecode
Same as the command line option @option{--decode}.
@item /subst
@itemx /nosubst
Enable and disable variable substitution. It defaults to disabled
unless the command line option @option{--subst} has been used.
If /subst as been enabled once, leading whitespace is removed from
input lines which makes scripts easier to read.
@item /while @var{condition}
@itemx /end
These commands provide a way for executing loops. All lines between
the @code{while} and the corresponding @code{end} are executed as long
as the evaluation of @var{condition} yields a non-zero value or is the
string @code{true} or @code{yes}. The evaluation is done by passing
@var{condition} to the @code{strtol} function. Example:
@smallexample
/subst
/let i 3
/while $i
/echo loop counter is $i
/let i $@{- $i 1@}
/end
@end smallexample
@item /if @var{condition}
@itemx /end
These commands provide a way for conditional execution. All lines between
the @code{if} and the corresponding @code{end} are executed only if
the evaluation of @var{condition} yields a non-zero value or is the
string @code{true} or @code{yes}. The evaluation is done by passing
@var{condition} to the @code{strtol} function.
@item /run @var{file}
Run commands from @var{file}.
@item /history --clear
Clear the command history.
@item /bye
Terminate the connection and the program.
@item /help
Print a list of available control commands.
@end table
@ifset isman
@mansect see also
@command{gpg-agent}(1),
@command{scdaemon}(1)
@include see-also-note.texi
@end ifset
@c
@c DIRMNGR-CLIENT
@c
@node dirmngr-client
@section The Dirmngr Client Tool
@manpage dirmngr-client.1
@ifset manverb
.B dirmngr-client
\- Tool to access the Dirmngr services
@end ifset
@mansect synopsis
@ifset manverb
.B dirmngr-client
.RI [ options ]
.RI [ certfile | pattern ]
@end ifset
@mansect description
The @command{dirmngr-client} is a simple tool to contact a running
dirmngr and test whether a certificate has been revoked --- either by
being listed in the corresponding CRL or by running the OCSP protocol.
If no dirmngr is running, a new instances will be started but this is
in general not a good idea due to the huge performance overhead.
@noindent
The usual way to run this tool is either:
@example
dirmngr-client @var{acert}
@end example
@noindent
or
@example
dirmngr-client <@var{acert}
@end example
Where @var{acert} is one DER encoded (binary) X.509 certificates to be
tested.
@ifclear isman
The return value of this command is
@end ifclear
@mansect return value
@ifset isman
@command{dirmngr-client} returns these values:
@end ifset
@table @code
@item 0
The certificate under question is valid; i.e., there is a valid CRL
available and it is not listed there or the OCSP request returned that
that certificate is valid.
@item 1
The certificate has been revoked
@item 2 (and other values)
There was a problem checking the revocation state of the certificate.
A message to stderr has given more detailed information. Most likely
this is due to a missing or expired CRL or due to a network problem.
@end table
@mansect options
@noindent
@command{dirmngr-client} may be called with the following options:
@table @gnupgtabopt
@item --version
@opindex version
Print the program version and licensing information. Note that you cannot
abbreviate this command.
@item --help, -h
@opindex help
Print a usage message summarizing the most useful command-line options.
Note that you cannot abbreviate this command.
@item --quiet, -q
@opindex quiet
Make the output extra brief by suppressing any informational messages.
@item -v
@item --verbose
@opindex v
@opindex verbose
Outputs additional information while running.
You can increase the verbosity by giving several
verbose commands to @sc{dirmngr}, such as @samp{-vv}.
@item --pem
@opindex pem
Assume that the given certificate is in PEM (armored) format.
@item --ocsp
@opindex ocsp
Do the check using the OCSP protocol and ignore any CRLs.
@item --force-default-responder
@opindex force-default-responder
When checking using the OCSP protocol, force the use of the default OCSP
-responder. That is not to use the Reponder as given by the certificate.
+responder. That is not to use the Responder as given by the certificate.
@item --ping
@opindex ping
Check whether the dirmngr daemon is up and running.
@item --cache-cert
@opindex cache-cert
Put the given certificate into the cache of a running dirmngr. This is
mainly useful for debugging.
@item --validate
@opindex validate
Validate the given certificate using dirmngr's internal validation code.
This is mainly useful for debugging.
@item --load-crl
@opindex load-crl
This command expects a list of filenames with DER encoded CRL files.
With the option @option{--url} URLs are expected in place of filenames
and they are loaded directly from the given location. All CRLs will be
validated and then loaded into dirmngr's cache.
@item --lookup
@opindex lookup
Take the remaining arguments and run a lookup command on each of them.
The results are Base-64 encoded outputs (without header lines). This
may be used to retrieve certificates from a server. However the output
format is not very well suited if more than one certificate is returned.
@item --url
@itemx -u
@opindex url
Modify the @command{lookup} and @command{load-crl} commands to take an URL.
@item --local
@itemx -l
@opindex url
Let the @command{lookup} command only search the local cache.
@item --squid-mode
@opindex squid-mode
Run @sc{dirmngr-client} in a mode suitable as a helper program for
Squid's @option{external_acl_type} option.
@end table
@ifset isman
@mansect see also
@command{dirmngr}(8),
@command{gpgsm}(1)
@include see-also-note.texi
@end ifset
@c
@c GPGPARSEMAIL
@c
@node gpgparsemail
@section Parse a mail message into an annotated format
@manpage gpgparsemail.1
@ifset manverb
.B gpgparsemail
\- Parse a mail message into an annotated format
@end ifset
@mansect synopsis
@ifset manverb
.B gpgparsemail
.RI [ options ]
.RI [ file ]
@end ifset
@mansect description
The @command{gpgparsemail} is a utility currently only useful for
debugging. Run it with @code{--help} for usage information.
@c
@c GPGTAR
@c
@manpage gpgtar.1
@node gpgtar
@section Encrypt or sign files into an archive
@ifset manverb
.B gpgtar
\- Encrypt or sign files into an archive
@end ifset
@mansect synopsis
@ifset manverb
.B gpgtar
.RI [ options ]
.I filename1
.I [ filename2, ... ]
.I directory1
.I [ directory2, ... ]
@end ifset
@mansect description
@command{gpgtar} encrypts or signs files into an archive. It is an
gpg-ized tar using the same format as used by PGP's PGP Zip.
@manpause
@noindent
@command{gpgtar} is invoked this way:
@example
gpgtar [options] @var{filename1} [@var{filename2}, ...] @var{directory} [@var{directory2}, ...]
@end example
@mansect options
@noindent
@command{gpgtar} understands these options:
@table @gnupgtabopt
@item --create
@opindex create
Put given files and directories into a vanilla ``ustar'' archive.
@item --extract
@opindex extract
Extract all files from a vanilla ``ustar'' archive.
If no file name is given (or it is "-") the archive is taken from
stdin.
@item --encrypt
@itemx -e
@opindex encrypt
Encrypt given files and directories into an archive. This option may
be combined with option @option{--symmetric} for an archive that may
be decrypted via a secret key or a passphrase.
@item --decrypt
@itemx -d
@opindex decrypt
Extract all files from an encrypted archive. If no file name is given
(or it is "-") the archive is taken from stdin.
@item --sign
@itemx -s
Make a signed archive from the given files and directories. This can
be combined with option @option{--encrypt} to create a signed and then
encrypted archive.
@item --list-archive
@itemx -t
@opindex list-archive
List the contents of the specified archive. If no file name is given
(or it is "-") the archive is taken from stdin.
@item --symmetric
@itemx -c
Encrypt with a symmetric cipher using a passphrase. The default
symmetric cipher used is @value{GPGSYMENCALGO}, but may be chosen with the
@option{--cipher-algo} option to @command{gpg}.
@item --recipient @var{user}
@itemx -r @var{user}
@opindex recipient
Encrypt for user id @var{user}. For details see @command{gpg}.
@item --local-user @var{user}
@itemx -u @var{user}
@opindex local-user
Use @var{user} as the key to sign with. For details see @command{gpg}.
@item --output @var{file}
@itemx -o @var{file}
@opindex output
Write the archive to the specified file @var{file}.
@item --verbose
@itemx -v
@opindex verbose
Enable extra informational output.
@item --quiet
@itemx -q
@opindex quiet
Try to be as quiet as possible.
@item --skip-crypto
@opindex skip-crypto
Skip all crypto operations and create or extract vanilla ``ustar''
archives.
@item --dry-run
@opindex dry-run
Do not actually output the extracted files.
@item --directory @var{dir}
@itemx -C @var{dir}
@opindex directory
Extract the files into the directory @var{dir}. The default is to
take the directory name from the input filename. If no input filename
is known a directory named @file{GPGARCH} is used. For tarball
creation, switch to directory @var{dir} before performing any
operations.
@item --files-from @var{file}
@itemx -T @var{file}
Take the file names to work from the file @var{file}; one file per
line.
@item --null
@opindex null
Modify option @option{--files-from} to use a binary nul instead of a
linefeed to separate file names.
@item --utf8-strings
@opindex utf8-strings
Assume that the file names read by @option{--files-from} are UTF-8
encoded. This option has an effect only on Windows where the active
code page is otherwise assumed.
@item --openpgp
@opindex openpgp
This option has no effect because OpenPGP encryption and signing is
the default.
@item --cms
@opindex cms
This option is reserved and shall not be used. It will eventually be
used to encrypt or sign using the CMS protocol; but that is not yet
implemented.
@item --batch
@opindex batch
Use batch mode. Never ask but use the default action. This option is
passed directly to @command{gpg}.
@item --yes
@opindex yes
Assume "yes" on most questions. Often used together with
@option{--batch} to overwrite existing files. This option is passed
directly to @command{gpg}.
@item --no
@opindex no
Assume "no" on most questions. This option is passed directly to
@command{gpg}.
@item --require-compliance
@opindex require-compliance
This option is passed directly to @command{gpg}.
@item --status-fd @var{n}
@opindex status-fd
Write special status strings to the file descriptor @var{n}.
See the file DETAILS in the documentation for a listing of them.
@item --with-log
@opindex with-log
When extracting an encrypted tarball also write a log file with the
gpg output to a file named after the extraction directory with the
suffix ".log".
@item --set-filename @var{file}
@opindex set-filename
Use the last component of @var{file} as the output directory. The
default is to take the directory name from the input filename. If no
input filename is known a directory named @file{GPGARCH} is used.
This option is deprecated in favor of option @option{--directory}.
@item --no-compress
@opindex no-compress
This option tells gpg to disable compression (i.e., using option -z0).
It is useful for archiving only large files which are are already
compressed (e.g., a set of videos).
@item --gpg @var{gpgcmd}
@opindex gpg
Use the specified command @var{gpgcmd} instead of @command{gpg}.
@item --gpg-args @var{args}
@opindex gpg-args
Pass the specified extra options to @command{gpg}.
@item --tar-args @var{args}
@opindex tar-args
Assume @var{args} are standard options of the command @command{tar}
and parse them. The only supported tar options are
@option{--directory}, @option{--files-from}, and @option{--null}.
This is an obsolete options because those supported tar options can
also be given directly.
@item --tar @var{command}
@opindex tar
This is a dummy option for backward compatibility.
@c ... to the gpg-zip script we provided in the past
@item --version
@opindex version
Print version of the program and exit.
@item --help
@opindex help
Display a brief help page and exit.
@end table
@mansect diagnostics
@noindent
The program returns 0 if everything was fine, 1 otherwise.
@mansect examples
@ifclear isman
@noindent
Some examples:
@end ifclear
@noindent
Encrypt the contents of directory @file{mydocs} for user Bob to file
@file{test1}:
@example
gpgtar --encrypt --output test1 -r Bob mydocs
@end example
@noindent
List the contents of archive @file{test1}:
@example
gpgtar --list-archive test1
@end example
@mansect see also
@ifset isman
@command{gpg}(1),
@command{tar}(1),
@end ifset
@include see-also-note.texi
@c
@c GPG-CHECK-PATTERN
@c
@manpage gpg-check-pattern.1
@node gpg-check-pattern
@section Check a passphrase on stdin against the patternfile
@ifset manverb
.B gpg-check-pattern
\- Check a passphrase on stdin against the patternfile
@end ifset
@mansect synopsis
@ifset manverb
.B gpg\-check\-pattern
.RI [ options ]
.I patternfile
@end ifset
@mansect description
@command{gpg-check-pattern} checks a passphrase given on stdin against
a specified pattern file.
The pattern file is line based with comment lines beginning on the
@emph{first} position with a @code{#}. Empty lines and lines with
only white spaces are ignored. The actual pattern lines may either be
verbatim string pattern and match as they are (trailing spaces are
ignored) or extended regular expressions indicated by a @code{/} or
@code{!/} in the first column and terminated by another @code{/} or
end of line. If a regular expression starts with @code{!/} the match
result is reversed. By default all comparisons are case insensitive.
Tag lines may be used to further control the operation of this tool.
The currently defined tags are:
@table @code
@item [icase]
Switch to case insensitive comparison for all further patterns. This
is the default.
@item [case]
Switch to case sensitive comparison for all further patterns.
@item [reject]
Switch to reject mode. This is the default mode.
@item [accept]
Switch to accept mode.
@end table
In the future more tags may be introduced and thus it is advisable not to
start a plain pattern string with an open bracket. The tags must be
given verbatim on the line with no spaces to the left or any non white
space characters to the right.
In reject mode the program exits on the first match with an exit code
of 1 (failure). If at the end of the pattern list the reject mode is
still active the program exits with code 0 (success).
In accept mode blocks of patterns are used. A block starts at the
next pattern after an "accept" tag and ends with the last pattern
before the next "accept" or "reject" tag or at the end of the pattern
list. If all patterns in a block match the program exits with an exit
code of 0 (success). If any pattern in a block do not match the next
pattern block is evaluated. If at the end of the pattern list the
accept mode is still active the program exits with code 1 (failure).
@mansect options
@noindent
@table @gnupgtabopt
@item --verbose
@opindex verbose
Enable extra informational output.
@item --check
@opindex check
Run only a syntax check on the patternfile.
@item --null
@opindex null
Input is expected to be null delimited.
@end table
@mansect see also
@ifset isman
@command{gpg-agent}(1),
@end ifset
@include see-also-note.texi
diff --git a/g10/armor.c b/g10/armor.c
index 81af15339..ed5ef5d2e 100644
--- a/g10/armor.c
+++ b/g10/armor.c
@@ -1,1604 +1,1604 @@
/* armor.c - Armor filter
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include "gpg.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "../common/util.h"
#include "filter.h"
#include "packet.h"
#include "options.h"
#include "main.h"
#include "../common/i18n.h"
#define MAX_LINELEN 20000
static const byte bintoasc[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
"abcdefghijklmnopqrstuvwxyz"
"0123456789+/";
static u32 asctobin[4][256]; /* runtime initialized */
static int is_initialized;
typedef enum {
fhdrHASArmor = 0,
fhdrNOArmor,
fhdrINIT,
fhdrINITCont,
fhdrINITSkip,
fhdrCHECKBegin,
fhdrWAITHeader,
fhdrWAITClearsig,
fhdrSKIPHeader,
fhdrCLEARSIG,
fhdrREADClearsig,
fhdrNullClearsig,
fhdrEMPTYClearsig,
fhdrCHECKClearsig,
fhdrCHECKClearsig2,
fhdrCHECKDashEscaped,
fhdrCHECKDashEscaped2,
fhdrCHECKDashEscaped3,
fhdrREADClearsigNext,
fhdrENDClearsig,
fhdrENDClearsigHelp,
fhdrTESTSpaces,
fhdrCLEARSIGSimple,
fhdrCLEARSIGSimpleNext,
fhdrTEXT,
fhdrTEXTSimple,
fhdrERROR,
fhdrERRORShow,
fhdrEOF
} fhdr_state_t;
/* if we encounter this armor string with this index, go
* into a mode which fakes packets and wait for the next armor */
#define BEGIN_SIGNATURE 2
#define BEGIN_SIGNED_MSG_IDX 3
static char *head_strings[] = {
"BEGIN PGP MESSAGE",
"BEGIN PGP PUBLIC KEY BLOCK",
"BEGIN PGP SIGNATURE",
"BEGIN PGP SIGNED MESSAGE",
"BEGIN PGP ARMORED FILE", /* gnupg extension */
"BEGIN PGP PRIVATE KEY BLOCK",
"BEGIN PGP SECRET KEY BLOCK", /* only used by pgp2 */
NULL
};
static char *tail_strings[] = {
"END PGP MESSAGE",
"END PGP PUBLIC KEY BLOCK",
"END PGP SIGNATURE",
"END dummy",
"END PGP ARMORED FILE",
"END PGP PRIVATE KEY BLOCK",
"END PGP SECRET KEY BLOCK",
NULL
};
static int armor_filter ( void *opaque, int control,
iobuf_t chain, byte *buf, size_t *ret_len);
/* Create a new context for armor filters. */
armor_filter_context_t *
new_armor_context (void)
{
armor_filter_context_t *afx;
gpg_error_t err;
afx = xcalloc (1, sizeof *afx);
if (afx)
{
err = gcry_md_open (&afx->crc_md, GCRY_MD_CRC24_RFC2440, 0);
if (err != 0)
{
log_error ("gcry_md_open failed for GCRY_MD_CRC24_RFC2440: %s",
gpg_strerror (err));
xfree (afx);
return NULL;
}
afx->refcount = 1;
}
return afx;
}
/* Release an armor filter context. Passing NULL is explicitly
allowed and a no-op. */
void
release_armor_context (armor_filter_context_t *afx)
{
if (!afx)
return;
log_assert (afx->refcount);
if ( --afx->refcount )
return;
gcry_md_close (afx->crc_md);
xfree (afx);
}
/* Push the armor filter onto the iobuf stream IOBUF. */
int
push_armor_filter (armor_filter_context_t *afx, iobuf_t iobuf)
{
int rc;
afx->refcount++;
rc = iobuf_push_filter (iobuf, armor_filter, afx);
if (rc)
afx->refcount--;
return rc;
}
static void
initialize(void)
{
u32 i;
const byte *s;
/* Build the helptable for radix64 to bin conversion. Value 0xffffffff is
used to detect invalid characters. */
memset (asctobin, 0xff, sizeof(asctobin));
for(s=bintoasc,i=0; *s; s++,i++ )
{
asctobin[0][*s] = i << (0 * 6);
asctobin[1][*s] = i << (1 * 6);
asctobin[2][*s] = i << (2 * 6);
asctobin[3][*s] = i << (3 * 6);
}
is_initialized=1;
}
static inline u32
get_afx_crc (armor_filter_context_t *afx)
{
const byte *crc_buf;
u32 crc;
crc_buf = gcry_md_read (afx->crc_md, GCRY_MD_CRC24_RFC2440);
crc = crc_buf[0];
crc <<= 8;
crc |= crc_buf[1];
crc <<= 8;
crc |= crc_buf[2];
return crc;
}
/*
* Check whether this is an armored file. See also
* parse-packet.c for details on this code.
*
* Note that the buffer BUF needs to be at least 2 bytes long. If in
* doubt that the second byte to 0.
*
* Returns: True if it seems to be armored
*/
static int
is_armored (const byte *buf)
{
int ctb, pkttype;
int indeterminate_length_allowed;
ctb = *buf;
if( !(ctb & 0x80) )
/* The most significant bit of the CTB must be set. Since it is
cleared, this is not a binary OpenPGP message. Assume it is
armored. */
return 1;
pkttype = ctb & 0x40 ? (ctb & 0x3f) : ((ctb>>2)&0xf);
switch( pkttype ) {
case PKT_PUBKEY_ENC:
case PKT_SIGNATURE:
case PKT_SYMKEY_ENC:
case PKT_ONEPASS_SIG:
case PKT_SECRET_KEY:
case PKT_PUBLIC_KEY:
case PKT_SECRET_SUBKEY:
case PKT_MARKER:
case PKT_RING_TRUST:
case PKT_USER_ID:
case PKT_PUBLIC_SUBKEY:
case PKT_ATTRIBUTE:
case PKT_MDC:
indeterminate_length_allowed = 0;
break;
case PKT_COMPRESSED:
case PKT_ENCRYPTED:
case PKT_ENCRYPTED_MDC:
case PKT_ENCRYPTED_AEAD:
case PKT_PLAINTEXT:
case PKT_OLD_COMMENT:
case PKT_COMMENT:
case PKT_GPG_CONTROL:
indeterminate_length_allowed = 1;
break;
default:
/* Invalid packet type. */
return 1;
}
if (! indeterminate_length_allowed)
/* It is only legal to use an indeterminate length with a few
packet types. If a packet uses an indeterminate length, but
that is not allowed, then the data is not valid binary
OpenPGP data. */
{
int new_format;
int indeterminate_length;
new_format = !! (ctb & (1 << 6));
if (new_format)
indeterminate_length = (buf[1] >= 224 && buf[1] < 255);
else
indeterminate_length = (ctb & 3) == 3;
if (indeterminate_length)
return 1;
}
/* The first CTB seems legit. It is probably not armored
data. */
return 0;
}
/****************
* Try to check whether the iobuf is armored
* Returns true if this may be the case; the caller should use the
* filter to do further processing.
*/
int
use_armor_filter( IOBUF a )
{
byte buf[2];
int n;
/* fixme: there might be a problem with iobuf_peek */
n = iobuf_peek (a, buf, 2);
if( n == -1 )
return 0; /* EOF, doesn't matter whether armored or not */
if( !n )
return 1; /* can't check it: try armored */
if (n != 2)
return 0; /* short buffer */
return is_armored(buf);
}
static void
invalid_armor(void)
{
write_status(STATUS_BADARMOR);
g10_exit(1); /* stop here */
}
/****************
* check whether the armor header is valid on a signed message.
* this is for security reasons: the header lines are not included in the
* hash and by using some creative formatting rules, Mallory could fake
* any text at the beginning of a document; assuming it is read with
* a simple viewer. We only allow the Hash Header.
*/
static int
parse_hash_header( const char *line )
{
const char *s, *s2;
unsigned found = 0;
if( strlen(line) < 6 || strlen(line) > 60 )
return 0; /* too short or too long */
if( memcmp( line, "Hash:", 5 ) )
return 0; /* invalid header */
for(s=line+5;;s=s2) {
for(; *s && (*s==' ' || *s == '\t'); s++ )
;
if( !*s )
break;
for(s2=s+1; *s2 && *s2!=' ' && *s2 != '\t' && *s2 != ','; s2++ )
;
if( !strncmp( s, "RIPEMD160", s2-s ) )
found |= 1;
else if( !strncmp( s, "SHA1", s2-s ) )
found |= 2;
else if( !strncmp( s, "SHA224", s2-s ) )
found |= 8;
else if( !strncmp( s, "SHA256", s2-s ) )
found |= 16;
else if( !strncmp( s, "SHA384", s2-s ) )
found |= 32;
else if( !strncmp( s, "SHA512", s2-s ) )
found |= 64;
else
return 0;
for(; *s2 && (*s2==' ' || *s2 == '\t'); s2++ )
;
if( *s2 && *s2 != ',' )
return 0;
if( *s2 )
s2++;
}
return found;
}
/* Returns true if this is a valid armor tag as per RFC-2440bis-21. */
static int
is_armor_tag(const char *line)
{
if(strncmp(line,"Version",7)==0
|| strncmp(line,"Comment",7)==0
|| strncmp(line,"MessageID",9)==0
|| strncmp(line,"Hash",4)==0
|| strncmp(line,"Charset",7)==0)
return 1;
return 0;
}
/****************
* Check whether this is a armor line. Returns: -1 if it is not a
* armor header, 42 if it is a generic header, or the index number of
* the armor header.
*/
static int
is_armor_header( byte *line, unsigned len )
{
const char *s;
byte *save_p, *p;
int save_c;
int i;
if( len < 15 )
return -1; /* too short */
if( memcmp( line, "-----", 5 ) )
return -1; /* no */
p = strstr( line+5, "-----");
if( !p )
return -1;
save_p = p;
p += 5;
/* Some Windows environments seem to add whitespace to the end of
the line, so we strip it here. This becomes strict if
--rfc2440 is set since 2440 reads "The header lines, therefore,
MUST start at the beginning of a line, and MUST NOT have text
following them on the same line." It is unclear whether "text"
refers to all text or just non-whitespace text. 4880 clarified
this was only non-whitespace text. */
if(RFC2440)
{
if( *p == '\r' )
p++;
if( *p == '\n' )
p++;
}
else
while(*p==' ' || *p=='\r' || *p=='\n' || *p=='\t')
p++;
if( *p )
return -1; /* garbage after dashes */
save_c = *save_p; *save_p = 0;
p = line+5;
for(i=0; (s=head_strings[i]); i++ )
if( !strcmp(s, p) )
break;
*save_p = save_c;
if (!s)
{
if (!strncmp (p, "BEGIN ", 6))
return 42;
return -1; /* unknown armor line */
}
if( opt.verbose > 1 )
log_info(_("armor: %s\n"), head_strings[i]);
return i;
}
/****************
* Parse a header lines
* Return 0: Empty line (end of header lines)
* -1: invalid header line
* >0: Good header line
*/
static int
parse_header_line( armor_filter_context_t *afx, byte *line, unsigned int len )
{
byte *p;
int hashes=0;
unsigned int len2;
len2 = length_sans_trailing_ws ( line, len );
if( !len2 ) {
afx->buffer_pos = len2; /* (it is not the fine way to do it here) */
return 0; /* WS only: same as empty line */
}
/*
This is fussy. The spec says that a header line is delimited
with a colon-space pair. This means that a line such as
"Comment: " (with nothing else) is actually legal as an empty
string comment. However, email and cut-and-paste being what it
is, that trailing space may go away. Therefore, we accept empty
headers delimited with only a colon. --rfc2440, as always,
makes this strict and enforces the colon-space pair. -dms
*/
p = strchr( line, ':');
if (!p && afx->dearmor_state)
return 0; /* Special treatment in --dearmor mode. */
if( !p || (RFC2440 && p[1]!=' ')
|| (!RFC2440 && p[1]!=' ' && p[1]!='\n' && p[1]!='\r'))
{
log_error (_("invalid armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
return -1;
}
/* Chop off the whitespace we detected before */
len=len2;
line[len2]='\0';
if( opt.verbose ) {
log_info(_("armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
}
if (afx->dearmor_mode)
;
else if (afx->in_cleartext)
{
if( (hashes=parse_hash_header( line )) )
afx->hashes |= hashes;
else if( strlen(line) > 15 && !memcmp( line, "NotDashEscaped:", 15 ) )
afx->not_dash_escaped = 1;
else
{
log_error(_("invalid clearsig header\n"));
return -1;
}
}
else if(!is_armor_tag(line))
{
/* Section 6.2: "Unknown keys should be reported to the user,
but OpenPGP should continue to process the message." Note
that in a clearsigned message this applies to the signature
part (i.e. "BEGIN PGP SIGNATURE") and not the signed data
("BEGIN PGP SIGNED MESSAGE"). The only key allowed in the
signed data section is "Hash". */
log_info(_("unknown armor header: "));
es_write_sanitized (log_get_stream (), line, len, NULL, NULL);
log_printf ("\n");
}
return 1;
}
/* figure out whether the data is armored or not */
static int
check_input( armor_filter_context_t *afx, IOBUF a )
{
int rc = 0;
int i;
byte *line;
unsigned len;
unsigned maxlen;
int hdr_line = -1;
/* read the first line to see whether this is armored data */
maxlen = MAX_LINELEN;
len = afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
if( !maxlen ) {
/* line has been truncated: assume not armored */
afx->inp_checked = 1;
afx->inp_bypass = 1;
return 0;
}
if( !len ) {
return -1; /* eof */
}
/* (the line is always a C string but maybe longer) */
if( *line == '\n' || ( len && (*line == '\r' && line[1]=='\n') ) )
;
else if (len >= 2 && !is_armored (line)) {
afx->inp_checked = 1;
afx->inp_bypass = 1;
return 0;
}
/* find the armor header */
while(len) {
i = is_armor_header( line, len );
if ( i == 42 ) {
if (afx->dearmor_mode) {
afx->dearmor_state = 1;
break;
}
}
else if (i >= 0
&& !(afx->only_keyblocks && i != 1 && i != 5 && i != 6 )) {
hdr_line = i;
if( hdr_line == BEGIN_SIGNED_MSG_IDX ) {
if( afx->in_cleartext ) {
log_error(_("nested clear text signatures\n"));
rc = gpg_error (GPG_ERR_INV_ARMOR);
}
afx->in_cleartext = 1;
}
break;
}
/* read the next line (skip all truncated lines) */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
len = afx->buffer_len;
} while( !maxlen );
}
/* Parse the header lines. */
while(len) {
/* Read the next line (skip all truncated lines). */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
line = afx->buffer;
len = afx->buffer_len;
} while( !maxlen );
i = parse_header_line( afx, line, len );
if( i <= 0 ) {
if (i && RFC2440)
rc = GPG_ERR_INV_ARMOR;
break;
}
}
if( rc )
invalid_armor();
else if( afx->in_cleartext )
afx->faked = 1;
else {
afx->inp_checked = 1;
gcry_md_reset (afx->crc_md);
afx->idx = 0;
afx->radbuf[0] = 0;
}
return rc;
}
#define PARTIAL_CHUNK 512
#define PARTIAL_POW 9
/****************
* Fake a literal data packet and wait for the next armor line
* fixme: empty line handling and null length clear text signature are
* not implemented/checked.
*/
static int
fake_packet( armor_filter_context_t *afx, IOBUF a,
size_t *retn, byte *buf, size_t size )
{
int rc = 0;
size_t len = 0;
int lastline = 0;
unsigned maxlen, n;
byte *p;
byte tempbuf[PARTIAL_CHUNK];
size_t tempbuf_len=0;
int this_truncated;
while( !rc && size-len>=(PARTIAL_CHUNK+1)) {
/* copy what we have in the line buffer */
if( afx->faked == 1 )
afx->faked++; /* skip the first (empty) line */
else
{
/* It's full, so write this partial chunk */
if(tempbuf_len==PARTIAL_CHUNK)
{
buf[len++]=0xE0+PARTIAL_POW;
memcpy(&buf[len],tempbuf,PARTIAL_CHUNK);
len+=PARTIAL_CHUNK;
tempbuf_len=0;
continue;
}
while( tempbuf_len < PARTIAL_CHUNK
&& afx->buffer_pos < afx->buffer_len )
tempbuf[tempbuf_len++] = afx->buffer[afx->buffer_pos++];
if( tempbuf_len==PARTIAL_CHUNK )
continue;
}
/* read the next line */
maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !afx->buffer_len ) {
rc = -1; /* eof (should not happen) */
continue;
}
if( !maxlen )
{
afx->truncated++;
this_truncated = 1;
}
else
this_truncated = 0;
p = afx->buffer;
n = afx->buffer_len;
/* Armor header or dash-escaped line? */
if(p[0]=='-')
{
/* 2440bis-10: When reversing dash-escaping, an
implementation MUST strip the string "- " if it occurs
at the beginning of a line, and SHOULD warn on "-" and
any character other than a space at the beginning of a
line. */
if(p[1]==' ' && !afx->not_dash_escaped)
{
/* It's a dash-escaped line, so skip over the
escape. */
afx->buffer_pos = 2;
}
else if(p[1]=='-' && p[2]=='-' && p[3]=='-' && p[4]=='-')
{
/* Five dashes in a row mean it's probably armor
header. */
int type = is_armor_header( p, n );
if (type == 42)
type = -1; /* Only OpenPGP armors are expected. */
if( afx->not_dash_escaped && type != BEGIN_SIGNATURE )
; /* this is okay */
else
{
if( type != BEGIN_SIGNATURE )
{
log_info(_("unexpected armor: "));
es_write_sanitized (log_get_stream (), p, n,
NULL, NULL);
log_printf ("\n");
}
lastline = 1;
rc = -1;
}
}
else if(!afx->not_dash_escaped)
{
/* Bad dash-escaping. */
log_info (_("invalid dash escaped line: "));
es_write_sanitized (log_get_stream (), p, n, NULL, NULL);
log_printf ("\n");
}
}
/* Now handle the end-of-line canonicalization */
if( !afx->not_dash_escaped || this_truncated)
{
int crlf = n > 1 && p[n-2] == '\r' && p[n-1]=='\n';
afx->buffer_len=
trim_trailing_chars( &p[afx->buffer_pos], n-afx->buffer_pos,
" \t\r\n");
afx->buffer_len+=afx->buffer_pos;
/* the buffer is always allocated with enough space to append
* the removed [CR], LF and a Nul
* The reason for this complicated procedure is to keep at least
* the original type of lineending - handling of the removed
* trailing spaces seems to be impossible in our method
* of faking a packet; either we have to use a temporary file
* or calculate the hash here in this module and somehow find
* a way to send the hash down the processing line (well, a special
* faked packet could do the job).
*
* To make sure that a truncated line triggers a bad
* signature error we replace a removed LF by a FF or
* append a FF. Right, this is a hack but better than a
* global variable and way easier than to introduce a new
* control packet or insert a line like "[truncated]\n"
* into the filter output.
*/
if( crlf )
afx->buffer[afx->buffer_len++] = '\r';
afx->buffer[afx->buffer_len++] = this_truncated? '\f':'\n';
afx->buffer[afx->buffer_len] = '\0';
}
}
if( lastline ) { /* write last (ending) length header */
if(tempbuf_len<192)
buf[len++]=tempbuf_len;
else
{
buf[len++]=((tempbuf_len-192)/256) + 192;
buf[len++]=(tempbuf_len-192) % 256;
}
memcpy(&buf[len],tempbuf,tempbuf_len);
len+=tempbuf_len;
rc = 0;
afx->faked = 0;
afx->in_cleartext = 0;
/* and now read the header lines */
afx->buffer_pos = 0;
for(;;) {
int i;
/* read the next line (skip all truncated lines) */
do {
maxlen = MAX_LINELEN;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
} while( !maxlen );
p = afx->buffer;
n = afx->buffer_len;
if( !n ) {
rc = -1;
break; /* eof */
}
i = parse_header_line( afx, p , n );
if( i <= 0 ) {
if( i )
invalid_armor();
break;
}
}
afx->inp_checked = 1;
gcry_md_reset (afx->crc_md);
afx->idx = 0;
afx->radbuf[0] = 0;
}
*retn = len;
return rc;
}
static int
invalid_crc(void)
{
if ( opt.ignore_crc_error )
return 0;
log_inc_errorcount();
return gpg_error (GPG_ERR_INV_ARMOR);
}
static int
radix64_read( armor_filter_context_t *afx, IOBUF a, size_t *retn,
byte *buf, size_t size )
{
byte val;
int c;
u32 binc;
int checkcrc=0;
int rc = 0;
size_t n = 0;
int idx, onlypad=0;
int skip_fast = 0;
idx = afx->idx;
val = afx->radbuf[0];
for( n=0; n < size; ) {
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
again:
binc = asctobin[0][c];
if( binc != 0xffffffffUL )
{
if( idx == 0 && skip_fast == 0
&& afx->buffer_pos + (16 - 1) < afx->buffer_len
&& n + 12 < size)
{
/* Fast path for radix64 to binary conversion. */
u32 b0,b1,b2,b3;
/* Speculatively load 15 more input bytes. */
b0 = binc << (3 * 6);
b0 |= asctobin[2][afx->buffer[afx->buffer_pos + 0]];
b0 |= asctobin[1][afx->buffer[afx->buffer_pos + 1]];
b0 |= asctobin[0][afx->buffer[afx->buffer_pos + 2]];
b1 = asctobin[3][afx->buffer[afx->buffer_pos + 3]];
b1 |= asctobin[2][afx->buffer[afx->buffer_pos + 4]];
b1 |= asctobin[1][afx->buffer[afx->buffer_pos + 5]];
b1 |= asctobin[0][afx->buffer[afx->buffer_pos + 6]];
b2 = asctobin[3][afx->buffer[afx->buffer_pos + 7]];
b2 |= asctobin[2][afx->buffer[afx->buffer_pos + 8]];
b2 |= asctobin[1][afx->buffer[afx->buffer_pos + 9]];
b2 |= asctobin[0][afx->buffer[afx->buffer_pos + 10]];
b3 = asctobin[3][afx->buffer[afx->buffer_pos + 11]];
b3 |= asctobin[2][afx->buffer[afx->buffer_pos + 12]];
b3 |= asctobin[1][afx->buffer[afx->buffer_pos + 13]];
b3 |= asctobin[0][afx->buffer[afx->buffer_pos + 14]];
/* Check if any of the input bytes were invalid. */
if( (b0 | b1 | b2 | b3) != 0xffffffffUL )
{
/* All 16 bytes are valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
buf[n + 3] = b1 >> (2 * 8);
buf[n + 4] = b1 >> (1 * 8);
buf[n + 5] = b1 >> (0 * 8);
buf[n + 6] = b2 >> (2 * 8);
buf[n + 7] = b2 >> (1 * 8);
buf[n + 8] = b2 >> (0 * 8);
buf[n + 9] = b3 >> (2 * 8);
buf[n + 10] = b3 >> (1 * 8);
buf[n + 11] = b3 >> (0 * 8);
afx->buffer_pos += 16 - 1;
n += 12;
continue;
}
else if( b0 == 0xffffffffUL )
{
/* byte[1..3] have invalid character(s). Switch to slow
path. */
skip_fast = 1;
}
else if( b1 == 0xffffffffUL )
{
/* byte[4..7] have invalid character(s), first 4 bytes are
valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
afx->buffer_pos += 4 - 1;
n += 3;
skip_fast = 1;
continue;
}
else if( b2 == 0xffffffffUL )
{
/* byte[8..11] have invalid character(s), first 8 bytes are
valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
buf[n + 3] = b1 >> (2 * 8);
buf[n + 4] = b1 >> (1 * 8);
buf[n + 5] = b1 >> (0 * 8);
afx->buffer_pos += 8 - 1;
n += 6;
skip_fast = 1;
continue;
}
else /*if( b3 == 0xffffffffUL )*/
{
/* byte[12..15] have invalid character(s), first 12 bytes
are valid. */
buf[n + 0] = b0 >> (2 * 8);
buf[n + 1] = b0 >> (1 * 8);
buf[n + 2] = b0 >> (0 * 8);
buf[n + 3] = b1 >> (2 * 8);
buf[n + 4] = b1 >> (1 * 8);
buf[n + 5] = b1 >> (0 * 8);
buf[n + 6] = b2 >> (2 * 8);
buf[n + 7] = b2 >> (1 * 8);
buf[n + 8] = b2 >> (0 * 8);
afx->buffer_pos += 12 - 1;
n += 9;
skip_fast = 1;
continue;
}
}
switch(idx)
{
case 0: val = binc << 2; break;
case 1: val |= (binc>>4)&3; buf[n++]=val;val=(binc<<4)&0xf0;break;
case 2: val |= (binc>>2)&15; buf[n++]=val;val=(binc<<6)&0xc0;break;
case 3: val |= binc&0x3f; buf[n++] = val; break;
}
idx = (idx+1) % 4;
continue;
}
skip_fast = 0;
if( c == '\n' || c == ' ' || c == '\r' || c == '\t' )
continue;
else if( c == '=' ) { /* pad character: stop */
/* some mailers leave quoted-printable encoded characters
* so we try to workaround this */
if( afx->buffer_pos+2 < afx->buffer_len ) {
int cc1, cc2, cc3;
cc1 = afx->buffer[afx->buffer_pos];
cc2 = afx->buffer[afx->buffer_pos+1];
cc3 = afx->buffer[afx->buffer_pos+2];
if( isxdigit(cc1) && isxdigit(cc2)
&& strchr( "=\n\r\t ", cc3 )) {
/* well it seems to be the case - adjust */
c = isdigit(cc1)? (cc1 - '0'): (ascii_toupper(cc1)-'A'+10);
c <<= 4;
c |= isdigit(cc2)? (cc2 - '0'): (ascii_toupper(cc2)-'A'+10);
afx->buffer_pos += 2;
afx->qp_detected = 1;
goto again;
}
}
/* Occasionally a bug MTA will leave the = escaped as
=3D. If the 4 characters following that are valid
Radix64 characters and they are following by a new
line, assume that this is the case and skip the
3D. */
if (afx->buffer_pos + 6 < afx->buffer_len
&& afx->buffer[afx->buffer_pos + 0] == '3'
&& afx->buffer[afx->buffer_pos + 1] == 'D'
&& asctobin[0][afx->buffer[afx->buffer_pos + 2]] != 0xffffffffUL
&& asctobin[0][afx->buffer[afx->buffer_pos + 3]] != 0xffffffffUL
&& asctobin[0][afx->buffer[afx->buffer_pos + 4]] != 0xffffffffUL
&& asctobin[0][afx->buffer[afx->buffer_pos + 5]] != 0xffffffffUL
&& afx->buffer[afx->buffer_pos + 6] == '\n')
{
afx->buffer_pos += 2;
afx->qp_detected = 1;
}
if (!n)
onlypad = 1;
if( idx == 1 )
buf[n++] = val;
checkcrc++;
break;
}
else if (c == '-'
&& afx->buffer_pos + 8 < afx->buffer_len
&& !strncmp (afx->buffer, "-----END ", 8)) {
break; /* End in --dearmor mode or No CRC. */
}
else {
log_error(_("invalid radix64 character %02X skipped\n"), c);
continue;
}
}
afx->idx = idx;
afx->radbuf[0] = val;
if( n )
gcry_md_write (afx->crc_md, buf, n);
if( checkcrc ) {
gcry_md_final (afx->crc_md);
afx->any_data = 1;
afx->inp_checked=0;
afx->faked = 0;
for(;;) { /* skip lf and pad characters */
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size, &maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
if( c == '\n' || c == ' ' || c == '\r'
|| c == '\t' || c == '=' )
continue;
break;
}
if( !afx->buffer_len )
log_error(_("premature eof (no CRC)\n"));
else {
u32 mycrc = 0;
idx = 0;
do {
if( (binc = asctobin[0][c]) == 0xffffffffUL )
break;
switch(idx) {
case 0: val = binc << 2; break;
case 1: val |= (binc>>4)&3; mycrc |= val << 16;val=(binc<<4)&0xf0;break;
case 2: val |= (binc>>2)&15; mycrc |= val << 8;val=(binc<<6)&0xc0;break;
case 3: val |= binc&0x3f; mycrc |= val; break;
}
for(;;) {
if( afx->buffer_pos < afx->buffer_len )
c = afx->buffer[afx->buffer_pos++];
else { /* read the next line */
unsigned maxlen = MAX_LINELEN;
afx->buffer_pos = 0;
afx->buffer_len = iobuf_read_line( a, &afx->buffer,
&afx->buffer_size,
&maxlen );
if( !maxlen )
afx->truncated++;
if( !afx->buffer_len )
break; /* eof */
continue;
}
break;
}
if( !afx->buffer_len )
break; /* eof */
} while( ++idx < 4 );
if( !afx->buffer_len ) {
log_info(_("premature eof (in CRC)\n"));
rc = invalid_crc();
}
else if( idx == 0 ) {
/* No CRC at all is legal ("MAY") */
rc=0;
}
else if( idx != 4 ) {
log_info(_("malformed CRC\n"));
rc = invalid_crc();
}
else if( mycrc != get_afx_crc (afx) ) {
log_info (_("CRC error; %06lX - %06lX\n"),
(ulong)get_afx_crc (afx), (ulong)mycrc);
rc = invalid_crc();
}
else {
rc = 0;
/* FIXME: Here we should emit another control packet,
* so that we know in mainproc that we are processing
* a clearsign message */
#if 0
for(rc=0;!rc;) {
rc = 0 /*check_trailer( &fhdr, c )*/;
if( !rc ) {
if( (c=iobuf_get(a)) == -1 )
rc = 2;
}
}
if( rc == -1 )
rc = 0;
else if( rc == 2 ) {
log_error(_("premature eof (in trailer)\n"));
rc = GPG_ERR_INV_ARMOR;
}
else {
log_error(_("error in trailer line\n"));
rc = GPG_ERR_INV_ARMOR;
}
#endif
}
}
}
if( !n && !onlypad )
rc = -1;
*retn = n;
return rc;
}
static void
armor_output_buf_as_radix64 (armor_filter_context_t *afx, IOBUF a,
byte *buf, size_t size)
{
byte radbuf[sizeof (afx->radbuf)];
byte outbuf[64 + sizeof (afx->eol)];
unsigned int eollen = strlen (afx->eol);
u32 in, in2;
int idx, idx2;
int i;
idx = afx->idx;
idx2 = afx->idx2;
memcpy (radbuf, afx->radbuf, sizeof (afx->radbuf));
if (size && (idx || idx2))
{
/* preload eol to outbuf buffer */
memcpy (outbuf + 4, afx->eol, sizeof (afx->eol));
for (; size && (idx || idx2); buf++, size--)
{
radbuf[idx++] = *buf;
if (idx > 2)
{
idx = 0;
in = (u32)radbuf[0] << (2 * 8);
in |= (u32)radbuf[1] << (1 * 8);
in |= (u32)radbuf[2] << (0 * 8);
outbuf[0] = bintoasc[(in >> 18) & 077];
outbuf[1] = bintoasc[(in >> 12) & 077];
outbuf[2] = bintoasc[(in >> 6) & 077];
outbuf[3] = bintoasc[(in >> 0) & 077];
if (++idx2 >= (64/4))
{ /* pgp doesn't like 72 here */
idx2=0;
iobuf_write (a, outbuf, 4 + eollen);
}
else
{
iobuf_write (a, outbuf, 4);
}
}
}
}
if (size >= (64/4)*3)
{
/* preload eol to outbuf buffer */
memcpy (outbuf + 64, afx->eol, sizeof(afx->eol));
do
{
/* idx and idx2 == 0 */
for (i = 0; i < (64/8); i++)
{
in = (u32)buf[0] << (2 * 8);
in |= (u32)buf[1] << (1 * 8);
in |= (u32)buf[2] << (0 * 8);
in2 = (u32)buf[3] << (2 * 8);
in2 |= (u32)buf[4] << (1 * 8);
in2 |= (u32)buf[5] << (0 * 8);
outbuf[i*8+0] = bintoasc[(in >> 18) & 077];
outbuf[i*8+1] = bintoasc[(in >> 12) & 077];
outbuf[i*8+2] = bintoasc[(in >> 6) & 077];
outbuf[i*8+3] = bintoasc[(in >> 0) & 077];
outbuf[i*8+4] = bintoasc[(in2 >> 18) & 077];
outbuf[i*8+5] = bintoasc[(in2 >> 12) & 077];
outbuf[i*8+6] = bintoasc[(in2 >> 6) & 077];
outbuf[i*8+7] = bintoasc[(in2 >> 0) & 077];
buf+=6;
size-=6;
}
/* pgp doesn't like 72 here */
iobuf_write (a, outbuf, 64 + eollen);
}
while (size >= (64/4)*3);
/* restore eol for tail handling */
if (size)
memcpy (outbuf + 4, afx->eol, sizeof (afx->eol));
}
for (; size; buf++, size--)
{
radbuf[idx++] = *buf;
if (idx > 2)
{
idx = 0;
in = (u32)radbuf[0] << (2 * 8);
in |= (u32)radbuf[1] << (1 * 8);
in |= (u32)radbuf[2] << (0 * 8);
outbuf[0] = bintoasc[(in >> 18) & 077];
outbuf[1] = bintoasc[(in >> 12) & 077];
outbuf[2] = bintoasc[(in >> 6) & 077];
outbuf[3] = bintoasc[(in >> 0) & 077];
if (++idx2 >= (64/4))
{ /* pgp doesn't like 72 here */
idx2=0;
iobuf_write (a, outbuf, 4 + eollen);
}
else
{
iobuf_write (a, outbuf, 4);
}
}
}
memcpy (afx->radbuf, radbuf, sizeof (afx->radbuf));
afx->idx = idx;
afx->idx2 = idx2;
}
/****************
* This filter is used to handle the armor stuff
*/
static int
armor_filter( void *opaque, int control,
IOBUF a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
armor_filter_context_t *afx = opaque;
int rc=0, c;
byte radbuf[3];
int idx, idx2;
size_t n=0;
u32 crc;
#if 0
static FILE *fp ;
if( !fp ) {
fp = fopen("armor.out", "w");
log_assert(fp);
}
#endif
if( DBG_FILTER )
log_debug("armor-filter: control: %d\n", control );
if( control == IOBUFCTRL_UNDERFLOW && afx->inp_bypass ) {
n = 0;
if( afx->buffer_len ) {
/* Copy the data from AFX->BUFFER to BUF. */
for(; n < size && afx->buffer_pos < afx->buffer_len; n++ )
buf[n++] = afx->buffer[afx->buffer_pos++];
if( afx->buffer_pos >= afx->buffer_len )
afx->buffer_len = 0;
}
/* If there is still space in BUF, read directly into it. */
for(; n < size; n++ ) {
if( (c=iobuf_get(a)) == -1 )
break;
buf[n] = c & 0xff;
}
if( !n )
/* We didn't get any data. EOF. */
rc = -1;
*ret_len = n;
}
else if( control == IOBUFCTRL_UNDERFLOW ) {
- /* We need some space for the faked packet. The minmum
+ /* We need some space for the faked packet. The minimum
* required size is the PARTIAL_CHUNK size plus a byte for the
* length itself */
if( size < PARTIAL_CHUNK+1 )
BUG(); /* supplied buffer too short */
if( afx->faked )
rc = fake_packet( afx, a, &n, buf, size );
else if( !afx->inp_checked ) {
rc = check_input( afx, a );
if( afx->inp_bypass ) {
for(n=0; n < size && afx->buffer_pos < afx->buffer_len; )
buf[n++] = afx->buffer[afx->buffer_pos++];
if( afx->buffer_pos >= afx->buffer_len )
afx->buffer_len = 0;
if( !n )
rc = -1;
}
else if( afx->faked ) {
unsigned int hashes = afx->hashes;
const byte *sesmark;
size_t sesmarklen;
sesmark = get_session_marker( &sesmarklen );
if ( sesmarklen > 20 )
BUG();
/* the buffer is at least 15+n*15 bytes long, so it
* is easy to construct the packets */
hashes &= 1|2|8|16|32|64;
if( !hashes ) {
hashes |= 2; /* Default to SHA-1. */
}
n=0;
/* First a gpg control packet... */
buf[n++] = 0xff; /* new format, type 63, 1 length byte */
n++; /* see below */
memcpy(buf+n, sesmark, sesmarklen ); n+= sesmarklen;
buf[n++] = CTRLPKT_CLEARSIGN_START;
buf[n++] = afx->not_dash_escaped? 0:1; /* sigclass */
if( hashes & 1 )
buf[n++] = DIGEST_ALGO_RMD160;
if( hashes & 2 )
buf[n++] = DIGEST_ALGO_SHA1;
if( hashes & 8 )
buf[n++] = DIGEST_ALGO_SHA224;
if( hashes & 16 )
buf[n++] = DIGEST_ALGO_SHA256;
if( hashes & 32 )
buf[n++] = DIGEST_ALGO_SHA384;
if( hashes & 64 )
buf[n++] = DIGEST_ALGO_SHA512;
buf[1] = n - 2;
/* ...followed by an invented plaintext packet.
Amusingly enough, this packet is not compliant with
2440 as the initial partial length is less than 512
bytes. Of course, we'll accept it anyway ;) */
buf[n++] = 0xCB; /* new packet format, type 11 */
buf[n++] = 0xE1; /* 2^1 == 2 bytes */
buf[n++] = 't'; /* canonical text mode */
buf[n++] = 0; /* namelength */
buf[n++] = 0xE2; /* 2^2 == 4 more bytes */
memset(buf+n, 0, 4); /* timestamp */
n += 4;
}
else if( !rc )
rc = radix64_read( afx, a, &n, buf, size );
}
else
rc = radix64_read( afx, a, &n, buf, size );
#if 0
if( n )
if( fwrite(buf, n, 1, fp ) != 1 )
BUG();
#endif
*ret_len = n;
}
else if( control == IOBUFCTRL_FLUSH && !afx->cancel ) {
if( !afx->status ) { /* write the header line */
const char *s;
strlist_t comment=opt.comments;
if( afx->what >= DIM(head_strings) )
log_bug("afx->what=%d", afx->what);
iobuf_writestr(a, "-----");
iobuf_writestr(a, head_strings[afx->what] );
iobuf_writestr(a, "-----" );
iobuf_writestr(a,afx->eol);
if (opt.emit_version)
{
iobuf_writestr (a, "Version: "GNUPG_NAME" v");
for (s=VERSION; *s && *s != '.'; s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 1 && *s)
{
iobuf_writebyte (a, *s++);
for (; *s && *s != '.'; s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 2)
{
for (; *s && *s != '-' && !spacep (s); s++)
iobuf_writebyte (a, *s);
if (opt.emit_version > 3)
iobuf_writestr (a, " (" PRINTABLE_OS_NAME ")");
}
}
iobuf_writestr(a,afx->eol);
}
/* write the comment strings */
for(;comment;comment=comment->next)
{
iobuf_writestr(a, "Comment: " );
for( s=comment->d; *s; s++ )
{
if( *s == '\n' )
iobuf_writestr(a, "\\n" );
else if( *s == '\r' )
iobuf_writestr(a, "\\r" );
else if( *s == '\v' )
iobuf_writestr(a, "\\v" );
else
iobuf_put(a, *s );
}
iobuf_writestr(a,afx->eol);
}
if ( afx->hdrlines ) {
for ( s = afx->hdrlines; *s; s++ ) {
#ifdef HAVE_DOSISH_SYSTEM
if ( *s == '\n' )
iobuf_put( a, '\r');
#endif
iobuf_put(a, *s );
}
}
iobuf_writestr(a,afx->eol);
afx->status++;
afx->idx = 0;
afx->idx2 = 0;
gcry_md_reset (afx->crc_md);
}
if( size ) {
gcry_md_write (afx->crc_md, buf, size);
armor_output_buf_as_radix64 (afx, a, buf, size);
}
}
else if( control == IOBUFCTRL_INIT )
{
if( !is_initialized )
initialize();
/* Figure out what we're using for line endings if the caller
didn't specify. */
if(afx->eol[0]==0)
{
#ifdef HAVE_DOSISH_SYSTEM
afx->eol[0]='\r';
afx->eol[1]='\n';
#else
afx->eol[0]='\n';
#endif
}
}
else if( control == IOBUFCTRL_CANCEL ) {
afx->cancel = 1;
}
else if( control == IOBUFCTRL_FREE ) {
if( afx->cancel )
;
- else if( afx->status ) { /* pad, write cecksum, and bottom line */
+ else if( afx->status ) { /* pad, write checksum, and bottom line */
gcry_md_final (afx->crc_md);
crc = get_afx_crc (afx);
idx = afx->idx;
idx2 = afx->idx2;
if( idx ) {
c = bintoasc[(afx->radbuf[0]>>2)&077];
iobuf_put(a, c);
if( idx == 1 ) {
c = bintoasc[((afx->radbuf[0] << 4) & 060) & 077];
iobuf_put(a, c);
iobuf_put(a, '=');
iobuf_put(a, '=');
}
else { /* 2 */
c = bintoasc[(((afx->radbuf[0]<<4)&060)
|((afx->radbuf[1]>>4)&017))&077];
iobuf_put(a, c);
c = bintoasc[((afx->radbuf[1] << 2) & 074) & 077];
iobuf_put(a, c);
iobuf_put(a, '=');
}
if( ++idx2 >= (64/4) )
{ /* pgp doesn't like 72 here */
iobuf_writestr(a,afx->eol);
idx2=0;
}
}
/* may need a linefeed */
if( idx2 )
iobuf_writestr(a,afx->eol);
/* write the CRC */
iobuf_put(a, '=');
radbuf[0] = crc >>16;
radbuf[1] = crc >> 8;
radbuf[2] = crc;
c = bintoasc[(*radbuf >> 2) & 077];
iobuf_put(a, c);
c = bintoasc[(((*radbuf<<4)&060)|((radbuf[1] >> 4)&017))&077];
iobuf_put(a, c);
c = bintoasc[(((radbuf[1]<<2)&074)|((radbuf[2]>>6)&03))&077];
iobuf_put(a, c);
c = bintoasc[radbuf[2]&077];
iobuf_put(a, c);
iobuf_writestr(a,afx->eol);
/* and the trailer */
if( afx->what >= DIM(tail_strings) )
log_bug("afx->what=%d", afx->what);
iobuf_writestr(a, "-----");
iobuf_writestr(a, tail_strings[afx->what] );
iobuf_writestr(a, "-----" );
iobuf_writestr(a,afx->eol);
}
else if( !afx->any_data && !afx->inp_bypass ) {
log_error(_("no valid OpenPGP data found.\n"));
afx->no_openpgp_data = 1;
write_status_text( STATUS_NODATA, "1" );
}
/* Note that in a cleartext signature truncated lines in the
* plaintext are detected and propagated to the signature
* checking code by inserting a \f into the plaintext. We do
* not use log_info here because some of the truncated lines
* are harmless. */
if( afx->truncated )
log_info(_("invalid armor: line longer than %d characters\n"),
MAX_LINELEN );
/* issue an error to enforce dissemination of correct software */
if( afx->qp_detected )
log_error(_("quoted printable character in armor - "
"probably a buggy MTA has been used\n") );
xfree( afx->buffer );
afx->buffer = NULL;
release_armor_context (afx);
}
else if( control == IOBUFCTRL_DESC )
mem2str (buf, "armor_filter", *ret_len);
return rc;
}
/****************
* create a radix64 encoded string.
*/
char *
make_radix64_string( const byte *data, size_t len )
{
char *buffer, *p;
buffer = p = xmalloc( (len+2)/3*4 + 1 );
for( ; len >= 3 ; len -= 3, data += 3 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
*p++ = bintoasc[(((data[1]<<2)&074)|((data[2]>>6)&03))&077];
*p++ = bintoasc[data[2]&077];
}
if( len == 2 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(((data[0] <<4)&060)|((data[1] >> 4)&017))&077];
*p++ = bintoasc[((data[1]<<2)&074)];
}
else if( len == 1 ) {
*p++ = bintoasc[(data[0] >> 2) & 077];
*p++ = bintoasc[(data[0] <<4)&060];
}
*p = 0;
return buffer;
}
diff --git a/g10/call-agent.c b/g10/call-agent.c
index cfd4c086a..3a4d35b07 100644
--- a/g10/call-agent.c
+++ b/g10/call-agent.c
@@ -1,3404 +1,3404 @@
/* call-agent.c - Divert GPG operations to the agent.
* Copyright (C) 2001-2003, 2006-2011, 2013 Free Software Foundation, Inc.
* Copyright (C) 2013-2015 Werner Koch
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/sysutils.h"
#include "call-agent.h"
#include "../common/status.h"
#include "../common/shareddefs.h"
#include "../common/host2net.h"
#include "../common/ttyio.h"
#define CONTROL_D ('D' - 'A' + 1)
static assuan_context_t agent_ctx = NULL;
static int did_early_card_test;
struct confirm_parm_s
{
char *desc;
char *ok;
char *notok;
};
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
struct {
u32 *keyid;
u32 *mainkeyid;
int pubkey_algo;
} keyinfo;
struct confirm_parm_s *confirm;
};
struct cipher_parm_s
{
struct default_inq_parm_s *dflt;
assuan_context_t ctx;
unsigned char *ciphertext;
size_t ciphertextlen;
};
struct writecert_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *certdata;
size_t certdatalen;
};
struct writekey_parm_s
{
struct default_inq_parm_s *dflt;
const unsigned char *keydata;
size_t keydatalen;
};
struct genkey_parm_s
{
struct default_inq_parm_s *dflt;
const char *keyparms;
const char *passphrase;
};
struct import_key_parm_s
{
struct default_inq_parm_s *dflt;
const void *key;
size_t keylen;
};
struct cache_nonce_parm_s
{
char **cache_nonce_addr;
char **passwd_nonce_addr;
};
static gpg_error_t learn_status_cb (void *opaque, const char *line);
/* If RC is not 0, write an appropriate status message. */
static void
status_sc_op_failure (int rc)
{
switch (gpg_err_code (rc))
{
case 0:
break;
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
case GPG_ERR_BAD_RESET_CODE:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
}
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
const char *s;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpg_proxy_pinentry_notify (parm->ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
assuan_begin_confidential (parm->ctx);
if (have_static_passphrase ())
{
s = get_static_passphrase ();
err = assuan_send_data (parm->ctx, s, strlen (s));
}
else
{
char *pw;
char buf[32];
if (parm->keyinfo.keyid)
emit_status_need_passphrase (parm->ctrl,
parm->keyinfo.keyid,
parm->keyinfo.mainkeyid,
parm->keyinfo.pubkey_algo);
snprintf (buf, sizeof (buf), "%u", 100);
write_status_text (STATUS_INQUIRE_MAXLEN, buf);
pw = cpr_get_hidden ("passphrase.enter", _("Enter passphrase: "));
cpr_kill_prompt ();
if (*pw == CONTROL_D && !pw[1])
err = gpg_error (GPG_ERR_CANCELED);
else
err = assuan_send_data (parm->ctx, pw, strlen (pw));
xfree (pw);
}
assuan_end_confidential (parm->ctx);
}
else if ((s = has_leading_keyword (line, "CONFIRM"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK
&& parm->confirm)
{
int ask = atoi (s);
int yes;
if (ask)
{
yes = cpr_get_answer_is_yes (NULL, parm->confirm->desc);
if (yes)
err = assuan_send_data (parm->ctx, NULL, 0);
else
err = gpg_error (GPG_ERR_NOT_CONFIRMED);
}
else
{
tty_printf ("%s", parm->confirm->desc);
err = assuan_send_data (parm->ctx, NULL, 0);
}
}
else
log_debug ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername, int mode)
{
return warn_server_version_mismatch (ctx, servername, mode,
write_status_strings2, NULL,
!opt.quiet);
}
#define FLAG_FOR_CARD_SUPPRESS_ERRORS 2
/* Try to connect to the agent via socket or fork it off and work by
pipes. Handle the server's initial greeting */
static int
start_agent (ctrl_t ctrl, int flag_for_card)
{
int rc;
(void)ctrl; /* Not yet used. */
/* Fixme: We need a context for each thread or serialize the access
to the agent. */
if (agent_ctx)
rc = 0;
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart?ASSHELP_FLAG_AUTOSTART:0,
opt.verbose, DBG_IPC,
NULL, NULL);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc
&& !(rc = warn_version_mismatch (agent_ctx, GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications.
No error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Tell the agent about what version we are aware. This is
here used to indirectly enable GPG_ERR_FULLY_CANCELED. */
assuan_transact (agent_ctx, "OPTION agent-awareness=2.1.0",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
write_status_error ("set_pinentry_mode", rc);
}
}
/* Pass on the request origin. */
if (opt.request_origin)
{
char *tmp = xasprintf ("OPTION pretend-request-origin=%s",
str_request_origin (opt.request_origin));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
{
log_error ("setting request origin '%s' failed: %s\n",
str_request_origin (opt.request_origin),
gpg_strerror (rc));
write_status_error ("set_request_origin", rc);
}
}
/* In DE_VS mode under Windows we require that the JENT RNG
* is active. */
#ifdef HAVE_W32_SYSTEM
if (!rc && opt.compliance == CO_DE_VS)
{
if (assuan_transact (agent_ctx, "GETINFO jent_active",
NULL, NULL, NULL, NULL, NULL, NULL))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
GPG_AGENT_NAME,
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", rc);
}
}
#endif /*HAVE_W32_SYSTEM*/
}
}
if (!rc && flag_for_card && !did_early_card_test)
{
/* Request the serial number of the card for an early test. */
struct agent_card_info_s info;
memset (&info, 0, sizeof info);
if (!(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
rc = warn_version_mismatch (agent_ctx, SCDAEMON_NAME, 2);
if (!rc)
rc = assuan_transact (agent_ctx,
opt.flags.use_only_openpgp_card?
"SCD SERIALNO openpgp" : "SCD SERIALNO",
NULL, NULL, NULL, NULL,
learn_status_cb, &info);
if (rc && !(flag_for_card & FLAG_FOR_CARD_SUPPRESS_ERRORS))
{
switch (gpg_err_code (rc))
{
case GPG_ERR_NOT_SUPPORTED:
case GPG_ERR_NO_SCDAEMON:
write_status_text (STATUS_CARDCTRL, "6");
break;
case GPG_ERR_OBJ_TERM_STATE:
write_status_text (STATUS_CARDCTRL, "7");
break;
default:
write_status_text (STATUS_CARDCTRL, "4");
log_info ("selecting card failed: %s\n", gpg_strerror (rc));
break;
}
}
if (!rc && is_status_enabled () && info.serialno)
{
char *buf;
buf = xasprintf ("3 %s", info.serialno);
write_status_text (STATUS_CARDCTRL, buf);
xfree (buf);
}
agent_release_card_info (&info);
if (!rc)
did_early_card_test = 1;
}
return rc;
}
/* Return a new malloced string by unescaping the string S. Escaping
is percent escaping and '+'/space mapping. A binary nul will
silently be replaced by a 0xFF. Function returns NULL to indicate
an out of memory status. */
static char *
unescape_status_string (const unsigned char *s)
{
return percent_plus_unescape (s, 0xff);
}
/* Take a 20 or 32 byte hexencoded string and put it into the provided
* FPRLEN byte long buffer FPR in binary format. Returns the actual
* used length of the FPR buffer or 0 on error. */
static unsigned int
unhexify_fpr (const char *hexstr, unsigned char *fpr, unsigned int fprlen)
{
const char *s;
int n;
for (s=hexstr, n=0; hexdigitp (s); s++, n++)
;
if ((*s && *s != ' ') || !(n == 40 || n == 64))
return 0; /* no fingerprint (invalid or wrong length). */
for (s=hexstr, n=0; *s && n < fprlen; s += 2, n++)
fpr[n] = xtoi_2 (s);
return (n == 20 || n == 32)? n : 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* This is a dummy data line callback. */
static gpg_error_t
dummy_data_cb (void *opaque, const void *buffer, size_t length)
{
(void)opaque;
(void)buffer;
(void)length;
return 0;
}
/* A simple callback used to return the serialnumber of a card. */
static gpg_error_t
get_serialno_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *keyword = line;
const char *s;
int keywordlen, n;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
if (*serialno)
return gpg_error (GPG_ERR_CONFLICT); /* Unexpected status line. */
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1)|| !(spacep (s) || !*s) )
return gpg_error (GPG_ERR_ASS_PARAMETER);
*serialno = xtrymalloc (n+1);
if (!*serialno)
return out_of_core ();
memcpy (*serialno, line, n);
(*serialno)[n] = 0;
}
return 0;
}
/* Release the card info structure INFO. */
void
agent_release_card_info (struct agent_card_info_s *info)
{
int i;
if (!info)
return;
xfree (info->reader); info->reader = NULL;
xfree (info->manufacturer_name); info->manufacturer_name = NULL;
xfree (info->serialno); info->serialno = NULL;
xfree (info->apptype); info->apptype = NULL;
xfree (info->disp_name); info->disp_name = NULL;
xfree (info->disp_lang); info->disp_lang = NULL;
xfree (info->pubkey_url); info->pubkey_url = NULL;
xfree (info->login_data); info->login_data = NULL;
info->cafpr1len = info->cafpr2len = info->cafpr3len = 0;
info->fpr1len = info->fpr2len = info->fpr3len = 0;
for (i=0; i < DIM(info->private_do); i++)
{
xfree (info->private_do[i]);
info->private_do[i] = NULL;
}
for (i=0; i < DIM(info->supported_keyalgo); i++)
{
free_strlist (info->supported_keyalgo[i]);
info->supported_keyalgo[i] = NULL;
}
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct agent_card_info_s *parm = opaque;
const char *keyword = line;
int keywordlen;
int i;
char *endp;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 6 && !memcmp (keyword, "READER", keywordlen))
{
xfree (parm->reader);
parm->reader = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (parm->serialno);
parm->serialno = store_serialno (line);
parm->is_v2 = (strlen (parm->serialno) >= 16
&& (xtoi_2 (parm->serialno+12) == 0 /* Yubikey */
|| xtoi_2 (parm->serialno+12) >= 2));
}
else if (keywordlen == 7 && !memcmp (keyword, "APPTYPE", keywordlen))
{
xfree (parm->apptype);
parm->apptype = unescape_status_string (line);
}
else if (keywordlen == 10 && !memcmp (keyword, "APPVERSION", keywordlen))
{
unsigned int val = 0;
sscanf (line, "%x", &val);
parm->appversion = val;
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-NAME", keywordlen))
{
xfree (parm->disp_name);
parm->disp_name = unescape_status_string (line);
}
else if (keywordlen == 9 && !memcmp (keyword, "DISP-LANG", keywordlen))
{
xfree (parm->disp_lang);
parm->disp_lang = unescape_status_string (line);
}
else if (keywordlen == 8 && !memcmp (keyword, "DISP-SEX", keywordlen))
{
parm->disp_sex = *line == '1'? 1 : *line == '2' ? 2: 0;
}
else if (keywordlen == 10 && !memcmp (keyword, "PUBKEY-URL", keywordlen))
{
xfree (parm->pubkey_url);
parm->pubkey_url = unescape_status_string (line);
}
else if (keywordlen == 10 && !memcmp (keyword, "LOGIN-DATA", keywordlen))
{
xfree (parm->login_data);
parm->login_data = unescape_status_string (line);
}
else if (keywordlen == 11 && !memcmp (keyword, "SIG-COUNTER", keywordlen))
{
parm->sig_counter = strtoul (line, NULL, 0);
}
else if (keywordlen == 10 && !memcmp (keyword, "CHV-STATUS", keywordlen))
{
char *p, *buf;
buf = p = unescape_status_string (line);
if (buf)
{
while (spacep (p))
p++;
parm->chv1_cached = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
for (i=0; *p && i < 3; i++)
{
parm->chvmaxlen[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
for (i=0; *p && i < 3; i++)
{
parm->chvretry[i] = atoi (p);
while (*p && !spacep (p))
p++;
while (spacep (p))
p++;
}
xfree (buf);
}
}
else if (keywordlen == 6 && !memcmp (keyword, "EXTCAP", keywordlen))
{
char *p, *p2, *buf;
int abool;
buf = p = unescape_status_string (line);
if (buf)
{
for (p = strtok (buf, " "); p; p = strtok (NULL, " "))
{
p2 = strchr (p, '=');
if (p2)
{
*p2++ = 0;
abool = (*p2 == '1');
if (!strcmp (p, "ki"))
parm->extcap.ki = abool;
else if (!strcmp (p, "aac"))
parm->extcap.aac = abool;
else if (!strcmp (p, "bt"))
parm->extcap.bt = abool;
else if (!strcmp (p, "kdf"))
parm->extcap.kdf = abool;
else if (!strcmp (p, "si"))
parm->status_indicator = strtoul (p2, NULL, 10);
}
}
xfree (buf);
}
}
else if (keywordlen == 7 && !memcmp (keyword, "KEY-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1len = unhexify_fpr (line, parm->fpr1, sizeof parm->fpr1);
else if (no == 2)
parm->fpr2len = unhexify_fpr (line, parm->fpr2, sizeof parm->fpr2);
else if (no == 3)
parm->fpr3len = unhexify_fpr (line, parm->fpr3, sizeof parm->fpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-TIME", keywordlen))
{
int no = atoi (line);
while (* line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->fpr1time = strtoul (line, NULL, 10);
else if (no == 2)
parm->fpr2time = strtoul (line, NULL, 10);
else if (no == 3)
parm->fpr3time = strtoul (line, NULL, 10);
}
else if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
const char *hexgrp = line;
int no;
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (strncmp (line, "OPENPGP.", 8))
;
else if ((no = atoi (line+8)) == 1)
unhexify_fpr (hexgrp, parm->grp1, sizeof parm->grp1);
else if (no == 2)
unhexify_fpr (hexgrp, parm->grp2, sizeof parm->grp2);
else if (no == 3)
unhexify_fpr (hexgrp, parm->grp3, sizeof parm->grp3);
}
else if (keywordlen == 6 && !memcmp (keyword, "CA-FPR", keywordlen))
{
int no = atoi (line);
while (*line && !spacep (line))
line++;
while (spacep (line))
line++;
if (no == 1)
parm->cafpr1len = unhexify_fpr (line, parm->cafpr1,sizeof parm->cafpr1);
else if (no == 2)
parm->cafpr2len = unhexify_fpr (line, parm->cafpr2,sizeof parm->cafpr2);
else if (no == 3)
parm->cafpr3len = unhexify_fpr (line, parm->cafpr3,sizeof parm->cafpr3);
}
else if (keywordlen == 8 && !memcmp (keyword, "KEY-ATTR", keywordlen))
{
int keyno = 0;
int algo = PUBKEY_ALGO_RSA;
int n = 0;
sscanf (line, "%d %d %n", &keyno, &algo, &n);
keyno--;
if (keyno < 0 || keyno >= DIM (parm->key_attr))
return 0;
parm->key_attr[keyno].algo = algo;
if (algo == PUBKEY_ALGO_RSA)
parm->key_attr[keyno].nbits = strtoul (line+n+3, NULL, 10);
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
parm->key_attr[keyno].curve = openpgp_is_curve_supported (line + n,
NULL, NULL);
}
else if (keywordlen == 12 && !memcmp (keyword, "PRIVATE-DO-", 11)
&& strchr("1234", keyword[11]))
{
int no = keyword[11] - '1';
log_assert (no >= 0 && no <= 3);
xfree (parm->private_do[no]);
parm->private_do[no] = unescape_status_string (line);
}
else if (keywordlen == 12 && !memcmp (keyword, "MANUFACTURER", 12))
{
xfree (parm->manufacturer_name);
parm->manufacturer_name = NULL;
parm->manufacturer_id = strtoul (line, &endp, 0);
while (endp && spacep (endp))
endp++;
if (endp && *endp)
parm->manufacturer_name = xstrdup (endp);
}
else if (keywordlen == 3 && !memcmp (keyword, "KDF", 3))
{
unsigned char *data = unescape_status_string (line);
if (data[2] != 0x03)
parm->kdf_do_enabled = 0;
else if (data[22] != 0x85)
parm->kdf_do_enabled = 1;
else
parm->kdf_do_enabled = 2;
xfree (data);
}
else if (keywordlen == 5 && !memcmp (keyword, "UIF-", 4)
&& strchr("123", keyword[4]))
{
unsigned char *data;
int no = keyword[4] - '1';
log_assert (no >= 0 && no <= 2);
data = unescape_status_string (line);
parm->uif[no] = (data[0] != 0xff);
xfree (data);
}
else if (keywordlen == 13 && !memcmp (keyword, "KEY-ATTR-INFO", 13))
{
if (!strncmp (line, "OPENPGP.", 8))
{
int no;
line += 8;
no = atoi (line);
if (no >= 1 && no <= 3)
{
no--;
line++;
while (spacep (line))
line++;
append_to_strlist (&parm->supported_keyalgo[no], xstrdup (line));
}
}
/* Skip when it's not "OPENPGP.[123]". */
}
return 0;
}
/* Call the scdaemon to learn about a smartcard. Note that in
* contradiction to the function's name, gpg-agent's LEARN command is
* used and not the low-level "SCD LEARN".
* Used by:
* card-util.c
* keyedit_menu
- * card_store_key_with_backup (Woth force to remove secret key data)
+ * card_store_key_with_backup (With force to remove secret key data)
*/
int
agent_scd_learn (struct agent_card_info_s *info, int force)
{
int rc;
struct default_inq_parm_s parm;
struct agent_card_info_s dummyinfo;
if (!info)
info = &dummyinfo;
memset (info, 0, sizeof *info);
memset (&parm, 0, sizeof parm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx,
force ? "LEARN --sendinfo --force" : "LEARN --sendinfo",
dummy_data_cb, NULL, default_inq_cb, &parm,
learn_status_cb, info);
/* Also try to get the key attributes. */
if (!rc)
agent_scd_getattr ("KEY-ATTR", info);
if (info == &dummyinfo)
agent_release_card_info (info);
return rc;
}
struct keypairinfo_cb_parm_s
{
keypair_info_t kpinfo;
keypair_info_t *kpinfo_tail;
};
/* Callback for the agent_scd_keypairinfo function. */
static gpg_error_t
scd_keypairinfo_status_cb (void *opaque, const char *line)
{
struct keypairinfo_cb_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *keyword = line;
int keywordlen;
char *line_buffer = NULL;
keypair_info_t kpi = NULL;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime] [algostr]
*/
const char *fields[4];
int nfields;
const char *hexgrp, *keyref, *usage;
time_t atime;
u32 keytime;
line_buffer = xtrystrdup (line);
if (!line_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 2)
goto leave; /* not enough args - invalid status line - ignore */
hexgrp = fields[0];
keyref = fields[1];
if (nfields > 2)
usage = fields[2];
else
usage = "";
if (nfields > 3)
{
atime = parse_timestamp (fields[3], NULL);
if (atime == (time_t)(-1))
atime = 0;
keytime = atime;
}
else
keytime = 0;
kpi = xtrycalloc (1, sizeof *kpi);
if (!kpi)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (*hexgrp == 'X' && !hexgrp[1])
*kpi->keygrip = 0; /* No hexgrip. */
else if (strlen (hexgrp) == 2*KEYGRIP_LEN)
mem2str (kpi->keygrip, hexgrp, sizeof kpi->keygrip);
else
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (!*keyref)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
kpi->idstr = xtrystrdup (keyref);
if (!kpi->idstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Parse and set the usage. */
for (; *usage; usage++)
{
switch (*usage)
{
case 's': kpi->usage |= GCRY_PK_USAGE_SIGN; break;
case 'c': kpi->usage |= GCRY_PK_USAGE_CERT; break;
case 'a': kpi->usage |= GCRY_PK_USAGE_AUTH; break;
case 'e': kpi->usage |= GCRY_PK_USAGE_ENCR; break;
}
}
kpi->keytime = keytime;
/* Append to the list. */
*parm->kpinfo_tail = kpi;
parm->kpinfo_tail = &kpi->next;
kpi = NULL;
}
leave:
free_keypair_info (kpi);
xfree (line_buffer);
return err;
}
/* Read the keypairinfo lines of the current card directly from
* scdaemon. The list is returned as a string made up of the keygrip,
* a space and the keyref. The flags of the string carry the usage
* bits. If KEYREF is not NULL, only a single string is returned
* which matches the given keyref. */
gpg_error_t
agent_scd_keypairinfo (ctrl_t ctrl, const char *keyref, keypair_info_t *r_list)
{
gpg_error_t err;
struct keypairinfo_cb_parm_s parm;
struct default_inq_parm_s inq_parm;
char line[ASSUAN_LINELENGTH];
*r_list = NULL;
err= start_agent (ctrl, 1);
if (err)
return err;
memset (&inq_parm, 0, sizeof inq_parm);
inq_parm.ctx = agent_ctx;
parm.kpinfo = NULL;
parm.kpinfo_tail = &parm.kpinfo;
if (keyref)
snprintf (line, DIM(line), "SCD READKEY --info-only %s", keyref);
else
snprintf (line, DIM(line), "SCD LEARN --keypairinfo");
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &inq_parm,
scd_keypairinfo_status_cb, &parm);
if (!err && !parm.kpinfo)
err = gpg_error (GPG_ERR_NO_DATA);
if (err)
free_keypair_info (parm.kpinfo);
else
*r_list = parm.kpinfo;
return err;
}
/* Send an APDU to the current card. On success the status word is
* stored at R_SW unless R_SQ is NULL. With HEXAPDU being NULL only a
* RESET command is send to scd. HEXAPDU may also be one of theseo
* special strings:
*
* "undefined" :: Send the command "SCD SERIALNO undefined"
* "lock" :: Send the command "SCD LOCK --wait"
* "trylock" :: Send the command "SCD LOCK"
* "unlock" :: Send the command "SCD UNLOCK"
* "reset-keep-lock" :: Send the command "SCD RESET --keep-lock"
*
* Used by:
* card-util.c
*/
gpg_error_t
agent_scd_apdu (const char *hexapdu, unsigned int *r_sw)
{
gpg_error_t err;
/* Start the agent but not with the card flag so that we do not
autoselect the openpgp application. */
err = start_agent (NULL, 0);
if (err)
return err;
if (!hexapdu)
{
err = assuan_transact (agent_ctx, "SCD RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "reset-keep-lock"))
{
err = assuan_transact (agent_ctx, "SCD RESET --keep-lock",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "lock"))
{
err = assuan_transact (agent_ctx, "SCD LOCK --wait",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "trylock"))
{
err = assuan_transact (agent_ctx, "SCD LOCK",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "unlock"))
{
err = assuan_transact (agent_ctx, "SCD UNLOCK",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else if (!strcmp (hexapdu, "undefined"))
{
err = assuan_transact (agent_ctx, "SCD SERIALNO undefined",
NULL, NULL, NULL, NULL, NULL, NULL);
}
else
{
char line[ASSUAN_LINELENGTH];
membuf_t mb;
unsigned char *data;
size_t datalen;
init_membuf (&mb, 256);
snprintf (line, DIM(line), "SCD APDU %s", hexapdu);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &mb, NULL, NULL, NULL, NULL);
if (!err)
{
data = get_membuf (&mb, &datalen);
if (!data)
err = gpg_error_from_syserror ();
else if (datalen < 2) /* Ooops */
err = gpg_error (GPG_ERR_CARD);
else
{
*r_sw = buf16_to_uint (data+datalen-2);
}
xfree (data);
}
}
return err;
}
int
agent_keytotpm (ctrl_t ctrl, const char *hexgrip)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
snprintf(line, DIM(line), "KEYTOTPM %s\n", hexgrip);
if (strchr (hexgrip, ','))
{
log_error ("storing a part of a dual key is not yet supported\n");
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
rc = start_agent (ctrl, 0);
if (rc)
return rc;
parm.ctx = agent_ctx;
parm.ctrl = ctrl;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
NULL, NULL);
if (rc)
log_log (GPGRT_LOGLVL_ERROR, _("error from TPM: %s\n"), gpg_strerror (rc));
return rc;
}
/* Used by:
* card_store_subkey
* card_store_key_with_backup
*/
int
agent_keytocard (const char *hexgrip, int keyno, int force,
const char *serialno, const char *timestamp,
const char *ecdh_param_str)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (strchr (hexgrip, ','))
{
log_error ("storing a part of a dual key is not yet supported\n");
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
snprintf (line, DIM(line), "KEYTOCARD %s%s %s OPENPGP.%d %s%s%s",
force?"--force ": "", hexgrip, serialno, keyno, timestamp,
ecdh_param_str? " ":"", ecdh_param_str? ecdh_param_str:"");
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Object used with the agent_scd_getattr_one. */
struct getattr_one_parm_s {
const char *keyword; /* Keyword to look for. */
char *data; /* Malloced and unescaped data. */
gpg_error_t err; /* Error code or 0 on success. */
};
/* Callback for agent_scd_getattr_one. */
static gpg_error_t
getattr_one_status_cb (void *opaque, const char *line)
{
struct getattr_one_parm_s *parm = opaque;
const char *s;
if (parm->data)
return 0; /* We want only the first occurrence. */
if ((s=has_leading_keyword (line, parm->keyword)))
{
parm->data = percent_plus_unescape (s, 0xff);
if (!parm->data)
parm->err = gpg_error_from_syserror ();
}
return 0;
}
/* Simplified version of agent_scd_getattr. This function returns
* only the first occurrence of the attribute NAME and stores it at
* R_VALUE. A nul in the result is silennly replaced by 0xff. On
* error NULL is stored at R_VALUE. */
gpg_error_t
agent_scd_getattr_one (const char *name, char **r_value)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inqparm;
struct getattr_one_parm_s parm;
*r_value = NULL;
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
memset (&inqparm, 0, sizeof inqparm);
inqparm.ctx = agent_ctx;
memset (&parm, 0, sizeof parm);
parm.keyword = name;
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
err = start_agent (NULL, 1);
if (err)
return err;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &inqparm,
getattr_one_status_cb, &parm);
if (!err && parm.err)
err = parm.err;
else if (!err && !parm.data)
err = gpg_error (GPG_ERR_NO_DATA);
if (!err)
*r_value = parm.data;
else
xfree (parm.data);
return err;
}
/* Call the agent to retrieve a data object. This function returns
* the data in the same structure as used by the learn command. It is
* allowed to update such a structure using this command.
*
* Used by:
* build_sk_list
* enum_secret_keys
* get_signature_count
* card-util.c
* generate_keypair (KEY-ATTR)
* card_store_key_with_backup (SERIALNO)
* generate_card_subkeypair (KEY-ATTR)
*/
int
agent_scd_getattr (const char *name, struct agent_card_info_s *info)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
stpcpy (stpcpy (line, "SCD GETATTR "), name);
rc = start_agent (NULL, 1);
if (rc)
return rc;
parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line, NULL, NULL, default_inq_cb, &parm,
learn_status_cb, info);
if (!rc && !strcmp (name, "KEY-FPR"))
{
/* Let the agent create the shadow keys if not yet done. */
if (info->fpr1len)
assuan_transact (agent_ctx, "READKEY --card --no-data -- $SIGNKEYID",
NULL, NULL, NULL, NULL, NULL, NULL);
if (info->fpr2len)
assuan_transact (agent_ctx, "READKEY --card --no-data -- $ENCRKEYID",
NULL, NULL, NULL, NULL, NULL, NULL);
}
return rc;
}
/* Send an setattr command to the SCdaemon.
* Used by:
* card-util.c
*/
gpg_error_t
agent_scd_setattr (const char *name, const void *value_arg, size_t valuelen)
{
gpg_error_t err;
const unsigned char *value = value_arg;
char line[ASSUAN_LINELENGTH];
char *p;
struct default_inq_parm_s parm;
memset (&parm, 0, sizeof parm);
if (!*name || !valuelen)
return gpg_error (GPG_ERR_INV_VALUE);
/* We assume that NAME does not need escaping. */
if (12 + strlen (name) > DIM(line)-1)
return gpg_error (GPG_ERR_TOO_LARGE);
p = stpcpy (stpcpy (line, "SCD SETATTR "), name);
*p++ = ' ';
for (; valuelen; value++, valuelen--)
{
if (p >= line + DIM(line)-5 )
return gpg_error (GPG_ERR_TOO_LARGE);
if (*value < ' ' || *value == '+' || *value == '%')
{
sprintf (p, "%%%02X", *value);
p += 3;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
*p = 0;
err = start_agent (NULL, 1);
if (!err)
{
parm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &parm, NULL, NULL);
}
status_sc_op_failure (err);
return err;
}
/* Handle a CERTDATA inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END
command. */
static gpg_error_t
inq_writecert_parms (void *opaque, const char *line)
{
int rc;
struct writecert_parm_s *parm = opaque;
if (has_leading_keyword (line, "CERTDATA"))
{
rc = assuan_send_data (parm->dflt->ctx,
parm->certdata, parm->certdatalen);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Send a WRITECERT command to the SCdaemon.
* Used by:
* card-util.c
*/
int
agent_scd_writecert (const char *certidstr,
const unsigned char *certdata, size_t certdatalen)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct writecert_parm_s parms;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
memset (&parms, 0, sizeof parms);
snprintf (line, DIM(line), "SCD WRITECERT %s", certidstr);
dfltparm.ctx = agent_ctx;
parms.dflt = &dfltparm;
parms.certdata = certdata;
parms.certdatalen = certdatalen;
rc = assuan_transact (agent_ctx, line, NULL, NULL,
inq_writecert_parms, &parms, NULL, NULL);
return rc;
}
/* Status callback for the SCD GENKEY command. */
static gpg_error_t
scd_genkey_cb (void *opaque, const char *line)
{
u32 *createtime = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 14 && !memcmp (keyword,"KEY-CREATED-AT", keywordlen))
{
*createtime = (u32)strtoul (line, NULL, 10);
}
else if (keywordlen == 8 && !memcmp (keyword, "PROGRESS", keywordlen))
{
write_status_text (STATUS_PROGRESS, line);
}
return 0;
}
/* Send a GENKEY command to the SCdaemon. If *CREATETIME is not 0,
* the value will be passed to SCDAEMON with --timestamp option so that
* the key is created with this. Otherwise, timestamp was generated by
* SCDEAMON. On success, creation time is stored back to
* CREATETIME.
* Used by:
* gen_card_key
*/
int
agent_scd_genkey (int keyno, int force, u32 *createtime)
{
int rc;
char line[ASSUAN_LINELENGTH];
gnupg_isotime_t tbuf;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
if (*createtime)
epoch2isotime (tbuf, *createtime);
else
*tbuf = 0;
snprintf (line, DIM(line), "SCD GENKEY %s%s %s %d",
*tbuf? "--timestamp=":"", tbuf,
force? "--force":"",
keyno);
dfltparm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, line,
NULL, NULL, default_inq_cb, &dfltparm,
scd_genkey_cb, createtime);
status_sc_op_failure (rc);
return rc;
}
/* Return the serial number of the card or an appropriate error. The
* serial number is returned as a hexstring. With DEMAND the active
* card is switched to the card with that serialno.
* Used by:
* card-util.c
* build_sk_list
* enum_secret_keys
*/
int
agent_scd_serialno (char **r_serialno, const char *demand)
{
int err;
char *serialno = NULL;
char line[ASSUAN_LINELENGTH];
if (r_serialno)
*r_serialno = NULL;
err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS));
if (err)
return err;
if (!demand)
strcpy (line, "SCD SERIALNO");
else
snprintf (line, DIM(line), "SCD SERIALNO --demand=%s", demand);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
get_serialno_cb, &serialno);
if (err)
{
xfree (serialno);
return err;
}
if (r_serialno)
*r_serialno = serialno;
else
xfree (serialno);
return 0;
}
/* Send a READCERT command to the SCdaemon.
* Used by:
* card-util.c
*/
int
agent_scd_readcert (const char *certidstr,
void **r_buf, size_t *r_buflen)
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
*r_buf = NULL;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
init_membuf (&data, 2048);
snprintf (line, DIM(line), "SCD READCERT %s", certidstr);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
*r_buf = get_membuf (&data, r_buflen);
if (!*r_buf)
return gpg_error (GPG_ERR_ENOMEM);
return 0;
}
/* Callback for the agent_scd_readkey function. */
static gpg_error_t
readkey_status_cb (void *opaque, const char *line)
{
u32 *keytimep = opaque;
gpg_error_t err = 0;
const char *args;
char *line_buffer = NULL;
/* FIXME: Get that info from the KEYPAIRINFO line. */
if ((args = has_leading_keyword (line, "KEYPAIRINFO"))
&& !*keytimep)
{
/* The format of such a line is:
* KEYPAIRINFO <hexgrip> <keyref> [usage] [keytime]
*
* Note that we use only the first valid KEYPAIRINFO line. More
* lines are possible if a second card carries the same key.
*/
const char *fields[4];
int nfields;
time_t atime;
line_buffer = xtrystrdup (line);
if (!line_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((nfields = split_fields (line_buffer, fields, DIM (fields))) < 4)
goto leave; /* not enough args - ignore */
if (nfields > 3)
{
atime = parse_timestamp (fields[3], NULL);
if (atime == (time_t)(-1))
atime = 0;
*keytimep = atime;
}
else
*keytimep = 0;
}
leave:
xfree (line_buffer);
return err;
}
/* This is a variant of agent_readkey which sends a READKEY command
* directly Scdaemon. On success a new s-expression is stored at
* R_RESULT. If R_KEYTIME is not NULL the key cresation time of an
* OpenPGP card is stored there - if that is not known 0 is stored.
* In the latter case it is allowed to pass NULL for R_RESULT. */
gpg_error_t
agent_scd_readkey (ctrl_t ctrl, const char *keyrefstr,
gcry_sexp_t *r_result, u32 *r_keytime)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
unsigned char *buf;
size_t len, buflen;
struct default_inq_parm_s dfltparm;
u32 keytime;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctx = agent_ctx;
if (r_result)
*r_result = NULL;
if (r_keytime)
*r_keytime = 0;
err = start_agent (ctrl, 1);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, DIM(line),
"SCD READKEY --info%s -- %s",
r_result? "":"-only", keyrefstr);
keytime = 0;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
readkey_status_cb, &keytime);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &buflen);
if (!buf)
return gpg_error_from_syserror ();
if (r_result)
err = gcry_sexp_new (r_result, buf, buflen, 0);
else
err = 0;
xfree (buf);
if (!err && r_keytime)
*r_keytime = keytime;
return err;
}
struct card_cardlist_parm_s {
int error;
strlist_t list;
};
/* Callback function for agent_card_cardlist. */
static gpg_error_t
card_cardlist_cb (void *opaque, const char *line)
{
struct card_cardlist_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
const char *s;
int n;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n || (n&1) || *s)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
else
add_to_strlist (&parm->list, line);
}
return 0;
}
/* Return a list of currently available cards.
* Used by:
* card-util.c
* skclist.c
*/
int
agent_scd_cardlist (strlist_t *result)
{
int err;
char line[ASSUAN_LINELENGTH];
struct card_cardlist_parm_s parm;
memset (&parm, 0, sizeof parm);
*result = NULL;
err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
if (err)
return err;
strcpy (line, "SCD GETINFO card_list");
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
card_cardlist_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_strlist (parm.list);
return 0;
}
/* Make the app APPNAME the one on the card. This is sometimes
* required to make sure no other process has switched a card to
* another application. The only useful APPNAME is "openpgp". */
gpg_error_t
agent_scd_switchapp (const char *appname)
{
int err;
char line[ASSUAN_LINELENGTH];
if (appname && !*appname)
appname = NULL;
err = start_agent (NULL, (1 | FLAG_FOR_CARD_SUPPRESS_ERRORS));
if (err)
return err;
snprintf (line, DIM(line), "SCD SWITCHAPP --%s%s",
appname? " ":"", appname? appname:"");
return assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
NULL, NULL);
}
struct card_keyinfo_parm_s {
int error;
keypair_info_t list;
};
/* Callback function for agent_card_keylist. */
static gpg_error_t
card_keyinfo_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct card_keyinfo_parm_s *parm = opaque;
const char *keyword = line;
int keywordlen;
keypair_info_t keyinfo = NULL;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 7 && !memcmp (keyword, "KEYINFO", keywordlen))
{
const char *s;
int n;
keypair_info_t *l_p = &parm->list;
while ((*l_p))
l_p = &(*l_p)->next;
keyinfo = xtrycalloc (1, sizeof *keyinfo);
if (!keyinfo)
goto alloc_error;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (n != 40)
goto parm_error;
memcpy (keyinfo->keygrip, line, 40);
keyinfo->keygrip[40] = 0;
line = s;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
if (*line++ != 'T')
goto parm_error;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
for (n=0,s=line; hexdigitp (s); s++, n++)
;
if (!n)
goto parm_error;
keyinfo->serialno = xtrymalloc (n+1);
if (!keyinfo->serialno)
goto alloc_error;
memcpy (keyinfo->serialno, line, n);
keyinfo->serialno[n] = 0;
line = s;
if (!*line)
goto parm_error;
while (spacep (line))
line++;
if (!*line)
goto parm_error;
keyinfo->idstr = xtrystrdup (line);
if (!keyinfo->idstr)
goto alloc_error;
*l_p = keyinfo;
}
return err;
alloc_error:
xfree (keyinfo);
if (!parm->error)
parm->error = gpg_error_from_syserror ();
return 0;
parm_error:
xfree (keyinfo);
if (!parm->error)
parm->error = gpg_error (GPG_ERR_ASS_PARAMETER);
return 0;
}
/* Free a keypair info list. */
void
free_keypair_info (keypair_info_t l)
{
keypair_info_t l_next;
for (; l; l = l_next)
{
l_next = l->next;
xfree (l->serialno);
xfree (l->idstr);
xfree (l);
}
}
/* Call the scdaemon to check if a key of KEYGRIP is available, or
retrieve list of available keys on cards. With CAP, we can limit
keys with specified capability. On success, the allocated
structure is stored at RESULT. On error, an error code is returned
and NULL is stored at RESULT. */
gpg_error_t
agent_scd_keyinfo (const char *keygrip, int cap,
keypair_info_t *result)
{
int err;
struct card_keyinfo_parm_s parm;
char line[ASSUAN_LINELENGTH];
char *list_option;
*result = NULL;
switch (cap)
{
case 0: list_option = "--list"; break;
case GCRY_PK_USAGE_SIGN: list_option = "--list=sign"; break;
case GCRY_PK_USAGE_ENCR: list_option = "--list=encr"; break;
case GCRY_PK_USAGE_AUTH: list_option = "--list=auth"; break;
default: return gpg_error (GPG_ERR_INV_VALUE);
}
memset (&parm, 0, sizeof parm);
snprintf (line, sizeof line, "SCD KEYINFO %s",
keygrip ? keygrip : list_option);
err = start_agent (NULL, 1 | FLAG_FOR_CARD_SUPPRESS_ERRORS);
if (err)
return err;
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL,
card_keyinfo_cb, &parm);
if (!err && parm.error)
err = parm.error;
if (!err)
*result = parm.list;
else
free_keypair_info (parm.list);
return err;
}
/* Change the PIN of an OpenPGP card or reset the retry counter.
* CHVNO 1: Change the PIN
* 2: For v1 cards: Same as 1.
* For v2 cards: Reset the PIN using the Reset Code.
* 3: Change the admin PIN
* 101: Set a new PIN and reset the retry counter
* 102: For v1 cars: Same as 101.
* For v2 cards: Set a new Reset Code.
* SERIALNO is not used.
* Used by:
* card-util.c
*/
int
agent_scd_change_pin (int chvno, const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
const char *reset = "";
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
(void)serialno;
if (chvno >= 100)
reset = "--reset";
chvno %= 100;
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD PASSWD %s %d", reset, chvno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Perform a CHECKPIN operation. SERIALNO should be the serial
* number of the card - optionally followed by the fingerprint;
* however the fingerprint is ignored here.
* Used by:
* card-util.c
*/
int
agent_scd_checkpin (const char *serialno)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 1);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "SCD CHECKPIN %s", serialno);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
status_sc_op_failure (rc);
return rc;
}
/* Note: All strings shall be UTF-8. On success the caller needs to
free the string stored at R_PASSPHRASE. On error NULL will be
stored at R_PASSPHRASE and an appropriate error code returned.
Only called from passphrase.c:passphrase_get - see there for more
comments on this ugly API. */
gpg_error_t
agent_get_passphrase (const char *cache_id,
const char *err_msg,
const char *prompt,
const char *desc_msg,
int newsymkey,
int repeat,
int check,
char **r_passphrase)
{
int rc;
char line[ASSUAN_LINELENGTH];
char *arg1 = NULL;
char *arg2 = NULL;
char *arg3 = NULL;
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s dfltparm;
int have_newsymkey, wasconf;
memset (&dfltparm, 0, sizeof dfltparm);
*r_passphrase = NULL;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
/* Check that the gpg-agent understands the repeat option. */
if (assuan_transact (agent_ctx,
"GETINFO cmd_has_option GET_PASSPHRASE repeat",
NULL, NULL, NULL, NULL, NULL, NULL))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
have_newsymkey = !(assuan_transact
(agent_ctx,
"GETINFO cmd_has_option GET_PASSPHRASE newsymkey",
NULL, NULL, NULL, NULL, NULL, NULL));
if (cache_id && *cache_id)
if (!(arg1 = percent_plus_escape (cache_id)))
goto no_mem;
if (err_msg && *err_msg)
if (!(arg2 = percent_plus_escape (err_msg)))
goto no_mem;
if (prompt && *prompt)
if (!(arg3 = percent_plus_escape (prompt)))
goto no_mem;
if (desc_msg && *desc_msg)
if (!(arg4 = percent_plus_escape (desc_msg)))
goto no_mem;
/* CHECK && REPEAT or NEWSYMKEY is here an indication that a new
* passphrase for symmetric encryption is requested; if the agent
* supports this we enable the modern API by also passing --newsymkey. */
snprintf (line, DIM(line),
"GET_PASSPHRASE --data --repeat=%d%s%s -- %s %s %s %s",
repeat,
((repeat && check) || newsymkey)? " --check":"",
(have_newsymkey && newsymkey)? " --newsymkey":"",
arg1? arg1:"X",
arg2? arg2:"X",
arg3? arg3:"X",
arg4? arg4:"X");
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
init_membuf_secure (&data, 64);
wasconf = assuan_get_flag (agent_ctx, ASSUAN_CONFIDENTIAL);
assuan_begin_confidential (agent_ctx);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (!wasconf)
assuan_end_confidential (agent_ctx);
if (rc)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
rc = gpg_error_from_syserror ();
}
return rc;
no_mem:
rc = gpg_error_from_syserror ();
xfree (arg1);
xfree (arg2);
xfree (arg3);
xfree (arg4);
return rc;
}
gpg_error_t
agent_clear_passphrase (const char *cache_id)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
if (!cache_id || !*cache_id)
return 0;
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "CLEAR_PASSPHRASE %s", cache_id);
return assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpg_agent_get_confirmation (const char *desc)
{
int rc;
char *tmp;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
rc = start_agent (NULL, 0);
if (rc)
return rc;
dfltparm.ctx = agent_ctx;
tmp = percent_plus_escape (desc);
if (!tmp)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_CONFIRMATION %s", tmp);
xfree (tmp);
rc = assuan_transact (agent_ctx, line,
NULL, NULL,
default_inq_cb, &dfltparm,
NULL, NULL);
return rc;
}
/* Return the S2K iteration count as computed by gpg-agent. On error
* print a warning and return a default value. */
unsigned long
agent_get_s2k_count (void)
{
gpg_error_t err;
membuf_t data;
char *buf;
unsigned long count = 0;
err = start_agent (NULL, 0);
if (err)
goto leave;
init_membuf (&data, 32);
err = assuan_transact (agent_ctx, "GETINFO s2k_count",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
buf = get_membuf (&data, NULL);
if (!buf)
err = gpg_error_from_syserror ();
else
{
count = strtoul (buf, NULL, 10);
xfree (buf);
}
}
leave:
if (err || count < 65536)
{
/* Don't print an error if an older agent is used. */
if (err && gpg_err_code (err) != GPG_ERR_ASS_PARAMETER)
log_error (_("problem with the agent: %s\n"), gpg_strerror (err));
/* Default to 65536 which was used up to 2.0.13. */
count = 65536;
}
return count;
}
struct keyinfo_data_parm_s
{
char *serialno;
int is_smartcard;
int passphrase_cached;
int cleartext;
int card_available;
};
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
struct keyinfo_data_parm_s *data = opaque;
char *s;
if ((s = has_leading_keyword (line, "KEYINFO")) && data)
{
/* Parse the arguments:
* 0 1 2 3 4 5
* <keygrip> <type> <serialno> <idstr> <cached> <protection>
*
* 6 7 8
* <sshfpr> <ttl> <flags>
*/
const char *fields[9];
if (split_fields (s, fields, DIM (fields)) == 9)
{
data->is_smartcard = (fields[1][0] == 'T');
if (data->is_smartcard && !data->serialno && strcmp (fields[2], "-"))
data->serialno = xtrystrdup (fields[2]);
/* '1' for cached */
data->passphrase_cached = (fields[4][0] == '1');
/* 'P' for protected, 'C' for clear */
data->cleartext = (fields[5][0] == 'C');
/* 'A' for card is available */
data->card_available = (fields[8][0] == 'A');
}
}
return 0;
}
/* Ask the agent whether a secret key for the given public key is
* available. Returns 0 if not available. Bigger value is preferred.
* Will never return a value less than 0. Defined return values are:
* 0 := No key or error
* 1 := Key available
* 2 := Key available on a smartcard
* 3 := Key available and passphrase cached
* 4 := Key available on current smartcard
*/
int
agent_probe_secret_key (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *hexgrip, *p;
struct keyinfo_data_parm_s keyinfo;
int result, result2;
memset (&keyinfo, 0, sizeof keyinfo);
err = start_agent (ctrl, 0);
if (err)
return 0;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
return 0;
if ((p=strchr (hexgrip, ',')))
*p++ = 0;
snprintf (line, sizeof line, "KEYINFO %s", hexgrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
xfree (keyinfo.serialno);
if (err)
result = 0;
else if (keyinfo.card_available)
result = 4;
else if (keyinfo.passphrase_cached)
result = 3;
else if (keyinfo.is_smartcard)
result = 2;
else
result = 1;
if (!p)
{
xfree (hexgrip);
return result; /* Not a dual algo - we are ready. */
}
/* Now check the second keygrip. */
memset (&keyinfo, 0, sizeof keyinfo);
snprintf (line, sizeof line, "KEYINFO %s", p);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
xfree (keyinfo.serialno);
if (err)
result2 = 0;
else if (keyinfo.card_available)
result2 = 4;
else if (keyinfo.passphrase_cached)
result2 = 3;
else if (keyinfo.is_smartcard)
result2 = 2;
else
result2 = 1;
xfree (hexgrip);
if (result == result2)
return result; /* Both keys have the same status. */
else if (!result && result2)
return 0; /* Only first key available - return no key. */
else if (result && !result2)
- return 0; /* Only second key not availabale - return no key. */
+ return 0; /* Only second key not available - return no key. */
else if (result == 4 || result == 2)
return result; /* First key on card - don't care where the second is. */
else
return result;
}
/* Ask the agent whether a secret key is available for any of the
keys (primary or sub) in KEYBLOCK. Returns 0 if available. */
gpg_error_t
agent_probe_any_secret_key (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *p;
kbnode_t kbctx, node;
int nkeys; /* (always zero in secret_keygrips mode) */
unsigned char grip[KEYGRIP_LEN];
unsigned char grip2[KEYGRIP_LEN];
int grip2_valid;
const unsigned char *s;
unsigned int n;
err = start_agent (ctrl, 0);
if (err)
return err;
/* If we have not yet issued a "HAVEKEY --list" do that now. We use
- * a more or less arbitray limit of 1000 keys. */
+ * a more or less arbitrary limit of 1000 keys. */
if (ctrl && !ctrl->secret_keygrips && !ctrl->no_more_secret_keygrips)
{
membuf_t data;
init_membuf (&data, 4096);
err = assuan_transact (agent_ctx, "HAVEKEY --list=1000",
put_membuf_cb, &data,
NULL, NULL, NULL, NULL);
if (err)
xfree (get_membuf (&data, NULL));
else
{
ctrl->secret_keygrips = get_membuf (&data,
&ctrl->secret_keygrips_len);
if (!ctrl->secret_keygrips)
err = gpg_error_from_syserror ();
if ((ctrl->secret_keygrips_len % 20))
{
err = gpg_error (GPG_ERR_INV_DATA);
xfree (ctrl->secret_keygrips);
ctrl->secret_keygrips = NULL;
}
}
if (err)
{
if (!opt.quiet)
log_info ("problem with fast path key listing: %s - ignored\n",
gpg_strerror (err));
err = 0;
}
/* We want to do this only once. */
ctrl->no_more_secret_keygrips = 1;
}
err = gpg_error (GPG_ERR_NO_SECKEY); /* Just in case no key was
found in KEYBLOCK. */
p = stpcpy (line, "HAVEKEY");
for (kbctx=NULL, nkeys=0; (node = walk_kbnode (keyblock, &kbctx, 0)); )
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (ctrl && ctrl->secret_keygrips)
{
/* We got an array with all secret keygrips. Check this. */
err = keygrip_from_pk (node->pkt->pkt.public_key, grip, 0);
if (err)
return err;
err = keygrip_from_pk (node->pkt->pkt.public_key, grip2, 1);
if (err && gpg_err_code (err) != GPG_ERR_FALSE)
return err;
grip2_valid = !err;
for (s=ctrl->secret_keygrips, n = 0;
n < ctrl->secret_keygrips_len;
s += 20, n += 20)
{
if (!memcmp (s, grip, 20))
return 0;
if (grip2_valid && !memcmp (s, grip2, 20))
return 0;
}
err = gpg_error (GPG_ERR_NO_SECKEY);
/* Keep on looping over the keyblock. Never bump nkeys. */
}
else
{
if (nkeys
&& ((p - line) + 4*KEYGRIP_LEN+1+1) > (ASSUAN_LINELENGTH - 2))
{
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err != gpg_err_code (GPG_ERR_NO_SECKEY))
break; /* Seckey available or unexpected error - ready. */
p = stpcpy (line, "HAVEKEY");
nkeys = 0;
}
err = keygrip_from_pk (node->pkt->pkt.public_key, grip, 0);
if (err)
return err;
*p++ = ' ';
bin2hex (grip, 20, p);
p += 40;
nkeys++;
err = keygrip_from_pk (node->pkt->pkt.public_key, grip2, 1);
if (err && gpg_err_code (err) != GPG_ERR_FALSE)
return err;
if (!err) /* Add the second keygrip from dual algos. */
{
*p++ = ' ';
bin2hex (grip2, 20, p);
p += 40;
nkeys++;
}
}
}
if (!err && nkeys)
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
return err;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO.
if r_cleartext is not NULL, the referenced int will be set to 1 if
the agent's copy of the key is stored in the clear, or 0 otherwise
*/
gpg_error_t
agent_get_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno, int *r_cleartext)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct keyinfo_data_parm_s keyinfo;
const char *s;
memset (&keyinfo, 0,sizeof keyinfo);
*r_serialno = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
/* FIXME: Support dual keys. Maybe under the assumption that the
* first key might be on a card. */
if (!hexkeygrip)
return gpg_error (GPG_ERR_INV_VALUE);
s = strchr (hexkeygrip, ',');
if (!s)
s = hexkeygrip + strlen (hexkeygrip);
if (s - hexkeygrip != 40)
return gpg_error (GPG_ERR_INV_VALUE);
/* Note that for a dual algo we only get info for the first key.
* FIXME: We need to see how we can show the status of the second
* key in a key listing. */
snprintf (line, DIM(line), "KEYINFO %.40s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &keyinfo);
if (!err && keyinfo.serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (keyinfo.serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (keyinfo.serialno);
else
{
*r_serialno = keyinfo.serialno;
if (r_cleartext)
*r_cleartext = keyinfo.cleartext;
}
return err;
}
/* Status callback for agent_import_key, agent_export_key and
agent_genkey. */
static gpg_error_t
cache_nonce_status_cb (void *opaque, const char *line)
{
struct cache_nonce_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "CACHE_NONCE")))
{
if (parm->cache_nonce_addr)
{
xfree (*parm->cache_nonce_addr);
*parm->cache_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PASSWD_NONCE")))
{
if (parm->passwd_nonce_addr)
{
xfree (*parm->passwd_nonce_addr);
*parm->passwd_nonce_addr = xtrystrdup (s);
}
}
else if ((s = has_leading_keyword (line, "PROGRESS")))
{
if (opt.enable_progress_filter)
write_status_text (STATUS_PROGRESS, s);
}
return 0;
}
/* Handle a KEYPARMS inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYPARAM"))
{
err = assuan_send_data (parm->dflt->ctx,
parm->keyparms, strlen (parm->keyparms));
}
else if (has_leading_keyword (line, "NEWPASSWD") && parm->passphrase)
{
err = assuan_send_data (parm->dflt->ctx,
parm->passphrase, strlen (parm->passphrase));
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to generate a new key. KEYPARMS is the usual
S-expression giving the parameters of the key. gpg-agent passes it
gcry_pk_genkey. If NO_PROTECTION is true the agent is advised not
to protect the generated key. If NO_PROTECTION is not set and
PASSPHRASE is not NULL the agent is requested to protect the key
with that passphrase instead of asking for one. TIMESTAMP is the
creation time of the key or zero. */
gpg_error_t
agent_genkey (ctrl_t ctrl, char **cache_nonce_addr, char **passwd_nonce_addr,
const char *keyparms, int no_protection,
const char *passphrase, time_t timestamp, gcry_sexp_t *r_pubkey)
{
gpg_error_t err;
struct genkey_parm_s gk_parm;
struct cache_nonce_parm_s cn_parm;
struct default_inq_parm_s dfltparm;
membuf_t data;
size_t len;
unsigned char *buf;
char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */
char line[ASSUAN_LINELENGTH];
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
/* Do not use our cache of secret keygrips anymore - this command
* would otherwise requiring to update that cache. */
if (ctrl && ctrl->secret_keygrips)
{
xfree (ctrl->secret_keygrips);
ctrl->secret_keygrips = 0;
}
if (timestamp)
{
strcpy (timestamparg, " --timestamp=");
epoch2isotime (timestamparg+13, timestamp);
}
else
*timestamparg = 0;
if (passwd_nonce_addr && *passwd_nonce_addr)
; /* A RESET would flush the passwd nonce cache. */
else
{
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf (&data, 1024);
gk_parm.dflt = &dfltparm;
gk_parm.keyparms = keyparms;
gk_parm.passphrase = passphrase;
snprintf (line, sizeof line, "GENKEY%s%s%s%s%s%s",
*timestamparg? timestamparg : "",
no_protection? " --no-protection" :
passphrase ? " --inq-passwd" :
/* */ "",
passwd_nonce_addr && *passwd_nonce_addr? " --passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_pubkey, NULL, buf, len);
xfree (buf);
}
return err;
}
/* Call the agent to read the public key part for a given keygrip.
* Values from FROMCARD:
* 0 - Standard
* 1 - The key is read from the current card
* via the agent and a stub file is created.
*/
gpg_error_t
agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
unsigned char **r_pubkey)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_pubkey = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (fromcard)
snprintf (line, DIM(line), "READKEY --card -- %s", hexkeygrip);
else
snprintf (line, DIM(line), "READKEY -- %s", hexkeygrip);
init_membuf (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Call the agent to do a sign operation using the key identified by
the hex string KEYGRIP. DESC is a description of the key to be
displayed if the agent needs to ask for the PIN. DIGEST and
DIGESTLEN is the hash value to sign and DIGESTALGO the algorithm id
used to compute the digest. If CACHE_NONCE is used the agent is
advised to first try a passphrase associated with that nonce. */
gpg_error_t
agent_pksign (ctrl_t ctrl, const char *cache_nonce,
const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
unsigned char *digest, size_t digestlen, int digestalgo,
gcry_sexp_t *r_sigval)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_sigval = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, sizeof line, "SETHASH %d ", digestalgo);
bin2hex (digest, digestlen, line + strlen (line));
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
init_membuf (&data, 1024);
snprintf (line, sizeof line, "PKSIGN%s%s",
cache_nonce? " -- ":"",
cache_nonce? cache_nonce:"");
if (DBG_CLOCK)
log_clock ("enter signing");
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (DBG_CLOCK)
log_clock ("leave signing");
if (err)
xfree (get_membuf (&data, NULL));
else
{
unsigned char *buf;
size_t len;
buf = get_membuf (&data, &len);
if (!buf)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_sscan (r_sigval, NULL, buf, len);
xfree (buf);
}
}
return err;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the END. */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->dflt->ctx,
parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
rc = default_inq_cb (parm->dflt, line);
return rc;
}
/* Check whether there is any padding info from the agent. */
static gpg_error_t
padding_info_cb (void *opaque, const char *line)
{
int *r_padding = opaque;
const char *s;
if ((s=has_leading_keyword (line, "PADDING")))
{
*r_padding = atoi (s);
}
return 0;
}
/* Call the agent to do a decrypt operation using the key identified
by the hex string KEYGRIP and the input data S_CIPHERTEXT. On the
success the decoded value is stored verbatim at R_BUF and its
length at R_BUF; the callers needs to release it. KEYID, MAINKEYID
and PUBKEY_ALGO are used to construct additional promots or status
messages. The padding information is stored at R_PADDING with -1
for not known. */
gpg_error_t
agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
u32 *keyid, u32 *mainkeyid, int pubkey_algo,
gcry_sexp_t s_ciphertext,
unsigned char **r_buf, size_t *r_buflen, int *r_padding)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
membuf_t data;
size_t n, len;
char *p, *buf, *endp;
const char *keygrip2 = NULL;
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
if (!keygrip || !s_ciphertext || !r_buf || !r_buflen || !r_padding)
return gpg_error (GPG_ERR_INV_VALUE);
*r_buf = NULL;
*r_padding = -1;
/* Parse the keygrip in case of a dual algo. */
keygrip2 = strchr (keygrip, ',');
if (!keygrip2)
keygrip2 = keygrip + strlen (keygrip);
if (keygrip2 - keygrip != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (*keygrip2)
{
keygrip2++;
if (strlen (keygrip2) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
}
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
err = assuan_transact (agent_ctx, "RESET",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
snprintf (line, sizeof line, "SETKEY %.40s", keygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
if (*keygrip2)
{
snprintf (line, sizeof line, "SETKEY --another %.40s", keygrip2);
err = assuan_transact (agent_ctx, line, NULL, NULL,NULL,NULL,NULL,NULL);
if (err)
return err;
}
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
init_membuf_secure (&data, 1024);
{
struct cipher_parm_s parm;
parm.dflt = &dfltparm;
parm.ctx = agent_ctx;
err = make_canon_sexp (s_ciphertext, &parm.ciphertext, &parm.ciphertextlen);
if (err)
return err;
err = assuan_transact (agent_ctx,
*keygrip2? "PKDECRYPT --kem=PQC-PGP":"PKDECRYPT",
put_membuf_cb, &data,
inq_ciphertext_cb, &parm,
padding_info_cb, r_padding);
xfree (parm.ciphertext);
}
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
if (len == 0 || *buf != '(')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
if (len < 12 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)" */
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
while (buf[len-1] == 0)
len--;
if (buf[len-1] != ')')
return gpg_error (GPG_ERR_INV_SEXP);
len--; /* Drop the final close-paren. */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
len -= 8; /* Count only the data of the second part. */
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
endp++;
if (endp-p+n > len)
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
}
memmove (buf, endp, n);
*r_buflen = n;
*r_buf = buf;
return 0;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be used for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
agent_keywrap_key (ctrl_t ctrl, int forexport, void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
*r_kek = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
err = assuan_send_data (parm->dflt->ctx, parm->key, parm->keylen);
}
else
err = default_inq_cb (parm->dflt, line);
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
agent_import_key (ctrl_t ctrl, const char *desc, char **cache_nonce_addr,
const void *key, size_t keylen, int unattended, int force,
u32 *keyid, u32 *mainkeyid, int pubkey_algo, u32 timestamp)
{
gpg_error_t err;
struct import_key_parm_s parm;
struct cache_nonce_parm_s cn_parm;
char timestamparg[16 + 16]; /* The 2nd 16 is sizeof(gnupg_isotime_t) */
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
/* Do not use our cache of secret keygrips anymore - this command
* would otherwise requiring to update that cache. */
if (ctrl && ctrl->secret_keygrips)
{
xfree (ctrl->secret_keygrips);
ctrl->secret_keygrips = 0;
}
if (timestamp)
{
strcpy (timestamparg, " --timestamp=");
epoch2isotime (timestamparg+13, timestamp);
}
else
*timestamparg = 0;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
parm.dflt = &dfltparm;
parm.key = key;
parm.keylen = keylen;
snprintf (line, sizeof line, "IMPORT_KEY%s%s%s%s%s",
*timestamparg? timestamparg : "",
unattended? " --unattended":"",
force? " --force":"",
cache_nonce_addr && *cache_nonce_addr? " ":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"");
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
NULL, NULL,
inq_import_key_parms, &parm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Receive a secret key from the agent. HEXKEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). if OPENPGP_PROTECTED
is not zero, ensure that the key material is returned in RFC
4880-compatible passphrased-protected form; if instead MODE1003 is
not zero the raw gpg-agent private key format is requested (either
protected or unprotected). If CACHE_NONCE_ADDR is not NULL the
agent is advised to first try a passphrase associated with that
nonce. On success the key is stored as a canonical S-expression at
R_RESULT and R_RESULTLEN. */
gpg_error_t
agent_export_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int openpgp_protected, int mode1003, char **cache_nonce_addr,
unsigned char **r_result, size_t *r_resultlen,
u32 *keyid, u32 *mainkeyid, int pubkey_algo)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.keyinfo.keyid = keyid;
dfltparm.keyinfo.mainkeyid = mainkeyid;
dfltparm.keyinfo.pubkey_algo = pubkey_algo;
*r_result = NULL;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
/* Check that the gpg-agent supports the --mode1003 option. */
if (mode1003 && assuan_transact (agent_ctx,
"GETINFO cmd_has_option EXPORT_KEY mode1003",
NULL, NULL, NULL, NULL, NULL, NULL))
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s%s%s %s",
mode1003? "--mode1003" : openpgp_protected ? "--openpgp ":"",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
init_membuf_secure (&data, 1024);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = NULL;
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
/* Status callback for handling confirmation. */
static gpg_error_t
confirm_status_cb (void *opaque, const char *line)
{
struct confirm_parm_s *parm = opaque;
const char *s;
if ((s = has_leading_keyword (line, "SETDESC")))
{
xfree (parm->desc);
parm->desc = unescape_status_string (s);
}
else if ((s = has_leading_keyword (line, "SETOK")))
{
xfree (parm->ok);
parm->ok = unescape_status_string (s);
}
else if ((s = has_leading_keyword (line, "SETNOTOK")))
{
xfree (parm->notok);
parm->notok = unescape_status_string (s);
}
return 0;
}
/* Ask the agent to delete the key identified by HEXKEYGRIP. If DESC
is not NULL, display DESC instead of the default description
message. If FORCE is true the agent is advised not to ask for
confirmation. */
gpg_error_t
agent_delete_key (ctrl_t ctrl, const char *hexkeygrip, const char *desc,
int force)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
struct confirm_parm_s confirm_parm;
memset (&confirm_parm, 0, sizeof confirm_parm);
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
dfltparm.confirm = &confirm_parm;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
/* FIXME: Shall we add support to DELETE_KEY for dual keys? */
snprintf (line, DIM(line), "DELETE_KEY%s %s",
force? " --force":"", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
confirm_status_cb, &confirm_parm);
xfree (confirm_parm.desc);
xfree (confirm_parm.ok);
xfree (confirm_parm.notok);
return err;
}
/* Ask the agent to change the passphrase of the key identified by
* HEXKEYGRIP. If DESC is not NULL, display DESC instead of the
* default description message. If CACHE_NONCE_ADDR is not NULL the
* agent is advised to first try a passphrase associated with that
* nonce. If PASSWD_NONCE_ADDR is not NULL the agent will try to use
* the passphrase associated with that nonce for the new passphrase.
* If VERIFY is true the passphrase is only verified. */
gpg_error_t
agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc, int verify,
char **cache_nonce_addr, char **passwd_nonce_addr)
{
gpg_error_t err;
struct cache_nonce_parm_s cn_parm;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s dfltparm;
memset (&dfltparm, 0, sizeof dfltparm);
dfltparm.ctrl = ctrl;
err = start_agent (ctrl, 0);
if (err)
return err;
dfltparm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
if (verify)
snprintf (line, DIM(line), "PASSWD %s%s --verify %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
hexkeygrip);
else
snprintf (line, DIM(line), "PASSWD %s%s %s%s %s",
cache_nonce_addr && *cache_nonce_addr? "--cache-nonce=":"",
cache_nonce_addr && *cache_nonce_addr? *cache_nonce_addr:"",
passwd_nonce_addr && *passwd_nonce_addr? "--passwd-nonce=":"",
passwd_nonce_addr && *passwd_nonce_addr? *passwd_nonce_addr:"",
hexkeygrip);
cn_parm.cache_nonce_addr = cache_nonce_addr;
cn_parm.passwd_nonce_addr = passwd_nonce_addr;
err = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &dfltparm,
cache_nonce_status_cb, &cn_parm);
return err;
}
/* Enable or disable the ephemeral mode. In ephemeral mode keys are
* created,searched and used in a per-session key store and not in the
* on-disk file. Set ENABLE to 1 to enable this mode, to 0 to disable
* this mode and to -1 to only query the current mode. If R_PREVIOUS
* is given the previously used state of the ephemeral mode is stored
* at that address. */
gpg_error_t
agent_set_ephemeral_mode (ctrl_t ctrl, int enable, int *r_previous)
{
gpg_error_t err;
err = start_agent (ctrl, 0);
if (err)
goto leave;
if (r_previous)
{
err = assuan_transact (agent_ctx, "GETINFO ephemeral",
NULL, NULL, NULL, NULL, NULL, NULL);
if (!err)
*r_previous = 1;
else if (gpg_err_code (err) == GPG_ERR_FALSE)
*r_previous = 0;
else
goto leave;
}
/* Skip setting if we are only querying or if the mode is already set. */
if (enable == -1 || (r_previous && !!*r_previous == !!enable))
err = 0;
else
err = assuan_transact (agent_ctx,
enable? "OPTION ephemeral=1" : "OPTION ephemeral=0",
NULL, NULL, NULL, NULL, NULL, NULL);
leave:
return err;
}
/* Return the version reported by gpg-agent. */
gpg_error_t
agent_get_version (ctrl_t ctrl, char **r_version)
{
gpg_error_t err;
err = start_agent (ctrl, 0);
if (err)
return err;
err = get_assuan_server_version (agent_ctx, 0, r_version);
return err;
}
diff --git a/g10/call-dirmngr.c b/g10/call-dirmngr.c
index d00f61450..ed2cf9dc2 100644
--- a/g10/call-dirmngr.c
+++ b/g10/call-dirmngr.c
@@ -1,1304 +1,1304 @@
/* call-dirmngr.c - GPG operations to the Dirmngr.
* Copyright (C) 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include "gpg.h"
#include <assuan.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/status.h"
#include "keyserver-internal.h"
#include "call-dirmngr.h"
/* Keys retrieved from the web key directory should be small. There
* is only one UID and we can expect that the number of subkeys is
* reasonable. So we set a generous limit of 256 KiB. */
#define MAX_WKD_RESULT_LENGTH (256 * 1024)
/* Parameter structure used to gather status info. Note that it is
* also used for WKD requests. */
struct ks_status_parm_s
{
const char *keyword; /* Look for this keyword or NULL for "SOURCE". */
char *source;
};
/* Parameter structure used with the KS_SEARCH command. */
struct ks_search_parm_s
{
gpg_error_t lasterr; /* Last error code. */
membuf_t saveddata; /* Buffer to build complete lines. */
char *helpbuf; /* NULL or malloced buffer. */
size_t helpbufsize; /* Allocated size of HELPBUF. */
gpg_error_t (*data_cb)(void*, int, char*); /* Callback. */
void *data_cb_value; /* First argument for DATA_CB. */
struct ks_status_parm_s *stparm; /* Link to the status parameter. */
};
/* Parameter structure used with the KS_GET command. */
struct ks_get_parm_s
{
estream_t memfp;
};
/* Parameter structure used with the KS_PUT command. */
struct ks_put_parm_s
{
assuan_context_t ctx;
kbnode_t keyblock; /* The optional keyblock. */
const void *data; /* The key in OpenPGP binary format. */
size_t datalen; /* The length of DATA. */
};
/* Parameter structure used with the DNS_CERT command. */
struct dns_cert_parm_s
{
estream_t memfp;
unsigned char *fpr;
size_t fprlen;
char *url;
};
/* Data used to associate an session with dirmngr contexts. We can't
use a simple one to one mapping because we sometimes need two
connections to the dirmngr; for example while doing a listing and
being in a data callback we may want to retrieve a key. The local
dirmngr data takes care of this. At the end of the session the
function dirmngr_deinit_session_data is called by gpg.c to cleanup
these resources. Note that gpg.h defines a typedef dirmngr_local_t
for this structure. */
struct dirmngr_local_s
{
/* Link to other contexts which are used simultaneously. */
struct dirmngr_local_s *next;
/* The active Assuan context. */
assuan_context_t ctx;
/* Flag set when the keyserver names have been send. */
int set_keyservers_done;
/* Flag set to true while an operation is running on CTX. */
int is_active;
};
/* Deinitialize all session data of dirmngr pertaining to CTRL. */
void
gpg_dirmngr_deinit_session_data (ctrl_t ctrl)
{
dirmngr_local_t dml;
while ((dml = ctrl->dirmngr_local))
{
ctrl->dirmngr_local = dml->next;
if (dml->is_active)
log_error ("oops: trying to cleanup an active dirmngr context\n");
else
assuan_release (dml->ctx);
xfree (dml);
}
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername)
{
return warn_server_version_mismatch (ctx, servername, 0,
write_status_strings2, NULL,
!opt.quiet);
}
/* Try to connect to the Dirmngr via a socket or spawn it if possible.
Handle the server's initial greeting and set global options. */
static gpg_error_t
create_context (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
*r_ctx = NULL;
if (opt.disable_dirmngr)
return gpg_error (GPG_ERR_NO_DIRMNGR);
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.dirmngr_program,
opt.autostart?ASSHELP_FLAG_AUTOSTART:0,
opt.verbose, DBG_IPC,
NULL /*gpg_status2*/, ctrl);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no dirmngr running in this session\n"));
}
}
else if (!err && !(err = warn_version_mismatch (ctx, DIRMNGR_NAME)))
{
char *line;
/* Tell the dirmngr that we want to collect audit event. */
/* err = assuan_transact (agent_ctx, "OPTION audit-events=1", */
/* NULL, NULL, NULL, NULL, NULL, NULL); */
if (opt.keyserver_options.http_proxy)
{
line = xtryasprintf ("OPTION http-proxy=%s",
opt.keyserver_options.http_proxy);
if (!line)
err = gpg_error_from_syserror ();
else
{
err = assuan_transact (ctx, line, NULL, NULL, NULL,
NULL, NULL, NULL);
xfree (line);
}
}
if (err)
;
else if ((opt.keyserver_options.options & KEYSERVER_HONOR_KEYSERVER_URL))
{
/* Tell the dirmngr that this possibly privacy invading
option is in use. If Dirmngr is running in Tor mode, it
will return an error. */
err = assuan_transact (ctx, "OPTION honor-keyserver-url-used",
NULL, NULL, NULL, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_FORBIDDEN)
log_error (_("keyserver option \"honor-keyserver-url\""
" may not be used in Tor mode\n"));
else if (gpg_err_code (err) == GPG_ERR_UNKNOWN_OPTION)
err = 0; /* Old dirmngr versions do not support this option. */
}
}
if (err)
assuan_release (ctx);
else
{
/* audit_log_ok (ctrl->audit, AUDIT_DIRMNGR_READY, err); */
*r_ctx = ctx;
}
return err;
}
/* Get a context for accessing dirmngr. If no context is available a
new one is created and - if required - dirmngr started. On success
an assuan context is stored at R_CTX. This context may only be
released by means of close_context. Note that NULL is stored at
R_CTX on error. */
static gpg_error_t
open_context (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
dirmngr_local_t dml;
*r_ctx = NULL;
for (;;)
{
for (dml = ctrl->dirmngr_local; dml && dml->is_active; dml = dml->next)
;
if (dml)
{
/* Found an inactive local session - return that. */
log_assert (!dml->is_active);
/* But first do the per session init if not yet done. */
if (!dml->set_keyservers_done)
{
keyserver_spec_t ksi;
/* Set all configured keyservers. We clear existing
keyservers so that any keyserver configured in GPG
overrides keyservers possibly still configured in Dirmngr
for the session (Note that the keyserver list of a
session in Dirmngr survives a RESET. */
for (ksi = opt.keyserver; ksi; ksi = ksi->next)
{
char *line;
line = xtryasprintf
("KEYSERVER%s %s",
ksi == opt.keyserver? " --clear":"", ksi->uri);
if (!line)
err = gpg_error_from_syserror ();
else
{
err = assuan_transact (dml->ctx, line, NULL, NULL, NULL,
NULL, NULL, NULL);
xfree (line);
}
if (err)
return err;
}
dml->set_keyservers_done = 1;
}
dml->is_active = 1;
*r_ctx = dml->ctx;
return 0;
}
dml = xtrycalloc (1, sizeof *dml);
if (!dml)
return gpg_error_from_syserror ();
err = create_context (ctrl, &dml->ctx);
if (err)
{
xfree (dml);
return err;
}
/* To be on the nPth thread safe site we need to add it to a
list; this is far easier than to have a lock for this
function. It should not happen anyway but the code is free
because we need it for the is_active check above. */
dml->next = ctrl->dirmngr_local;
ctrl->dirmngr_local = dml;
}
}
/* Close the assuan context CTX or return it to a pool of unused
contexts. If CTX is NULL, the function does nothing. */
static void
close_context (ctrl_t ctrl, assuan_context_t ctx)
{
dirmngr_local_t dml;
if (!ctx)
return;
for (dml = ctrl->dirmngr_local; dml; dml = dml->next)
{
if (dml->ctx == ctx)
{
if (!dml->is_active)
log_fatal ("closing inactive dirmngr context %p\n", ctx);
dml->is_active = 0;
return;
}
}
log_fatal ("closing unknown dirmngr ctx %p\n", ctx);
}
/* Clear the set_keyservers_done flag on context CTX. */
static void
clear_context_flags (ctrl_t ctrl, assuan_context_t ctx)
{
dirmngr_local_t dml;
if (!ctx)
return;
for (dml = ctrl->dirmngr_local; dml; dml = dml->next)
{
if (dml->ctx == ctx)
{
if (!dml->is_active)
log_fatal ("clear_context_flags on inactive dirmngr ctx %p\n", ctx);
dml->set_keyservers_done = 0;
return;
}
}
log_fatal ("clear_context_flags on unknown dirmngr ctx %p\n", ctx);
}
/* Status callback for ks_list, ks_get, ks_search, and wkd_get */
static gpg_error_t
ks_status_cb (void *opaque, const char *line)
{
struct ks_status_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *s, *s2;
const char *warn = NULL;
int is_note = 0;
char *p;
if ((s = has_leading_keyword (line, parm->keyword? parm->keyword : "SOURCE")))
{
/* Note that the arg for "S SOURCE" is the URL of a keyserver. */
if (!parm->source)
{
parm->source = xtrystrdup (s);
if (!parm->source)
err = gpg_error_from_syserror ();
else
{
p = strchr (parm->source, ':');
if (p && p[1] == '/' && p[2] == '/')
{
/* This is a real URL like "ldap://foo:389/bla,bla"
* Strip off the local part. */
if ((p = strchr (p+3, '/')))
*p = 0;
}
else
{
/* This is an LDAP config entry like
* "foo:389:user:pass:base:flags"
- * we strip off everything beyound the port. */
+ * we strip off everything beyond the port. */
if ((p = strchr (p+1, ':')))
{
if (p[-1] == ':')
p[-1] = 0; /* No port given. */
else
*p = 0;
}
}
}
}
}
else if ((s = has_leading_keyword (line, "WARNING"))
|| (is_note = !!(s = has_leading_keyword (line, "NOTE"))))
{
if ((s2 = has_leading_keyword (s, "wkd_cached_result")))
{
if (opt.verbose)
warn = _("WKD uses a cached result");
}
else if ((s2 = has_leading_keyword (s, "tor_not_running")))
warn = _("Tor is not running");
else if ((s2 = has_leading_keyword (s, "tor_config_problem")))
warn = _("Tor is not properly configured");
else if ((s2 = has_leading_keyword (s, "dns_config_problem")))
warn = _("DNS is not properly configured");
else if ((s2 = has_leading_keyword (s, "http_redirect")))
warn = _("unacceptable HTTP redirect from server");
else if ((s2 = has_leading_keyword (s, "http_redirect_cleanup")))
warn = _("unacceptable HTTP redirect from server was cleaned up");
else if ((s2 = has_leading_keyword (s, "tls_cert_error")))
warn = _("server uses an invalid certificate");
else
warn = NULL;
if (warn)
{
if (is_note)
log_info (_("Note: %s\n"), warn);
else
log_info (_("WARNING: %s\n"), warn);
if (s2)
{
while (*s2 && !spacep (s2))
s2++;
while (*s2 && spacep (s2))
s2++;
if (*s2)
print_further_info ("%s", s2);
}
}
}
return err;
}
/* Run the "KEYSERVER" command to return the name of the used
keyserver at R_KEYSERVER. */
gpg_error_t
gpg_dirmngr_ks_list (ctrl_t ctrl, char **r_keyserver)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_status_parm_s stparm;
memset (&stparm, 0, sizeof stparm);
stparm.keyword = "KEYSERVER";
if (r_keyserver)
*r_keyserver = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
err = assuan_transact (ctx, "KEYSERVER", NULL, NULL,
NULL, NULL, ks_status_cb, &stparm);
if (err)
goto leave;
if (!stparm.source)
{
err = gpg_error (GPG_ERR_NO_KEYSERVER);
goto leave;
}
if (r_keyserver)
*r_keyserver = stparm.source;
else
xfree (stparm.source);
stparm.source = NULL;
leave:
xfree (stparm.source);
close_context (ctrl, ctx);
return err;
}
/* Data callback for the KS_SEARCH command. */
static gpg_error_t
ks_search_data_cb (void *opaque, const void *data, size_t datalen)
{
gpg_error_t err = 0;
struct ks_search_parm_s *parm = opaque;
const char *line, *s;
size_t rawlen, linelen;
char fixedbuf[256];
if (parm->lasterr)
return 0;
if (parm->stparm->source)
{
err = parm->data_cb (parm->data_cb_value, 1, parm->stparm->source);
if (err)
{
parm->lasterr = err;
return err;
}
/* Clear it so that we won't get back here unless the server
accidentally sends a second source status line. Note that
will not see all accidentally sent source lines because it
depends on whether data lines have been send in between. */
xfree (parm->stparm->source);
parm->stparm->source = NULL;
}
if (!data)
return 0; /* Ignore END commands. */
put_membuf (&parm->saveddata, data, datalen);
again:
line = peek_membuf (&parm->saveddata, &rawlen);
if (!line)
{
parm->lasterr = gpg_error_from_syserror ();
return parm->lasterr; /* Tell the server about our problem. */
}
if ((s = memchr (line, '\n', rawlen)))
{
linelen = s - line; /* That is the length excluding the LF. */
if (linelen + 1 < sizeof fixedbuf)
{
/* We can use the static buffer. */
memcpy (fixedbuf, line, linelen);
fixedbuf[linelen] = 0;
if (linelen && fixedbuf[linelen-1] == '\r')
fixedbuf[linelen-1] = 0;
err = parm->data_cb (parm->data_cb_value, 0, fixedbuf);
}
else
{
if (linelen + 1 >= parm->helpbufsize)
{
xfree (parm->helpbuf);
parm->helpbufsize = linelen + 1 + 1024;
parm->helpbuf = xtrymalloc (parm->helpbufsize);
if (!parm->helpbuf)
{
parm->lasterr = gpg_error_from_syserror ();
return parm->lasterr;
}
}
memcpy (parm->helpbuf, line, linelen);
parm->helpbuf[linelen] = 0;
if (linelen && parm->helpbuf[linelen-1] == '\r')
parm->helpbuf[linelen-1] = 0;
err = parm->data_cb (parm->data_cb_value, 0, parm->helpbuf);
}
if (err)
parm->lasterr = err;
else
{
clear_membuf (&parm->saveddata, linelen+1);
goto again; /* There might be another complete line. */
}
}
return err;
}
/* Run the KS_SEARCH command using the search string SEARCHSTR. All
data lines are passed to the CB function. That function is called
with CB_VALUE as its first argument, a 0 as second argument, and
the decoded data line as third argument. The callback function may
modify the data line and it is guaranteed that this data line is a
complete line with a terminating 0 character but without the
linefeed. NULL is passed to the callback to indicate EOF. */
gpg_error_t
gpg_dirmngr_ks_search (ctrl_t ctrl, const char *searchstr,
gpg_error_t (*cb)(void*, int, char *), void *cb_value)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_status_parm_s stparm;
struct ks_search_parm_s parm;
char line[ASSUAN_LINELENGTH];
err = open_context (ctrl, &ctx);
if (err)
return err;
{
char *escsearchstr = percent_plus_escape (searchstr);
if (!escsearchstr)
{
err = gpg_error_from_syserror ();
close_context (ctrl, ctx);
return err;
}
snprintf (line, sizeof line, "KS_SEARCH -- %s", escsearchstr);
xfree (escsearchstr);
}
memset (&stparm, 0, sizeof stparm);
memset (&parm, 0, sizeof parm);
init_membuf (&parm.saveddata, 1024);
parm.data_cb = cb;
parm.data_cb_value = cb_value;
parm.stparm = &stparm;
err = assuan_transact (ctx, line, ks_search_data_cb, &parm,
NULL, NULL, ks_status_cb, &stparm);
if (!err)
err = cb (cb_value, 0, NULL); /* Send EOF. */
else if (parm.stparm->source)
{
/* Error but we received a SOURCE status. Tell via callback but
* ignore errors. */
parm.data_cb (parm.data_cb_value, 1, parm.stparm->source);
}
xfree (get_membuf (&parm.saveddata, NULL));
xfree (parm.helpbuf);
xfree (stparm.source);
close_context (ctrl, ctx);
return err;
}
/* Data callback for the KS_GET and KS_FETCH commands. */
static gpg_error_t
ks_get_data_cb (void *opaque, const void *data, size_t datalen)
{
gpg_error_t err = 0;
struct ks_get_parm_s *parm = opaque;
size_t nwritten;
if (!data)
return 0; /* Ignore END commands. */
if (es_write (parm->memfp, data, datalen, &nwritten))
err = gpg_error_from_syserror ();
return err;
}
/* Run the KS_GET command using the patterns in the array PATTERN. On
success an estream object is returned to retrieve the keys. On
error an error code is returned and NULL stored at R_FP.
The pattern may only use search specification which a keyserver can
use to retrieve keys. Because we know the format of the pattern we
don't need to escape the patterns before sending them to the
server.
Bit values for FLAGS are:
- KEYSERVER_IMPORT_FLAG_QUICK :: dirmngr shall use a shorter timeout.
- KEYSERVER_IMPORT_FLAG_LDAP :: dirmngr shall only use LDAP or NTDS.
If R_SOURCE is not NULL the source of the data is stored as a
malloced string there. If a source is not known NULL is stored.
Note that this may even be returned after an error.
If there are too many patterns the function returns an error. That
could be fixed by issuing several search commands or by
implementing a different interface. However with long keyids we
are able to ask for (1000-10-1)/(2+8+1) = 90 keys at once. */
gpg_error_t
gpg_dirmngr_ks_get (ctrl_t ctrl, char **pattern,
keyserver_spec_t override_keyserver,
unsigned int flags,
estream_t *r_fp, char **r_source)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_status_parm_s stparm;
struct ks_get_parm_s parm;
char *line = NULL;
size_t linelen;
membuf_t mb;
int idx;
memset (&stparm, 0, sizeof stparm);
memset (&parm, 0, sizeof parm);
*r_fp = NULL;
if (r_source)
*r_source = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
/* If we have an override keyserver we first indicate that the next
user of the context needs to again setup the global keyservers and
then we send the override keyserver. */
if (override_keyserver)
{
clear_context_flags (ctrl, ctx);
line = xtryasprintf ("KEYSERVER --clear %s", override_keyserver->uri);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, NULL, NULL, NULL,
NULL, NULL, NULL);
if (err)
goto leave;
xfree (line);
line = NULL;
}
/* Lump all patterns into one string. */
init_membuf (&mb, 1024);
put_membuf_str (&mb, "KS_GET");
if ((flags & KEYSERVER_IMPORT_FLAG_QUICK))
put_membuf_str (&mb, " --quick");
if ((flags & KEYSERVER_IMPORT_FLAG_LDAP))
put_membuf_str (&mb, " --ldap");
put_membuf_str (&mb, " --");
for (idx=0; pattern[idx]; idx++)
{
put_membuf (&mb, " ", 1); /* Append Delimiter. */
put_membuf_str (&mb, pattern[idx]);
}
put_membuf (&mb, "", 1); /* Append Nul. */
line = get_membuf (&mb, &linelen);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (linelen + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_MANY);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, ks_get_data_cb, &parm,
NULL, NULL, ks_status_cb, &stparm);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_fp = parm.memfp;
parm.memfp = NULL;
leave:
if (r_source && stparm.source)
{
*r_source = stparm.source;
stparm.source = NULL;
}
es_fclose (parm.memfp);
xfree (stparm.source);
xfree (line);
close_context (ctrl, ctx);
return err;
}
/* Run the KS_FETCH and pass URL as argument. On success an estream
object is returned to retrieve the keys. On error an error code is
returned and NULL stored at R_FP.
The url is expected to point to a small set of keys; in many cases
only to one key. However, schemes like finger may return several
keys. Note that the configured keyservers are ignored by the
KS_FETCH command. */
gpg_error_t
gpg_dirmngr_ks_fetch (ctrl_t ctrl, const char *url, estream_t *r_fp)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_get_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
*r_fp = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
line = strconcat ("KS_FETCH -- ", url, NULL);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, ks_get_data_cb, &parm,
NULL, NULL, NULL, NULL);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_fp = parm.memfp;
parm.memfp = NULL;
leave:
es_fclose (parm.memfp);
xfree (line);
close_context (ctrl, ctx);
return err;
}
static void
record_output (estream_t output,
pkttype_t type,
const char *validity,
int pub_key_length, /* The public key length or -1. */
int pub_key_algo, /* The public key algo or -1. */
const u32 *keyid, /* 2 ulongs or NULL. */
u32 creation_date, /* The creation date or 0. */
u32 expiration_date, /* The expiration date or 0. */
const char *userid) /* The userid or NULL. */
{
const char *type_str = NULL;
switch (type)
{
case PKT_PUBLIC_KEY:
type_str = "pub";
break;
case PKT_PUBLIC_SUBKEY:
type_str = "sub";
break;
case PKT_USER_ID:
type_str = "uid";
break;
case PKT_SIGNATURE:
type_str = "sig";
break;
default:
log_assert (! "Unhandled type.");
}
es_fprintf (output, "%s:%s:",
type_str,
validity ? validity : "");
if (pub_key_length > 0)
es_fprintf (output, "%d", pub_key_length);
es_fputc (':', output);
if (pub_key_algo != -1)
es_fprintf (output, "%d", pub_key_algo);
es_fputc (':', output);
if (keyid)
es_fprintf (output, "%08lX%08lX", (ulong) keyid[0], (ulong) keyid[1]);
es_fprintf (output, ":%s:", colon_strtime (creation_date));
es_fprintf (output, "%s:::", colon_strtime (expiration_date));
if (userid)
es_write_sanitized (output, userid, strlen (userid), ":", NULL);
else
es_fputc (':', output);
es_fputs (":::::::::\n", output);
}
/* Handle the KS_PUT inquiries. */
static gpg_error_t
ks_put_inq_cb (void *opaque, const char *line)
{
struct ks_put_parm_s *parm = opaque;
gpg_error_t err = 0;
if (has_leading_keyword (line, "KEYBLOCK"))
{
if (parm->data)
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else if (has_leading_keyword (line, "KEYBLOCK_INFO"))
{
kbnode_t node;
estream_t fp;
char hexfpr[2*MAX_FINGERPRINT_LEN+1];
/* Parse the keyblock and send info lines back to the server. */
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
err = gpg_error_from_syserror ();
/* Note: the output format for the INFO block follows the colon
format as described in doc/DETAILS. We don't actually reuse
the functionality from g10/keylist.c to produce the output,
because we don't need all of it and some of it is quite
expensive to generate.
The fields are (the starred fields are the ones we need):
* Field 1 - Type of record
* Field 2 - Validity
* Field 3 - Key length
* Field 4 - Public key algorithm
* Field 5 - KeyID
* Field 6 - Creation date
* Field 7 - Expiration date
Field 8 - Certificate S/N, UID hash, trust signature info
Field 9 - Ownertrust
* Field 10 - User-ID
Field 11 - Signature class
Field 12 - Key capabilities
Field 13 - Issuer certificate fingerprint or other info
Field 14 - Flag field
Field 15 - S/N of a token
Field 16 - Hash algorithm
Field 17 - Curve name
*/
for (node = parm->keyblock; !err && node; node=node->next)
{
switch (node->pkt->pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
{
PKT_public_key *pk = node->pkt->pkt.public_key;
char validity[3];
int i;
i = 0;
if (pk->flags.revoked)
validity[i ++] = 'r';
if (pk->has_expired)
validity[i ++] = 'e';
validity[i] = '\0';
keyid_from_pk (pk, NULL);
record_output (fp, node->pkt->pkttype, validity,
nbits_from_pk (pk), pk->pubkey_algo,
pk->keyid, pk->timestamp, pk->expiredate,
NULL);
es_fprintf (fp, "fpr:::::::::%s:\n",
hexfingerprint (pk, hexfpr, sizeof hexfpr));
}
break;
case PKT_USER_ID:
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (!uid->attrib_data)
{
char validity[3];
int i;
i = 0;
if (uid->flags.revoked)
validity[i ++] = 'r';
if (uid->flags.expired)
validity[i ++] = 'e';
validity[i] = '\0';
record_output (fp, node->pkt->pkttype, validity,
-1, -1, NULL,
uid->created, uid->expiredate,
uid->name);
}
}
break;
default:
continue;
}
/* Given that the last operation was an es_fprintf we should
get the correct ERRNO if ferror indicates an error. */
if (es_ferror (fp))
err = gpg_error_from_syserror ();
}
/* Without an error and if we have an keyblock at all, send the
data back. */
if (!err && parm->keyblock)
{
int rc;
char buffer[512];
size_t nread;
es_rewind (fp);
while (!(rc=es_read (fp, buffer, sizeof buffer, &nread)) && nread)
{
err = assuan_send_data (parm->ctx, buffer, nread);
if (err)
break;
}
if (!err && rc)
err = gpg_error_from_syserror ();
}
es_fclose (fp);
}
else
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/* Send a key to the configured server. {DATA,DATLEN} contains the
key in OpenPGP binary transport format. If KEYBLOCK is not NULL it
has the internal representation of that key; this is for example
used to convey meta data to LDAP keyservers. */
gpg_error_t
gpg_dirmngr_ks_put (ctrl_t ctrl, void *data, size_t datalen, kbnode_t keyblock)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_put_parm_s parm;
memset (&parm, 0, sizeof parm);
/* We are going to parse the keyblock, thus we better make sure the
all information is readily available. */
if (keyblock)
merge_keys_and_selfsig (ctrl, keyblock);
err = open_context (ctrl, &ctx);
if (err)
return err;
parm.ctx = ctx;
parm.keyblock = keyblock;
parm.data = data;
parm.datalen = datalen;
err = assuan_transact (ctx, "KS_PUT", NULL, NULL,
ks_put_inq_cb, &parm, NULL, NULL);
close_context (ctrl, ctx);
return err;
}
/* Data callback for the DNS_CERT and WKD_GET commands. */
static gpg_error_t
dns_cert_data_cb (void *opaque, const void *data, size_t datalen)
{
struct dns_cert_parm_s *parm = opaque;
gpg_error_t err = 0;
size_t nwritten;
if (!data)
return 0; /* Ignore END commands. */
if (!parm->memfp)
return 0; /* Data is not required. */
if (es_write (parm->memfp, data, datalen, &nwritten))
err = gpg_error_from_syserror ();
return err;
}
/* Status callback for the DNS_CERT command. */
static gpg_error_t
dns_cert_status_cb (void *opaque, const char *line)
{
struct dns_cert_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *s;
size_t nbytes;
if ((s = has_leading_keyword (line, "FPR")))
{
char *buf;
if (!(buf = xtrystrdup (s)))
err = gpg_error_from_syserror ();
else if (parm->fpr)
err = gpg_error (GPG_ERR_DUP_KEY);
else if (!hex2str (buf, buf, strlen (buf)+1, &nbytes))
err = gpg_error_from_syserror ();
else if (nbytes < 20)
err = gpg_error (GPG_ERR_TOO_SHORT);
else
{
parm->fpr = xtrymalloc (nbytes);
if (!parm->fpr)
err = gpg_error_from_syserror ();
else
memcpy (parm->fpr, buf, (parm->fprlen = nbytes));
}
xfree (buf);
}
else if ((s = has_leading_keyword (line, "URL")) && *s)
{
if (parm->url)
err = gpg_error (GPG_ERR_DUP_KEY);
else if (!(parm->url = xtrystrdup (s)))
err = gpg_error_from_syserror ();
}
return err;
}
/* Ask the dirmngr for a DNS CERT record. Depending on the found
subtypes different return values are set:
- For a PGP subtype a new estream with that key will be returned at
R_KEY and the other return parameters are set to NULL/0.
- For an IPGP subtype the fingerprint is stored as a malloced block
at (R_FPR,R_FPRLEN). If an URL is available it is stored as a
malloced string at R_URL; NULL is stored if there is no URL.
If CERTTYPE is DNS_CERTTYPE_ANY this function returns the first
CERT record found with a supported type; it is expected that only
one CERT record is used. If CERTTYPE is one of the supported
certtypes, only records with this certtype are considered and the
first one found is returned. All R_* args are optional.
If CERTTYPE is NULL the DANE method is used to fetch the key.
*/
gpg_error_t
gpg_dirmngr_dns_cert (ctrl_t ctrl, const char *name, const char *certtype,
estream_t *r_key,
unsigned char **r_fpr, size_t *r_fprlen,
char **r_url)
{
gpg_error_t err;
assuan_context_t ctx;
struct dns_cert_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
if (r_key)
*r_key = NULL;
if (r_fpr)
*r_fpr = NULL;
if (r_fprlen)
*r_fprlen = 0;
if (r_url)
*r_url = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
line = es_bsprintf ("DNS_CERT %s %s", certtype? certtype : "--dane", name);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
NULL, NULL, dns_cert_status_cb, &parm);
if (err)
goto leave;
if (r_key)
{
es_rewind (parm.memfp);
*r_key = parm.memfp;
parm.memfp = NULL;
}
if (r_fpr && parm.fpr)
{
*r_fpr = parm.fpr;
parm.fpr = NULL;
}
if (r_fprlen)
*r_fprlen = parm.fprlen;
if (r_url && parm.url)
{
*r_url = parm.url;
parm.url = NULL;
}
leave:
xfree (parm.fpr);
xfree (parm.url);
es_fclose (parm.memfp);
xfree (line);
close_context (ctrl, ctx);
return err;
}
/* Ask the dirmngr to retrieve a key via the Web Key Directory
* protocol. If QUICK is set the dirmngr is advised to use a shorter
* timeout. On success a new estream with the key stored at R_KEY and the
* url of the lookup (if any) stored at R_URL. Note that
*/
gpg_error_t
gpg_dirmngr_wkd_get (ctrl_t ctrl, const char *name, int quick,
estream_t *r_key, char **r_url)
{
gpg_error_t err;
assuan_context_t ctx;
struct ks_status_parm_s stparm = { NULL };
struct dns_cert_parm_s parm = { NULL };
char *line = NULL;
if (r_key)
*r_key = NULL;
if (r_url)
*r_url = NULL;
err = open_context (ctrl, &ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET%s -- %s", quick?" --quick":"", name);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (MAX_WKD_RESULT_LENGTH, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, dns_cert_data_cb, &parm,
NULL, NULL, ks_status_cb, &stparm);
if (gpg_err_code (err) == GPG_ERR_ENOSPC)
err = gpg_error (GPG_ERR_TOO_LARGE);
if (err)
goto leave;
if (r_key)
{
es_rewind (parm.memfp);
*r_key = parm.memfp;
parm.memfp = NULL;
}
if (r_url)
{
*r_url = stparm.source;
stparm.source = NULL;
}
leave:
xfree (stparm.source);
xfree (parm.fpr);
xfree (parm.url);
es_fclose (parm.memfp);
xfree (line);
close_context (ctrl, ctx);
return err;
}
diff --git a/g10/call-keyboxd.c b/g10/call-keyboxd.c
index bc3806f0b..378c3d7ef 100644
--- a/g10/call-keyboxd.c
+++ b/g10/call-keyboxd.c
@@ -1,893 +1,893 @@
/* call-keyboxd.c - Access to the keyboxd storage server
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include <npth.h>
#include "gpg.h"
#include <assuan.h>
#include "../common/util.h"
#include "../common/membuf.h"
#include "options.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/host2net.h"
#include "../common/exechelp.h"
#include "../common/status.h"
#include "../kbx/kbx-client-util.h"
#include "keydb.h"
#include "keydb-private.h" /* For struct keydb_handle_s */
/* Data used to keep track of keybox daemon sessions. This allows us
- * to use several sessions with the keyboxd and also to re-use already
+ * to use several sessions with the keyboxd and also to reuse already
* established sessions. Note that gpg.h defines the type
* keyboxd_local_t for this structure. */
struct keyboxd_local_s
{
/* Link to other keyboxd contexts which are used simultaneously. */
struct keyboxd_local_s *next;
/* The active Assuan context. */
assuan_context_t ctx;
/* The client data helper context. */
kbx_client_data_t kcd;
/* I/O buffer with the last search result or NULL. Used if
* D-lines are used to convey the keyblocks. */
iobuf_t search_result;
/* This flag set while an operation is running on this context. */
unsigned int is_active : 1;
/* Flag indicating that a search reset is required. */
unsigned int need_search_reset : 1;
};
/* Flag indicating that for example bulk import is enabled. */
static unsigned int in_transaction;
/* Deinitialize all session resources pertaining to the keyboxd. */
void
gpg_keyboxd_deinit_session_data (ctrl_t ctrl)
{
keyboxd_local_t kbl;
gpg_error_t err;
while ((kbl = ctrl->keyboxd_local))
{
ctrl->keyboxd_local = kbl->next;
if (kbl->is_active)
log_error ("oops: trying to cleanup an active keyboxd context\n");
else
{
if (kbl->ctx && in_transaction)
{
/* This is our hack to commit the changes done during a
* bulk import. If we won't do that the loss of the
* connection would trigger a rollback in keyboxd. Note
* that transactions are not associated with a
* connection. */
err = assuan_transact (kbl->ctx, "TRANSACTION commit",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
log_error ("error committing last transaction: %s\n",
gpg_strerror (err));
in_transaction = 0;
}
assuan_release (kbl->ctx);
kbl->ctx = NULL;
/*
* Since there may be pipe output FD sent to the server (so
* that it can receive data through the pipe), we should
* release the assuan connection before releasing KBL->KCD.
* This way, the data receiving thread can finish cleanly,
* and we can join the thread.
*/
kbx_client_data_release (kbl->kcd);
kbl->kcd = NULL;
}
xfree (kbl);
}
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (assuan_context_t ctx, const char *servername)
{
return warn_server_version_mismatch (ctx, servername, 0,
write_status_strings2, NULL,
!opt.quiet);
}
/* Connect to the keybox daemon and launch it if necessary. Handle
* the server's initial greeting and set global options. Returns a
* new assuan context or an error. */
static gpg_error_t
create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
*r_ctx = NULL;
err = start_new_keyboxd (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.keyboxd_program,
opt.autostart?ASSHELP_FLAG_AUTOSTART:0,
opt.verbose, DBG_IPC,
NULL, ctrl);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_KEYBOXD)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no keyboxd running in this session\n"));
}
}
else if (!err && !(err = warn_version_mismatch (ctx, KEYBOXD_NAME)))
{
/* Place to emit global options. */
if ((opt.import_options & IMPORT_BULK) && !in_transaction)
{
err = assuan_transact (ctx, "TRANSACTION begin",
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
{
log_error ("error enabling bulk import option: %s\n",
gpg_strerror (err));
}
else
in_transaction = 1;
}
}
if (err)
assuan_release (ctx);
else
*r_ctx = ctx;
return err;
}
/* Get a context for accessing keyboxd. If no context is available a
* new one is created and if necessary keyboxd is started. R_KBL
* receives a pointer to the local context object. */
static gpg_error_t
open_context (ctrl_t ctrl, keyboxd_local_t *r_kbl)
{
gpg_error_t err;
keyboxd_local_t kbl;
*r_kbl = NULL;
for (;;)
{
for (kbl = ctrl->keyboxd_local; kbl && kbl->is_active; kbl = kbl->next)
;
if (kbl)
{
/* Found an inactive keyboxd session - return that. */
log_assert (!kbl->is_active);
kbl->is_active = 1;
kbl->need_search_reset = 1;
*r_kbl = kbl;
return 0;
}
/* None found. Create a new session and retry. */
kbl = xtrycalloc (1, sizeof *kbl);
if (!kbl)
return gpg_error_from_syserror ();
err = create_new_context (ctrl, &kbl->ctx);
if (err)
{
xfree (kbl);
return err;
}
err = kbx_client_data_new (&kbl->kcd, kbl->ctx, 0);
if (err)
{
assuan_release (kbl->ctx);
xfree (kbl);
return err;
}
/* For thread-saftey we add it to the list and retry; this is
* easier than to employ a lock. */
kbl->next = ctrl->keyboxd_local;
ctrl->keyboxd_local = kbl;
}
/*NOTREACHED*/
}
/* Create a new database handle. A database handle is similar to a
* file handle: it contains a local file position. This is used when
* searching: subsequent searches resume where the previous search
* left off. To rewind the position, use keydb_search_reset(). This
* function returns NULL on error, sets ERRNO, and prints an error
* diagnostic. Depending on --use-keyboxd either the old internal
* keydb code is used (keydb.c) or, if set, the processing is diverted
* to the keyboxd. */
/* FIXME: We should change the interface to return a gpg_error_t. */
KEYDB_HANDLE
keydb_new (ctrl_t ctrl)
{
gpg_error_t err;
KEYDB_HANDLE hd;
if (DBG_CLOCK)
log_clock ("keydb_new");
hd = xtrycalloc (1, sizeof *hd);
if (!hd)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (!opt.use_keyboxd)
{
err = internal_keydb_init (hd);
goto leave;
}
hd->use_keyboxd = 1;
hd->ctrl = ctrl;
err = open_context (ctrl, &hd->kbl);
leave:
if (err)
{
int rc;
log_error (_("error opening key DB: %s\n"), gpg_strerror (err));
xfree (hd);
hd = NULL;
if (!(rc = gpg_err_code_to_errno (err)))
rc = gpg_err_code_to_errno (GPG_ERR_EIO);
gpg_err_set_errno (rc);
}
return hd;
}
/* Release a keydb handle. */
void
keydb_release (KEYDB_HANDLE hd)
{
keyboxd_local_t kbl;
if (!hd)
return;
if (DBG_CLOCK)
log_clock ("keydb_release");
if (!hd->use_keyboxd)
internal_keydb_deinit (hd);
else
{
kbl = hd->kbl;
if (DBG_CLOCK)
log_clock ("close_context (found)");
if (!kbl->is_active)
log_fatal ("closing inactive keyboxd context %p\n", kbl);
kbl->is_active = 0;
hd->kbl = NULL;
hd->ctrl = NULL;
}
xfree (hd);
}
/* Take a lock if we are not using the keyboxd. */
gpg_error_t
keydb_lock (KEYDB_HANDLE hd)
{
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
return internal_keydb_lock (hd);
return 0;
}
/* Return the keyblock last found by keydb_search() in *RET_KB.
*
* On success, the function returns 0 and the caller must free *RET_KB
* using release_kbnode(). Otherwise, the function returns an error
* code.
*
* The returned keyblock has the kbnode flag bit 0 set for the node
* with the public key used to locate the keyblock or flag bit 1 set
* for the user ID node. */
gpg_error_t
keydb_get_keyblock (KEYDB_HANDLE hd, kbnode_t *ret_kb)
{
gpg_error_t err;
*ret_kb = NULL;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (DBG_CLOCK)
log_clock ("%s enter", __func__);
if (!hd->use_keyboxd)
{
err = internal_keydb_get_keyblock (hd, ret_kb);
goto leave;
}
if (hd->kbl->search_result)
{
err = keydb_parse_keyblock (hd->kbl->search_result,
hd->last_ubid_valid? hd->last_pk_no : 0,
hd->last_ubid_valid? hd->last_uid_no : 0,
ret_kb);
/* In contrast to the old code we close the iobuf here and thus
* this function may be called only once to get a keyblock. */
iobuf_close (hd->kbl->search_result);
hd->kbl->search_result = NULL;
}
else
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
leave:
if (DBG_CLOCK)
log_clock ("%s leave%s", __func__, err? " (failed)":"");
return err;
}
/* Default status callback used to show diagnostics from the keyboxd */
static gpg_error_t
keydb_default_status_cb (void *opaque, const char *line)
{
const char *s;
(void)opaque;
if ((s = has_leading_keyword (line, "NOTE")))
log_info (_("Note: %s\n"), s);
else if ((s = has_leading_keyword (line, "WARNING")))
log_info (_("WARNING: %s\n"), s);
return 0;
}
/* Communication object for STORE commands. */
struct store_parm_s
{
assuan_context_t ctx;
const void *data; /* The key in OpenPGP binary format. */
size_t datalen; /* The length of DATA. */
};
/* Handle the inquiries from the STORE command. */
static gpg_error_t
store_inq_cb (void *opaque, const char *line)
{
struct store_parm_s *parm = opaque;
gpg_error_t err = 0;
if (has_leading_keyword (line, "BLOB"))
{
if (parm->data)
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/* Update the keyblock KB (i.e., extract the fingerprint and find the
* corresponding keyblock in the keyring).
*
* This doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. Note:
* if there isn't a keyblock in the keyring corresponding to KB, then
* this function returns GPG_ERR_VALUE_NOT_FOUND.
*
* This function selects the matching record and modifies the current
* file position to point to the record just after the selected entry.
* Thus, if you do a subsequent search using HD, you should first do a
* keydb_search_reset. Further, if the selected record is important,
* you should use keydb_push_found_state and keydb_pop_found_state to
* save and restore it. */
gpg_error_t
keydb_update_keyblock (ctrl_t ctrl, KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
iobuf_t iobuf = NULL;
struct store_parm_s parm = {NULL};
log_assert (kb);
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
{
err = internal_keydb_update_keyblock (ctrl, hd, kb);
goto leave;
}
if (opt.dry_run)
{
err = 0;
goto leave;
}
err = build_keyblock_image (kb, &iobuf);
if (err)
goto leave;
parm.ctx = hd->kbl->ctx;
parm.data = iobuf_get_temp_buffer (iobuf);
parm.datalen = iobuf_get_temp_length (iobuf);
err = assuan_transact (hd->kbl->ctx, "STORE --update",
NULL, NULL,
store_inq_cb, &parm,
keydb_default_status_cb, hd);
leave:
iobuf_close (iobuf);
return err;
}
/* Insert a keyblock into one of the underlying keyrings or keyboxes.
*
* By default, the keyring / keybox from which the last search result
* came is used. If there was no previous search result (or
* keydb_search_reset was called), then the keyring / keybox where the
* next search would start is used (i.e., the current file position).
* In keyboxd mode the keyboxd decides where to store it.
*
* Note: this doesn't do anything if --dry-run was specified.
*
* Returns 0 on success. Otherwise, it returns an error code. */
gpg_error_t
keydb_insert_keyblock (KEYDB_HANDLE hd, kbnode_t kb)
{
gpg_error_t err;
iobuf_t iobuf = NULL;
struct store_parm_s parm = {NULL};
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
{
err = internal_keydb_insert_keyblock (hd, kb);
goto leave;
}
if (opt.dry_run)
{
err = 0;
goto leave;
}
err = build_keyblock_image (kb, &iobuf);
if (err)
goto leave;
parm.ctx = hd->kbl->ctx;
parm.data = iobuf_get_temp_buffer (iobuf);
parm.datalen = iobuf_get_temp_length (iobuf);
err = assuan_transact (hd->kbl->ctx, "STORE --insert",
NULL, NULL,
store_inq_cb, &parm,
keydb_default_status_cb, hd);
leave:
iobuf_close (iobuf);
return err;
}
/* Delete the currently selected keyblock. If you haven't done a
* search yet on this database handle (or called keydb_search_reset),
* then this function returns an error.
*
* Returns 0 on success or an error code, if an error occurred. */
gpg_error_t
keydb_delete_keyblock (KEYDB_HANDLE hd)
{
gpg_error_t err;
unsigned char hexubid[UBID_LEN * 2 + 1];
char line[ASSUAN_LINELENGTH];
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (!hd->use_keyboxd)
{
err = internal_keydb_delete_keyblock (hd);
goto leave;
}
if (opt.dry_run)
{
err = 0;
goto leave;
}
if (!hd->last_ubid_valid)
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
bin2hex (hd->last_ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "DELETE %s", hexubid);
err = assuan_transact (hd->kbl->ctx, line,
NULL, NULL,
NULL, NULL,
keydb_default_status_cb, hd);
leave:
return err;
}
/* Clears the current search result and resets the handle's position
* so that the next search starts at the beginning of the database.
*
* Returns 0 on success and an error code if an error occurred. */
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (DBG_CLOCK)
log_clock ("%s", __func__);
if (DBG_CACHE)
log_debug ("%s (hd=%p)", __func__, hd);
if (!hd->use_keyboxd)
{
err = internal_keydb_search_reset (hd);
goto leave;
}
/* All we need is to tell search that a reset is pending. Note that
* keydb_new sets this flag as well. To comply with the
* specification of keydb_delete_keyblock we also need to clear the
* ubid flag so that after a reset a delete can't be performed. */
hd->kbl->need_search_reset = 1;
hd->last_ubid_valid = 0;
err = 0;
leave:
return err;
}
-/* Status callback for SEARCH and NEXT operaions. */
+/* Status callback for SEARCH and NEXT operations. */
static gpg_error_t
search_status_cb (void *opaque, const char *line)
{
KEYDB_HANDLE hd = opaque;
gpg_error_t err = 0;
const char *s;
unsigned int n;
if ((s = has_leading_keyword (line, "PUBKEY_INFO")))
{
if (atoi (s) != PUBKEY_TYPE_OPGP)
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
else
{
hd->last_ubid_valid = 0;
while (*s && !spacep (s))
s++;
if (!(n=hex2fixedbuf (s, hd->last_ubid, sizeof hd->last_ubid)))
err = gpg_error (GPG_ERR_INV_VALUE);
else
{
hd->last_ubid_valid = 1;
hd->last_uid_no = 0;
hd->last_pk_no = 0;
s += n;
while (*s && !spacep (s))
s++;
while (spacep (s))
s++;
if (*s)
{
hd->last_uid_no = atoi (s);
while (*s && !spacep (s))
s++;
while (spacep (s))
s++;
if (*s)
hd->last_pk_no = atoi (s);
}
}
}
}
else
err = keydb_default_status_cb (opaque, line);
return err;
}
/* Search the database for keys matching the search description. If
* the DB contains any legacy keys, these are silently ignored.
*
* DESC is an array of search terms with NDESC entries. The search
* terms are or'd together. That is, the next entry in the DB that
* matches any of the descriptions will be returned.
*
* Note: this function resumes searching where the last search left
* off (i.e., at the current file position). If you want to search
* from the start of the database, then you need to first call
* keydb_search_reset().
*
* If no key matches the search description, returns
* GPG_ERR_NOT_FOUND. If there was a match, returns 0. If an error
* occurred, returns an error code.
*
* The returned key is considered to be selected and the raw data can,
* for instance, be returned by calling keydb_get_keyblock(). */
gpg_error_t
keydb_search (KEYDB_HANDLE hd, KEYDB_SEARCH_DESC *desc,
size_t ndesc, size_t *descindex)
{
gpg_error_t err;
int i;
char line[ASSUAN_LINELENGTH];
char *buffer;
size_t len;
if (!hd)
return gpg_error (GPG_ERR_INV_ARG);
if (descindex)
*descindex = 0; /* Make sure it is always set on return. */
if (DBG_CLOCK)
log_clock ("%s enter", __func__);
if (DBG_LOOKUP)
{
log_debug ("%s: %zu search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
char *t = keydb_search_desc_dump (&desc[i]);
log_debug ("%s %d: %s\n", __func__, i, t);
xfree (t);
}
}
if (!hd->use_keyboxd)
{
err = internal_keydb_search (hd, desc, ndesc, descindex);
goto leave;
}
/* Clear the result objects. */
if (hd->kbl->search_result)
{
iobuf_close (hd->kbl->search_result);
hd->kbl->search_result = NULL;
}
/* Check whether this is a NEXT search. */
if (!hd->kbl->need_search_reset)
{
/* No reset requested thus continue the search. The keyboxd
* keeps the context of the search and thus the NEXT operates on
* the last search pattern. This is how we always used the
* keydb.c functions. In theory we were able to modify the
* search pattern between searches but that is not anymore
* supported by keyboxd and a cursory check does not show that
* we actually made used of that misfeature. */
snprintf (line, sizeof line, "NEXT");
goto do_search;
}
hd->kbl->need_search_reset = 0;
if (!ndesc)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
for (i = 0; i < ndesc; i++)
if (desc->mode == KEYDB_SEARCH_MODE_FIRST)
{
/* If any description has mode FIRST, this item trumps all
* other descriptions. */
snprintf (line, sizeof line, "SEARCH --openpgp");
goto do_search;
}
for ( ; ndesc; desc++, ndesc--)
{
const char *more = ndesc > 1 ? "--openpgp --more" : "--openpgp";
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
snprintf (line, sizeof line, "SEARCH %s -- =%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
snprintf (line, sizeof line, "SEARCH %s -- *%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
snprintf (line, sizeof line, "SEARCH %s -- <%s",
more, desc->u.name+(desc->u.name[0] == '<') );
break;
case KEYDB_SEARCH_MODE_MAILSUB:
snprintf (line, sizeof line, "SEARCH %s -- @%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
snprintf (line, sizeof line, "SEARCH %s -- .%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_WORDS:
snprintf (line, sizeof line, "SEARCH %s -- +%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (line, sizeof line, "SEARCH %s -- 0x%08lX", more,
(ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (line, sizeof line, "SEARCH %s -- 0x%08lX%08lX", more,
(ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
{
unsigned char hexfpr[MAX_FINGERPRINT_LEN * 2 + 1];
log_assert (desc->fprlen <= MAX_FINGERPRINT_LEN);
bin2hex (desc->u.fpr, desc->fprlen, hexfpr);
snprintf (line, sizeof line, "SEARCH %s -- 0x%s", more, hexfpr);
}
break;
case KEYDB_SEARCH_MODE_ISSUER:
snprintf (line, sizeof line, "SEARCH %s -- #/%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
case KEYDB_SEARCH_MODE_SN:
snprintf (line, sizeof line, "SEARCH %s -- #%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_SUBJECT:
snprintf (line, sizeof line, "SEARCH %s -- /%s", more, desc->u.name);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
{
unsigned char hexgrip[KEYGRIP_LEN * 2 + 1];
bin2hex (desc->u.grip, KEYGRIP_LEN, hexgrip);
snprintf (line, sizeof line, "SEARCH %s -- &%s", more, hexgrip);
}
break;
case KEYDB_SEARCH_MODE_UBID:
{
unsigned char hexubid[UBID_LEN * 2 + 1];
bin2hex (desc->u.ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "SEARCH %s -- ^%s", more, hexubid);
}
break;
case KEYDB_SEARCH_MODE_NEXT:
log_debug ("%s: mode next - we should not get to here!\n", __func__);
snprintf (line, sizeof line, "NEXT");
break;
case KEYDB_SEARCH_MODE_FIRST:
log_debug ("%s: mode first - we should not get to here!\n", __func__);
/*fallthru*/
default:
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (ndesc > 1)
{
err = kbx_client_data_simple (hd->kbl->kcd, line);
if (err)
goto leave;
}
}
while (ndesc);
do_search:
hd->last_ubid_valid = 0;
err = kbx_client_data_cmd (hd->kbl->kcd, line, search_status_cb, hd);
if (!err && !(err = kbx_client_data_wait (hd->kbl->kcd, &buffer, &len)))
{
hd->kbl->search_result = iobuf_temp_with_content (buffer, len);
xfree (buffer);
if (DBG_LOOKUP && hd->last_ubid_valid)
log_printhex (hd->last_ubid, 20, "found UBID (%d,%d):",
hd->last_uid_no, hd->last_pk_no);
}
leave:
if (DBG_CLOCK)
log_clock ("%s leave (%sfound)", __func__, err? "not ":"");
return err;
}
diff --git a/g10/card-util.c b/g10/card-util.c
index 7df505f62..e8a5c1935 100644
--- a/g10/card-util.c
+++ b/g10/card-util.c
@@ -1,2611 +1,2611 @@
/* card-util.c - Utility functions for the OpenPGP card.
* Copyright (C) 2003-2005, 2009 Free Software Foundation, Inc.
* Copyright (C) 2003-2005, 2009 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif /*HAVE_LIBREADLINE*/
# include "gpg.h"
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "../common/status.h"
#include "options.h"
#include "main.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#define CONTROL_D ('D' - 'A' + 1)
static void
write_sc_op_status (gpg_error_t err)
{
switch (gpg_err_code (err))
{
case 0:
write_status (STATUS_SC_OP_SUCCESS);
break;
#if GNUPG_MAJOR_VERSION != 1
case GPG_ERR_CANCELED:
case GPG_ERR_FULLY_CANCELED:
write_status_text (STATUS_SC_OP_FAILURE, "1");
break;
case GPG_ERR_BAD_PIN:
case GPG_ERR_BAD_RESET_CODE:
write_status_text (STATUS_SC_OP_FAILURE, "2");
break;
case GPG_ERR_PIN_BLOCKED:
write_status_text (STATUS_SC_OP_FAILURE, "3");
break;
case GPG_ERR_NO_RESET_CODE:
write_status_text (STATUS_SC_OP_FAILURE, "4");
break;
default:
write_status (STATUS_SC_OP_FAILURE);
break;
#endif /* GNUPG_MAJOR_VERSION != 1 */
}
}
/* Change the PIN of an OpenPGP card. This is an interactive
function. */
void
change_pin (int unblock_v2, int allow_admin)
{
struct agent_card_info_s info;
int rc;
rc = agent_scd_learn (&info, 0);
if (rc)
{
log_error (_("OpenPGP card not available: %s\n"),
gpg_strerror (rc));
return;
}
log_info (_("OpenPGP card no. %s detected\n"),
info.serialno? info.serialno : "[none]");
if (opt.batch)
{
agent_release_card_info (&info);
log_error (_("can't do this in batch mode\n"));
return;
}
if (unblock_v2)
{
if (!info.is_v2)
log_error (_("This command is only available for version 2 cards\n"));
else if (!info.chvretry[1])
log_error (_("Reset Code not or not anymore available\n"));
else
{
rc = agent_scd_change_pin (2, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
}
else if (!allow_admin)
{
rc = agent_scd_change_pin (1, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else
for (;;)
{
char *answer;
tty_printf ("\n");
tty_printf ("1 - change PIN\n"
"2 - unblock PIN\n"
"3 - change Admin PIN\n"
"4 - set the Reset Code\n"
"Q - quit\n");
tty_printf ("\n");
answer = cpr_get("cardutil.change_pin.menu",_("Your selection? "));
cpr_kill_prompt();
if (strlen (answer) != 1)
{
xfree (answer);
continue;
}
if (*answer == '1')
{
/* Change PIN. */
rc = agent_scd_change_pin (1, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else if (*answer == '2')
{
/* Unblock PIN. */
rc = agent_scd_change_pin (101, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error unblocking the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN unblocked and new PIN set.\n");
}
else if (*answer == '3')
{
/* Change Admin PIN. */
rc = agent_scd_change_pin (3, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error changing the PIN: %s\n", gpg_strerror (rc));
else
tty_printf ("PIN changed.\n");
}
else if (*answer == '4')
{
/* Set a new Reset Code. */
rc = agent_scd_change_pin (102, info.serialno);
write_sc_op_status (rc);
if (rc)
tty_printf ("Error setting the Reset Code: %s\n",
gpg_strerror (rc));
else
tty_printf ("Reset Code set.\n");
}
else if (*answer == 'q' || *answer == 'Q')
{
xfree (answer);
break;
}
xfree (answer);
}
agent_release_card_info (&info);
}
static void
print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
/* FIXME: Fix formatting for FPRLEN != 20 */
for (i=0; i < fprlen ; i+=2, fpr += 2 )
{
if (i == 10 )
tty_fprintf (fp, " ");
tty_fprintf (fp, " %02X%02X", *fpr, fpr[1]);
}
}
else
tty_fprintf (fp, " [none]");
tty_fprintf (fp, "\n");
}
static void
print_shax_fpr_colon (estream_t fp,
const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
for (i=0; i < fprlen ; i++, fpr++)
es_fprintf (fp, "%02X", *fpr);
}
es_putc (':', fp);
}
static void
print_keygrip (estream_t fp, const unsigned char *grp)
{
int i;
if (opt.with_keygrip)
{
tty_fprintf (fp, " keygrip ....: ");
for (i=0; i < 20 ; i++, grp++)
tty_fprintf (fp, "%02X", *grp);
tty_fprintf (fp, "\n");
}
}
static void
print_name (estream_t fp, const char *text, const char *name)
{
tty_fprintf (fp, "%s", text);
/* FIXME: tty_printf_utf8_string2 eats everything after and
including an @ - e.g. when printing an url. */
if (name && *name)
{
if (fp)
print_utf8_buffer2 (fp, name, strlen (name), '\n');
else
tty_print_utf8_string2 (NULL, name, strlen (name), 0);
}
else
tty_fprintf (fp, _("[not set]"));
tty_fprintf (fp, "\n");
}
static void
print_isoname (estream_t fp, const char *text,
const char *tag, const char *name)
{
if (opt.with_colons)
es_fprintf (fp, "%s:", tag);
else
tty_fprintf (fp, "%s", text);
if (name && *name)
{
char *p, *given, *buf = xstrdup (name);
given = strstr (buf, "<<");
for (p=buf; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
if (opt.with_colons)
es_write_sanitized (fp, given, strlen (given), ":", NULL);
else if (fp)
print_utf8_buffer2 (fp, given, strlen (given), '\n');
else
tty_print_utf8_string2 (NULL, given, strlen (given), 0);
if (opt.with_colons)
es_putc (':', fp);
else if (*buf)
tty_fprintf (fp, " ");
}
if (opt.with_colons)
es_write_sanitized (fp, buf, strlen (buf), ":", NULL);
else if (fp)
print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
else
tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
xfree (buf);
}
else
{
if (opt.with_colons)
es_putc (':', fp);
else
tty_fprintf (fp, _("[not set]"));
}
if (opt.with_colons)
es_fputs (":\n", fp);
else
tty_fprintf (fp, "\n");
}
/* Return true if the SHA1 fingerprint FPR consists only of zeroes. */
static int
fpr_is_zero (const char *fpr, unsigned int fprlen)
{
int i;
for (i=0; i < fprlen && !fpr[i]; i++)
;
return (i == fprlen);
}
/* Return true if the fingerprint FPR consists only of 0xFF. */
static int
fpr_is_ff (const char *fpr, unsigned int fprlen)
{
int i;
for (i=0; i < fprlen && fpr[i] == '\xff'; i++)
;
return (i == fprlen);
}
static void
print_a_version (estream_t fp, const char *prefix, unsigned int value)
{
unsigned int a, b, c, d;
a = ((value >> 24) & 0xff);
b = ((value >> 16) & 0xff);
c = ((value >> 8) & 0xff);
d = ((value ) & 0xff);
if (a)
tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d);
else if (b)
tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d);
else if (c)
tty_fprintf (fp, "%s %u.%u\n", prefix, c, d);
else
tty_fprintf (fp, "%s %u\n", prefix, d);
}
/* Print all available information about the current card. */
static void
current_card_status (ctrl_t ctrl, estream_t fp,
char *serialno, size_t serialnobuflen)
{
struct agent_card_info_s info;
PKT_public_key *pk = xcalloc (1, sizeof *pk);
kbnode_t keyblock = NULL;
int rc;
unsigned int uval;
const unsigned char *thefpr;
unsigned int thefprlen;
int i;
char *pesc;
if (serialno && serialnobuflen)
*serialno = 0;
rc = agent_scd_learn (&info, 0);
if (rc)
{
if (opt.with_colons)
es_fputs ("AID:::\n", fp);
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (rc));
xfree (pk);
return;
}
if (opt.with_colons)
es_fprintf (fp, "Reader:%s:", info.reader? info.reader : "");
else
tty_fprintf (fp, "Reader ...........: %s\n",
info.reader? info.reader : "[none]");
if (opt.with_colons)
es_fprintf (fp, "AID:%s:", info.serialno? info.serialno : "");
else
tty_fprintf (fp, "Application ID ...: %s\n",
info.serialno? info.serialno : "[none]");
if (!info.serialno || strncmp (info.serialno, "D27600012401", 12)
|| strlen (info.serialno) != 32 )
{
const char *name1, *name2;
if (info.apptype && !ascii_strcasecmp (info.apptype, "openpgp"))
goto openpgp;
else if (info.apptype && !ascii_strcasecmp (info.apptype, "NKS"))
{
name1 = "netkey";
name2 = "NetKey";
}
else if (info.apptype && !ascii_strcasecmp (info.apptype, "DINSIG"))
{
name1 = "dinsig";
name2 = "DINSIG";
}
else if (info.apptype && !ascii_strcasecmp (info.apptype, "P15"))
{
name1 = "pkcs15";
name2 = "PKCS#15";
}
else if (info.apptype && !ascii_strcasecmp (info.apptype, "GELDKARTE"))
{
name1 = "geldkarte";
name2 = "Geldkarte";
}
else if (info.apptype && !ascii_strcasecmp (info.apptype, "PIV"))
{
name1 = "piv";
name2 = "PIV";
}
else
{
name1 = "unknown";
name2 = "Unknown";
}
if (opt.with_colons)
es_fprintf (fp, "%s-card:\n", name1);
else
tty_fprintf (fp, "Application type .: %s\n", name2);
agent_release_card_info (&info);
xfree (pk);
return;
}
openpgp:
if (!serialno)
;
else if (strlen (info.serialno)+1 > serialnobuflen)
log_error ("serial number longer than expected\n");
else
strcpy (serialno, info.serialno);
if (opt.with_colons)
es_fputs ("openpgp-card:\n", fp);
else
tty_fprintf (fp, "Application type .: %s\n", "OpenPGP");
if (opt.with_colons)
{
if (info.appversion)
es_fprintf (fp, "version:%02u%02u:\n",
(info.appversion >> 8) & 0xff,
info.appversion & 0xff);
else
es_fprintf (fp, "version:%.4s:\n", info.serialno+12);
uval = xtoi_2(info.serialno+16)*256 + xtoi_2 (info.serialno+18);
pesc = (info.manufacturer_name
? percent_escape (info.manufacturer_name, NULL) : NULL);
es_fprintf (fp, "vendor:%04x:%s:\n", uval, pesc? pesc:"");
xfree (pesc);
es_fprintf (fp, "serial:%.8s:\n", info.serialno+20);
print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
es_fputs ("lang:", fp);
if (info.disp_lang)
es_write_sanitized (fp, info.disp_lang, strlen (info.disp_lang),
":", NULL);
es_fputs (":\n", fp);
es_fprintf (fp, "sex:%c:\n", (info.disp_sex == 1? 'm':
info.disp_sex == 2? 'f' : 'u'));
es_fputs ("url:", fp);
if (info.pubkey_url)
es_write_sanitized (fp, info.pubkey_url, strlen (info.pubkey_url),
":", NULL);
es_fputs (":\n", fp);
es_fputs ("login:", fp);
if (info.login_data)
es_write_sanitized (fp, info.login_data, strlen (info.login_data),
":", NULL);
es_fputs (":\n", fp);
es_fprintf (fp, "forcepin:%d:::\n", !info.chv1_cached);
for (i=0; i < DIM (info.key_attr); i++)
if (info.key_attr[i].algo == PUBKEY_ALGO_RSA)
es_fprintf (fp, "keyattr:%d:%d:%u:\n", i+1,
info.key_attr[i].algo, info.key_attr[i].nbits);
else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH
|| info.key_attr[i].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[i].algo == PUBKEY_ALGO_EDDSA)
es_fprintf (fp, "keyattr:%d:%d:%s:\n", i+1,
info.key_attr[i].algo, info.key_attr[i].curve);
es_fprintf (fp, "maxpinlen:%d:%d:%d:\n",
info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
es_fprintf (fp, "pinretry:%d:%d:%d:\n",
info.chvretry[0], info.chvretry[1], info.chvretry[2]);
es_fprintf (fp, "sigcount:%lu:::\n", info.sig_counter);
if (info.extcap.kdf)
{
const char *setup;
if (info.kdf_do_enabled == 0)
setup = "off";
else if (info.kdf_do_enabled == 1)
setup = "single";
else
setup = "on";
es_fprintf (fp, "kdf:%s:\n", setup);
}
if (info.extcap.bt)
{
es_fprintf (fp, "uif:%d:%d:%d:\n",
info.uif[0], info.uif[1], info.uif[2]);
}
for (i=0; i < 4; i++)
{
if (info.private_do[i])
{
es_fprintf (fp, "private_do:%d:", i+1);
es_write_sanitized (fp, info.private_do[i],
strlen (info.private_do[i]), ":", NULL);
es_fputs (":\n", fp);
}
}
es_fputs ("cafpr:", fp);
print_shax_fpr_colon (fp, info.cafpr1len? info.cafpr1:NULL,
info.cafpr2len);
print_shax_fpr_colon (fp, info.cafpr2len? info.cafpr2:NULL,
info.cafpr2len);
print_shax_fpr_colon (fp, info.cafpr3len? info.cafpr3:NULL,
info.cafpr3len);
es_putc ('\n', fp);
es_fputs ("fpr:", fp);
print_shax_fpr_colon (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len);
print_shax_fpr_colon (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len);
print_shax_fpr_colon (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len);
es_putc ('\n', fp);
es_fprintf (fp, "fprtime:%lu:%lu:%lu:\n",
(unsigned long)info.fpr1time, (unsigned long)info.fpr2time,
(unsigned long)info.fpr3time);
es_fputs ("grp:", fp);
print_shax_fpr_colon (fp, info.grp1, sizeof info.grp1);
print_shax_fpr_colon (fp, info.grp2, sizeof info.grp2);
print_shax_fpr_colon (fp, info.grp3, sizeof info.grp3);
es_putc ('\n', fp);
}
else
{
if (info.appversion)
print_a_version (fp, "Version ..........:", info.appversion);
else
tty_fprintf (fp, "Version ..........: %.1s%c.%.1s%c\n",
info.serialno[12] == '0'?"":info.serialno+12,
info.serialno[13],
info.serialno[14] == '0'?"":info.serialno+14,
info.serialno[15]);
tty_fprintf (fp, "Manufacturer .....: %s\n",
info.manufacturer_name? info.manufacturer_name : "?");
tty_fprintf (fp, "Serial number ....: %.8s\n", info.serialno+20);
print_isoname (fp, "Name of cardholder: ", "name", info.disp_name);
print_name (fp, "Language prefs ...: ", info.disp_lang);
tty_fprintf (fp, "Salutation .......: %s\n",
info.disp_sex == 1? _("Mr."):
info.disp_sex == 2? _("Ms.") : "");
print_name (fp, "URL of public key : ", info.pubkey_url);
print_name (fp, "Login data .......: ", info.login_data);
if (info.private_do[0])
print_name (fp, "Private DO 1 .....: ", info.private_do[0]);
if (info.private_do[1])
print_name (fp, "Private DO 2 .....: ", info.private_do[1]);
if (info.private_do[2])
print_name (fp, "Private DO 3 .....: ", info.private_do[2]);
if (info.private_do[3])
print_name (fp, "Private DO 4 .....: ", info.private_do[3]);
if (info.cafpr1len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 1);
print_shax_fpr (fp, info.cafpr1, info.cafpr1len);
}
if (info.cafpr2len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 2);
print_shax_fpr (fp, info.cafpr2, info.cafpr2len);
}
if (info.cafpr3len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 3);
print_shax_fpr (fp, info.cafpr3, info.cafpr3len);
}
tty_fprintf (fp, "Signature PIN ....: %s\n",
info.chv1_cached? _("not forced"): _("forced"));
if (info.key_attr[0].algo)
{
tty_fprintf (fp, "Key attributes ...:");
for (i=0; i < DIM (info.key_attr); i++)
if (info.key_attr[i].algo == PUBKEY_ALGO_RSA)
tty_fprintf (fp, " rsa%u", info.key_attr[i].nbits);
else if (info.key_attr[i].algo == PUBKEY_ALGO_ECDH
|| info.key_attr[i].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[i].algo == PUBKEY_ALGO_EDDSA)
{
const char *curve_for_print = "?";
if (info.key_attr[i].curve)
{
const char *oid;
oid = openpgp_curve_to_oid (info.key_attr[i].curve,
NULL, NULL);
if (oid)
curve_for_print = openpgp_oid_to_curve (oid, 0);
}
tty_fprintf (fp, " %s", curve_for_print);
}
tty_fprintf (fp, "\n");
}
tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
info.chvmaxlen[0], info.chvmaxlen[1], info.chvmaxlen[2]);
tty_fprintf (fp, "PIN retry counter : %d %d %d\n",
info.chvretry[0], info.chvretry[1], info.chvretry[2]);
tty_fprintf (fp, "Signature counter : %lu\n", info.sig_counter);
if (info.extcap.kdf)
{
const char *setup;
if (info.kdf_do_enabled == 0)
setup = "off";
else if (info.kdf_do_enabled == 1)
setup = "single";
else
setup = "on";
tty_fprintf (fp, "KDF setting ......: %s\n", setup);
}
if (info.extcap.bt)
{
tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
info.uif[0] ? "on" : "off", info.uif[1] ? "on" : "off",
info.uif[2] ? "on" : "off");
}
tty_fprintf (fp, "Signature key ....:");
print_shax_fpr (fp, info.fpr1len? info.fpr1:NULL, info.fpr1len);
if (info.fpr1len && info.fpr1time)
{
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr1time));
print_keygrip (fp, info.grp1);
}
tty_fprintf (fp, "Encryption key....:");
print_shax_fpr (fp, info.fpr2len? info.fpr2:NULL, info.fpr2len);
if (info.fpr2len && info.fpr2time)
{
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr2time));
print_keygrip (fp, info.grp2);
}
tty_fprintf (fp, "Authentication key:");
print_shax_fpr (fp, info.fpr3len? info.fpr3:NULL, info.fpr3len);
if (info.fpr3len && info.fpr3time)
{
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (info.fpr3time));
print_keygrip (fp, info.grp3);
}
tty_fprintf (fp, "General key info..: ");
thefpr = (info.fpr1len? info.fpr1 : info.fpr2len? info.fpr2 :
info.fpr3len? info.fpr3 : NULL);
thefprlen = (info.fpr1len? info.fpr1len : info.fpr2len? info.fpr2len :
info.fpr3len? info.fpr3len : 0);
/* If the fingerprint is all 0xff, the key has no associated
OpenPGP certificate. */
if ( thefpr && !fpr_is_ff (thefpr, thefprlen)
&& !get_pubkey_byfprint (ctrl, pk, &keyblock, thefpr, thefprlen))
{
print_key_info (ctrl, fp, 0, pk, 0);
print_card_key_info (fp, keyblock);
}
else
tty_fprintf (fp, "[none]\n");
if (!info.manufacturer_name)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("Please try command \"%s\""
" if the listing does not look correct\n"),
"openpgp");
}
}
release_kbnode (keyblock);
free_public_key (pk);
agent_release_card_info (&info);
}
/* Print all available information for specific card with SERIALNO.
Print all available information for current card when SERIALNO is NULL.
Or print for all cards when SERIALNO is "all". */
void
card_status (ctrl_t ctrl, estream_t fp, const char *serialno)
{
int err;
strlist_t card_list, sl;
char *serialno0 = NULL;
int all_cards = 0;
int any_card = 0;
if (serialno == NULL)
{
current_card_status (ctrl, fp, NULL, 0);
return;
}
if (!strcmp (serialno, "all"))
all_cards = 1;
err = agent_scd_serialno (&serialno0, NULL);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_ENODEV && opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
/* Nothing available. */
return;
}
err = agent_scd_cardlist (&card_list);
for (sl = card_list; sl; sl = sl->next)
{
if (!all_cards && strcmp (serialno, sl->d))
continue;
if (any_card && !opt.with_colons)
tty_fprintf (fp, "\n");
any_card = 1;
err = agent_scd_serialno (NULL, sl->d);
if (err)
{
if (opt.verbose)
log_info (_("error getting serial number of card: %s\n"),
gpg_strerror (err));
continue;
}
current_card_status (ctrl, fp, NULL, 0);
if (!all_cards)
goto leave;
}
/* Select the original card again. */
err = agent_scd_serialno (NULL, serialno0);
leave:
xfree (serialno0);
free_strlist (card_list);
}
static char *
get_one_name (const char *prompt1, const char *prompt2)
{
char *name;
int i;
for (;;)
{
name = cpr_get (prompt1, prompt2);
if (!name)
return NULL;
trim_spaces (name);
cpr_kill_prompt ();
for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
;
/* The name must be in Latin-1 and not UTF-8 - lacking the code
to ensure this we restrict it to ASCII. */
if (name[i])
tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
else if (strchr (name, '<'))
tty_printf (_("Error: The \"<\" character may not be used.\n"));
else if (strstr (name, " "))
tty_printf (_("Error: Double spaces are not allowed.\n"));
else
return name;
xfree (name);
}
}
static int
change_name (void)
{
char *surname = NULL, *givenname = NULL;
char *isoname = NULL;
char *p;
int rc;
surname = get_one_name ("keygen.smartcard.surname",
_("Cardholder's surname: "));
givenname = get_one_name ("keygen.smartcard.givenname",
_("Cardholder's given name: "));
if (!surname || !givenname || (!*surname && !*givenname))
{
xfree (surname);
xfree (givenname);
rc = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
isoname = xmalloc ( strlen (surname) + 2 + strlen (givenname) + 1);
strcpy (stpcpy (stpcpy (isoname, surname), "<<"), givenname);
xfree (surname);
xfree (givenname);
for (p=isoname; *p; p++)
if (*p == ' ')
*p = '<';
if (strlen (isoname) > 39 )
{
tty_printf (_("Error: Combined name too long "
"(limit is %d characters).\n"), 39);
rc = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
rc = agent_scd_setattr ("DISP-NAME", isoname, strlen (isoname));
if (rc)
log_error ("error setting Name: %s\n", gpg_strerror (rc));
leave:
xfree (isoname);
write_sc_op_status (rc);
return rc;
}
static int
change_url (void)
{
char *url;
int rc;
url = cpr_get ("cardedit.change_url", _("URL to retrieve public key: "));
if (!url)
return -1;
trim_spaces (url);
cpr_kill_prompt ();
rc = agent_scd_setattr ("PUBKEY-URL", url, strlen (url));
if (rc)
log_error ("error setting URL: %s\n", gpg_strerror (rc));
xfree (url);
write_sc_op_status (rc);
return rc;
}
/* Fetch the key from the URL given on the card or try to get it from
the default keyserver. */
static int
fetch_url (ctrl_t ctrl)
{
int rc;
struct agent_card_info_s info;
memset(&info,0,sizeof(info));
rc=agent_scd_getattr("PUBKEY-URL",&info);
if(rc)
log_error("error retrieving URL from card: %s\n",gpg_strerror(rc));
else
{
rc=agent_scd_getattr("KEY-FPR",&info);
if(rc)
log_error("error retrieving key fingerprint from card: %s\n",
gpg_strerror(rc));
else if (info.pubkey_url && *info.pubkey_url)
{
strlist_t sl = NULL;
add_to_strlist (&sl, info.pubkey_url);
rc = keyserver_fetch (ctrl, sl, KEYORG_URL);
free_strlist (sl);
}
else if (info.fpr1len)
{
rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len,
opt.keyserver, 0);
}
}
agent_release_card_info (&info);
return rc;
}
#define MAX_GET_DATA_FROM_FILE 16384
/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
On error return -1 and store NULL at R_BUFFER; on success return
the number of bytes read and store the address of a newly allocated
buffer at R_BUFFER. */
static int
get_data_from_file (const char *fname, char **r_buffer)
{
estream_t fp;
char *data;
int n;
*r_buffer = NULL;
fp = es_fopen (fname, "rb");
if (!fp)
{
tty_printf (_("can't open '%s': %s\n"), fname, strerror (errno));
return -1;
}
data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
if (!data)
{
tty_printf (_("error allocating enough memory: %s\n"), strerror (errno));
es_fclose (fp);
return -1;
}
n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE, fp);
es_fclose (fp);
if (n < 0)
{
tty_printf (_("error reading '%s': %s\n"), fname, strerror (errno));
xfree (data);
return -1;
}
*r_buffer = data;
return n;
}
/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
success. */
static int
put_data_to_file (const char *fname, const void *buffer, size_t length)
{
estream_t fp;
fp = es_fopen (fname, "wb");
if (!fp)
{
tty_printf (_("can't create '%s': %s\n"), fname, strerror (errno));
return -1;
}
if (length && es_fwrite (buffer, length, 1, fp) != 1)
{
tty_printf (_("error writing '%s': %s\n"), fname, strerror (errno));
es_fclose (fp);
return -1;
}
es_fclose (fp);
return 0;
}
static int
change_login (const char *args)
{
char *data;
int n;
int rc;
if (args && *args == '<') /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, &data);
if (n < 0)
return -1;
}
else
{
data = cpr_get ("cardedit.change_login",
_("Login data (account name): "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
n = strlen (data);
}
rc = agent_scd_setattr ("LOGIN-DATA", data, n);
if (rc)
log_error ("error setting login data: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_private_do (const char *args, int nr)
{
char do_name[] = "PRIVATE-DO-X";
char *data;
int n;
int rc;
log_assert (nr >= 1 && nr <= 4);
do_name[11] = '0' + nr;
if (args && (args = strchr (args, '<'))) /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, &data);
if (n < 0)
return -1;
}
else
{
data = cpr_get ("cardedit.change_private_do",
_("Private DO data: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
n = strlen (data);
}
rc = agent_scd_setattr (do_name, data, n);
if (rc)
log_error ("error setting private DO: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_cert (const char *args)
{
char *data;
int n;
int rc;
if (args && *args == '<') /* Read it from a file */
{
for (args++; spacep (args); args++)
;
n = get_data_from_file (args, &data);
if (n < 0)
return -1;
}
else
{
tty_printf ("usage error: redirection to file required\n");
return -1;
}
rc = agent_scd_writecert ("OPENPGP.3", data, n);
if (rc)
log_error ("error writing certificate to card: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
read_cert (const char *args)
{
const char *fname;
void *buffer;
size_t length;
int rc;
if (args && *args == '>') /* Write it to a file */
{
for (args++; spacep (args); args++)
;
fname = args;
}
else
{
tty_printf ("usage error: redirection to file required\n");
return -1;
}
rc = agent_scd_readcert ("OPENPGP.3", &buffer, &length);
if (rc)
log_error ("error reading certificate from card: %s\n", gpg_strerror (rc));
else
rc = put_data_to_file (fname, buffer, length);
xfree (buffer);
write_sc_op_status (rc);
return rc;
}
static int
change_lang (void)
{
char *data, *p;
int rc;
data = cpr_get ("cardedit.change_lang",
_("Language preferences: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
if (strlen (data) > 8 || (strlen (data) & 1))
{
tty_printf (_("Error: invalid length of preference string.\n"));
xfree (data);
return -1;
}
for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
;
if (*p)
{
tty_printf (_("Error: invalid characters in preference string.\n"));
xfree (data);
return -1;
}
rc = agent_scd_setattr ("DISP-LANG", data, strlen (data));
if (rc)
log_error ("error setting lang: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_sex (void)
{
char *data;
const char *str;
int rc;
data = cpr_get ("cardedit.change_sex",
_("Salutation (M = Mr., F = Ms., or space): "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
if (!*data)
str = "9";
else if ((*data == 'M' || *data == 'm') && !data[1])
str = "1";
else if ((*data == 'F' || *data == 'f') && !data[1])
str = "2";
else
{
tty_printf (_("Error: invalid response.\n"));
xfree (data);
return -1;
}
rc = agent_scd_setattr ("DISP-SEX", str, 1);
if (rc)
log_error ("error setting salutation: %s\n", gpg_strerror (rc));
xfree (data);
write_sc_op_status (rc);
return rc;
}
static int
change_cafpr (int fprno)
{
char *data;
const char *s;
int i, c, rc;
unsigned char fpr[MAX_FINGERPRINT_LEN];
int fprlen;
data = cpr_get ("cardedit.change_cafpr", _("CA fingerprint: "));
if (!data)
return -1;
trim_spaces (data);
cpr_kill_prompt ();
for (i=0, s=data; i < MAX_FINGERPRINT_LEN && *s; )
{
while (spacep(s))
s++;
if (*s == ':')
s++;
while (spacep(s))
s++;
c = hextobyte (s);
if (c == -1)
break;
fpr[i++] = c;
s += 2;
}
fprlen = i;
xfree (data);
if ((fprlen != 20 && fprlen != 32) || *s)
{
tty_printf (_("Error: invalid formatted fingerprint.\n"));
return -1;
}
rc = agent_scd_setattr (fprno==1?"CA-FPR-1":
fprno==2?"CA-FPR-2":
fprno==3?"CA-FPR-3":"x", fpr, fprlen);
if (rc)
log_error ("error setting cafpr: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
return rc;
}
static void
toggle_forcesig (void)
{
struct agent_card_info_s info;
int rc;
int newstate;
memset (&info, 0, sizeof info);
rc = agent_scd_getattr ("CHV-STATUS", &info);
if (rc)
{
log_error ("error getting current status: %s\n", gpg_strerror (rc));
return;
}
newstate = !info.chv1_cached;
agent_release_card_info (&info);
rc = agent_scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
if (rc)
log_error ("error toggling signature PIN flag: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
}
/* Helper for the key generation/edit functions. */
static int
get_info_for_key_operation (struct agent_card_info_s *info)
{
int rc;
memset (info, 0, sizeof *info);
agent_scd_switchapp ("openpgp");
rc = agent_scd_getattr ("SERIALNO", info);
if (rc || !info->serialno || strncmp (info->serialno, "D27600012401", 12)
|| strlen (info->serialno) != 32 )
{
log_error (_("key operation not possible: %s\n"),
rc ? gpg_strerror (rc) : _("not an OpenPGP card"));
return rc? rc: -1;
}
rc = agent_scd_getattr ("KEY-FPR", info);
if (!rc)
rc = agent_scd_getattr ("CHV-STATUS", info);
if (!rc)
rc = agent_scd_getattr ("DISP-NAME", info);
if (!rc)
rc = agent_scd_getattr ("EXTCAP", info);
if (!rc)
rc = agent_scd_getattr ("KEY-ATTR", info);
if (rc)
log_error (_("error getting current key info: %s\n"), gpg_strerror (rc));
return rc;
}
/* Helper for the key generation/edit functions. */
static int
check_pin_for_key_operation (struct agent_card_info_s *info, int *forced_chv1)
{
int rc = 0;
*forced_chv1 = !info->chv1_cached;
if (*forced_chv1)
{ /* Switch off the forced mode so that during key generation we
don't get bothered with PIN queries for each
self-signature. */
rc = agent_scd_setattr ("CHV-STATUS-1", "\x01", 1);
if (rc)
{
log_error ("error clearing forced signature PIN flag: %s\n",
gpg_strerror (rc));
*forced_chv1 = 0;
}
}
if (!rc)
{
/* Check the PIN now, so that we won't get asked later for each
binding signature. */
rc = agent_scd_checkpin (info->serialno);
if (rc)
{
log_error ("error checking the PIN: %s\n", gpg_strerror (rc));
write_sc_op_status (rc);
}
}
return rc;
}
/* Helper for the key generation/edit functions. */
static void
restore_forced_chv1 (int *forced_chv1)
{
int rc;
if (*forced_chv1)
{ /* Switch back to forced state. */
rc = agent_scd_setattr ("CHV-STATUS-1", "", 1);
if (rc)
{
log_error ("error setting forced signature PIN flag: %s\n",
gpg_strerror (rc));
}
}
}
/* Helper for the key generation/edit functions. */
static void
show_card_key_info (struct agent_card_info_s *info)
{
tty_fprintf (NULL, "Signature key ....:");
print_shax_fpr (NULL, info->fpr1len? info->fpr1:NULL, info->fpr1len);
tty_fprintf (NULL, "Encryption key....:");
print_shax_fpr (NULL, info->fpr2len? info->fpr2:NULL, info->fpr2len);
tty_fprintf (NULL, "Authentication key:");
print_shax_fpr (NULL, info->fpr3len? info->fpr3:NULL, info->fpr3len);
tty_printf ("\n");
}
/* Helper for the key generation/edit functions. */
static int
replace_existing_key_p (struct agent_card_info_s *info, int keyno)
{
log_assert (keyno >= 0 && keyno <= 3);
if ((keyno == 1 && info->fpr1len)
|| (keyno == 2 && info->fpr2len)
|| (keyno == 3 && info->fpr3len))
{
tty_printf ("\n");
log_info ("WARNING: such a key has already been stored on the card!\n");
tty_printf ("\n");
if ( !cpr_get_answer_is_yes( "cardedit.genkeys.replace_key",
_("Replace existing key? (y/N) ")))
return -1;
return 1;
}
return 0;
}
static void
show_keysize_warning (void)
{
static int shown;
if (shown)
return;
shown = 1;
tty_printf
(_("Note: There is no guarantee that the card supports the requested\n"
" key type or size. If the key generation does not succeed,\n"
" please check the documentation of your card to see which\n"
" key types and sizes are supported.\n")
);
}
/* Ask for the size of a card key. NBITS is the current size
configured for the card. Returns 0 to use the default size
(i.e. NBITS) or the selected size. */
static unsigned int
ask_card_rsa_keysize (unsigned int nbits)
{
unsigned int min_nbits = 1024;
unsigned int max_nbits = 4096;
char *prompt, *answer;
unsigned int req_nbits;
for (;;)
{
prompt = xasprintf (_("What keysize do you want? (%u) "), nbits);
answer = cpr_get ("cardedit.genkeys.size", prompt);
cpr_kill_prompt ();
req_nbits = *answer? atoi (answer): nbits;
xfree (prompt);
xfree (answer);
if (req_nbits != nbits && (req_nbits % 32) )
{
req_nbits = ((req_nbits + 31) / 32) * 32;
tty_printf (_("rounded up to %u bits\n"), req_nbits);
}
if (req_nbits == nbits)
return 0; /* Use default. */
if (req_nbits < min_nbits || req_nbits > max_nbits)
{
tty_printf (_("%s keysizes must be in the range %u-%u\n"),
"RSA", min_nbits, max_nbits);
}
else
return req_nbits;
}
}
/* Ask for the key attribute of a card key. CURRENT is the current
attribute configured for the card. KEYNO is the number of the key
used to select the prompt. Returns NULL to use the default
attribute or the selected attribute structure. */
static struct key_attr *
ask_card_keyattr (int keyno, const struct key_attr *current)
{
struct key_attr *key_attr = NULL;
char *answer = NULL;
int algo;
tty_printf (_("Changing card key attribute for: "));
if (keyno == 0)
tty_printf (_("Signature key\n"));
else if (keyno == 1)
tty_printf (_("Encryption key\n"));
else
tty_printf (_("Authentication key\n"));
tty_printf (_("Please select what kind of key you want:\n"));
tty_printf (_(" (%d) RSA\n"), 1 );
tty_printf (_(" (%d) ECC\n"), 2 );
for (;;)
{
xfree (answer);
answer = cpr_get ("cardedit.genkeys.algo", _("Your selection? "));
cpr_kill_prompt ();
algo = *answer? atoi (answer) : 0;
if (!*answer || algo == 1 || algo == 2)
{
xfree (answer);
break;
}
else
tty_printf (_("Invalid selection.\n"));
}
if (algo == 0)
goto leave;
key_attr = xmalloc (sizeof (struct key_attr));
if (algo == 1)
{
unsigned int nbits, result_nbits;
if (current->algo == PUBKEY_ALGO_RSA)
nbits = current->nbits;
else
nbits = 2048;
result_nbits = ask_card_rsa_keysize (nbits);
if (result_nbits == 0)
{
if (current->algo == PUBKEY_ALGO_RSA)
{
xfree (key_attr);
key_attr = NULL;
}
else
result_nbits = nbits;
}
if (key_attr)
{
key_attr->algo = PUBKEY_ALGO_RSA;
key_attr->nbits = result_nbits;
}
}
else
{
const char *curve;
const char *oid_str;
if (current->algo == PUBKEY_ALGO_RSA)
{
if (keyno == 1)
/* Encryption key */
algo = PUBKEY_ALGO_ECDH;
else /* Signature key or Authentication key */
algo = PUBKEY_ALGO_ECDSA;
curve = NULL;
}
else
{
algo = current->algo;
curve = current->curve;
}
curve = ask_curve (&algo, NULL, curve);
if (curve)
{
key_attr->algo = algo;
oid_str = openpgp_curve_to_oid (curve, NULL, NULL);
key_attr->curve = openpgp_oid_to_curve (oid_str, 0);
}
else
{
xfree (key_attr);
key_attr = NULL;
}
}
leave:
if (key_attr)
{
if (key_attr->algo == PUBKEY_ALGO_RSA)
tty_printf (_("The card will now be re-configured"
" to generate a key of %u bits\n"), key_attr->nbits);
else if (key_attr->algo == PUBKEY_ALGO_ECDH
|| key_attr->algo == PUBKEY_ALGO_ECDSA
|| key_attr->algo == PUBKEY_ALGO_EDDSA)
tty_printf (_("The card will now be re-configured"
" to generate a key of type: %s\n"), key_attr->curve),
show_keysize_warning ();
}
return key_attr;
}
/* Change the key attribute of key KEYNO (0..2) and show an error
* message if that fails. */
static gpg_error_t
do_change_keyattr (int keyno, const struct key_attr *key_attr)
{
gpg_error_t err = 0;
char args[100];
if (key_attr->algo == PUBKEY_ALGO_RSA)
snprintf (args, sizeof args, "--force %d 1 rsa%u", keyno+1,
key_attr->nbits);
else if (key_attr->algo == PUBKEY_ALGO_ECDH
|| key_attr->algo == PUBKEY_ALGO_ECDSA
|| key_attr->algo == PUBKEY_ALGO_EDDSA)
snprintf (args, sizeof args, "--force %d %d %s",
keyno+1, key_attr->algo, key_attr->curve);
else
{
log_error (_("public key algorithm %d (%s) is not supported\n"),
key_attr->algo, gcry_pk_algo_name (key_attr->algo));
return gpg_error (GPG_ERR_PUBKEY_ALGO);
}
err = agent_scd_setattr ("KEY-ATTR", args, strlen (args));
if (err)
log_error (_("error changing key attribute for key %d: %s\n"),
keyno+1, gpg_strerror (err));
return err;
}
static void
key_attr (void)
{
struct agent_card_info_s info;
gpg_error_t err;
int keyno;
err = get_info_for_key_operation (&info);
if (err)
{
log_error (_("error getting card info: %s\n"), gpg_strerror (err));
return;
}
if (!(info.is_v2 && info.extcap.aac))
{
log_error (_("This command is not supported by this card\n"));
goto leave;
}
for (keyno = 0; keyno < DIM (info.key_attr); keyno++)
{
struct key_attr *key_attr;
if ((key_attr = ask_card_keyattr (keyno, &info.key_attr[keyno])))
{
err = do_change_keyattr (keyno, key_attr);
xfree (key_attr);
if (err)
{
/* Error: Better read the default key attribute again. */
agent_release_card_info (&info);
if (get_info_for_key_operation (&info))
goto leave;
/* Ask again for this key. */
keyno--;
}
}
}
leave:
agent_release_card_info (&info);
}
static void
generate_card_keys (ctrl_t ctrl)
{
struct agent_card_info_s info;
int forced_chv1;
int want_backup;
if (get_info_for_key_operation (&info))
return;
if (info.extcap.ki)
{
char *answer;
/* FIXME: Should be something like cpr_get_bool so that a status
GET_BOOL will be emitted. */
answer = cpr_get ("cardedit.genkeys.backup_enc",
_("Make off-card backup of encryption key? (Y/n) "));
want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
cpr_kill_prompt ();
xfree (answer);
}
else
want_backup = 0;
if ( (info.fpr1len && !fpr_is_zero (info.fpr1, info.fpr1len))
|| (info.fpr2len && !fpr_is_zero (info.fpr2, info.fpr2len))
|| (info.fpr3len && !fpr_is_zero (info.fpr3, info.fpr3len)))
{
tty_printf ("\n");
log_info (_("Note: keys are already stored on the card!\n"));
tty_printf ("\n");
if ( !cpr_get_answer_is_yes ("cardedit.genkeys.replace_keys",
_("Replace existing keys? (y/N) ")))
{
agent_release_card_info (&info);
return;
}
}
/* If no displayed name has been set, we assume that this is a fresh
card and print a hint about the default PINs. */
if (!info.disp_name || !*info.disp_name)
{
tty_printf ("\n");
tty_printf (_("Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"),
"123456", "12345678");
tty_printf ("\n");
}
if (check_pin_for_key_operation (&info, &forced_chv1))
goto leave;
generate_keypair (ctrl, 1, NULL, info.serialno, want_backup);
leave:
agent_release_card_info (&info);
restore_forced_chv1 (&forced_chv1);
}
/* This function is used by the key edit menu to generate an arbitrary
subkey. */
gpg_error_t
card_generate_subkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
gpg_error_t err;
struct agent_card_info_s info;
int forced_chv1 = 0;
int keyno;
err = get_info_for_key_operation (&info);
if (err)
return err;
show_card_key_info (&info);
tty_printf (_("Please select the type of key to generate:\n"));
tty_printf (_(" (1) Signature key\n"));
tty_printf (_(" (2) Encryption key\n"));
tty_printf (_(" (3) Authentication key\n"));
for (;;)
{
char *answer = cpr_get ("cardedit.genkeys.subkeytype",
_("Your selection? "));
cpr_kill_prompt();
if (*answer == CONTROL_D)
{
xfree (answer);
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
keyno = *answer? atoi(answer): 0;
xfree(answer);
if (keyno >= 1 && keyno <= 3)
break; /* Okay. */
tty_printf(_("Invalid selection.\n"));
}
if (replace_existing_key_p (&info, keyno) < 0)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
err = check_pin_for_key_operation (&info, &forced_chv1);
if (err)
goto leave;
err = generate_card_subkeypair (ctrl, pub_keyblock, keyno, info.serialno);
leave:
agent_release_card_info (&info);
restore_forced_chv1 (&forced_chv1);
return err;
}
/* Store the key at NODE into the smartcard and modify NODE to carry
the serialno stuff instead of the actual secret key parameters.
USE is the usage for that key; 0 means any usage. If
- PROCESSED_KEYS is not NULL it is a poiter to an strlist which will
+ PROCESSED_KEYS is not NULL it is a pointer to an strlist which will
be filled with the keygrips of successfully stored keys. */
int
card_store_subkey (KBNODE node, int use, strlist_t *processed_keys)
{
struct agent_card_info_s info;
int okay = 0;
unsigned int nbits;
int allow_keyno[3];
int keyno;
PKT_public_key *pk;
gpg_error_t err;
char *hexgrip = NULL;
int rc;
char *ecdh_param_str = NULL;
gnupg_isotime_t timebuf;
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
pk = node->pkt->pkt.public_key;
if (get_info_for_key_operation (&info))
return 0;
if (!info.extcap.ki)
{
tty_printf ("The card does not support the import of keys\n");
tty_printf ("\n");
goto leave;
}
nbits = nbits_from_pk (pk);
if (!info.is_v2 && nbits != 1024)
{
tty_printf ("You may only store a 1024 bit RSA key on the card\n");
tty_printf ("\n");
goto leave;
}
allow_keyno[0] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT)));
allow_keyno[1] = (!use || (use & (PUBKEY_USAGE_ENC)));
allow_keyno[2] = (!use || (use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH)));
tty_printf (_("Please select where to store the key:\n"));
if (allow_keyno[0])
tty_printf (_(" (1) Signature key\n"));
if (allow_keyno[1])
tty_printf (_(" (2) Encryption key\n"));
if (allow_keyno[2])
tty_printf (_(" (3) Authentication key\n"));
for (;;)
{
char *answer = cpr_get ("cardedit.genkeys.storekeytype",
_("Your selection? "));
cpr_kill_prompt();
if (*answer == CONTROL_D || !*answer)
{
xfree (answer);
goto leave;
}
keyno = *answer? atoi(answer): 0;
xfree(answer);
if (keyno >= 1 && keyno <= 3 && allow_keyno[keyno-1])
{
if (info.is_v2 && !info.extcap.aac
&& info.key_attr[keyno-1].nbits != nbits)
{
tty_printf ("Key does not match the card's capability.\n");
}
else
break; /* Okay. */
}
else
tty_printf(_("Invalid selection.\n"));
}
if ((rc = replace_existing_key_p (&info, keyno)) < 0)
goto leave;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
epoch2isotime (timebuf, (time_t)pk->timestamp);
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
ecdh_param_str = ecdh_param_str_from_pk (pk);
if (!ecdh_param_str)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
rc = agent_keytocard (hexgrip, keyno, rc, info.serialno,
timebuf, ecdh_param_str);
if (rc)
log_error (_("KEYTOCARD failed: %s\n"), gpg_strerror (rc));
else
{
okay = 1;
if (processed_keys)
add_to_strlist (processed_keys, hexgrip);
}
leave:
xfree (hexgrip);
xfree (ecdh_param_str);
agent_release_card_info (&info);
return okay;
}
/* Direct sending of an hex encoded APDU with error printing. */
static gpg_error_t
send_apdu (const char *hexapdu, const char *desc, unsigned int ignore)
{
gpg_error_t err;
unsigned int sw;
err = agent_scd_apdu (hexapdu, &sw);
if (err)
tty_printf ("sending card command %s failed: %s\n", desc,
gpg_strerror (err));
else if (!hexapdu
|| !strcmp (hexapdu, "undefined")
|| !strcmp (hexapdu, "reset-keep-lock")
|| !strcmp (hexapdu, "lock")
|| !strcmp (hexapdu, "trylock")
|| !strcmp (hexapdu, "unlock"))
; /* Ignore pseudo APDUs. */
else if (ignore == 0xffff)
; /* Ignore all status words. */
else if (sw != 0x9000)
{
switch (sw)
{
case 0x6285: err = gpg_error (GPG_ERR_OBJ_TERM_STATE); break;
case 0x6982: err = gpg_error (GPG_ERR_BAD_PIN); break;
case 0x6985: err = gpg_error (GPG_ERR_USE_CONDITIONS); break;
default: err = gpg_error (GPG_ERR_CARD);
}
if (!(ignore && ignore == sw))
tty_printf ("card command %s failed: %s (0x%04x)\n", desc,
gpg_strerror (err), sw);
}
return err;
}
/* Do a factory reset after confirmation. */
static void
factory_reset (void)
{
struct agent_card_info_s info;
gpg_error_t err;
char *answer = NULL;
int termstate = 0;
int i;
int locked = 0;
/* The code below basically does the same what this
gpg-connect-agent script does:
scd reset
scd serialno undefined
scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
scd apdu 00 e6 00 00
scd apdu 00 44 00 00
scd reset
/echo Card has been reset to factory defaults
but tries to find out something about the card first.
*/
err = agent_scd_learn (&info, 0);
if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
termstate = 1;
else if (err)
{
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
goto leave;
}
if (!termstate)
{
log_info (_("OpenPGP card no. %s detected\n"),
info.serialno? info.serialno : "[none]");
if (!(info.status_indicator == 3 || info.status_indicator == 5))
{
/* Note: We won't see status-indicator 3 here because it is not
possible to select a card application in termination state. */
log_error (_("This command is not supported by this card\n"));
goto leave;
}
tty_printf ("\n");
log_info (_("Note: This command destroys all keys stored on the card!\n"));
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("cardedit.factory-reset.proceed",
_("Continue? (y/N) ")))
goto leave;
answer = cpr_get ("cardedit.factory-reset.really",
_("Really do a factory reset? (enter \"yes\") "));
cpr_kill_prompt ();
trim_spaces (answer);
if (strcmp (answer, "yes"))
goto leave;
/* We need to select a card application before we can send APDUs
to the card without scdaemon doing anything on its own. We
then lock the connection so that other tools (e.g. Kleopatra)
don't try a new select. */
err = send_apdu ("lock", "locking connection ", 0);
if (err)
goto leave;
locked = 1;
err = send_apdu ("reset-keep-lock", "reset", 0);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select ", 0);
if (err)
goto leave;
/* Select the OpenPGP application. */
err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0);
if (err)
goto leave;
/* Do some dummy verifies with wrong PINs to set the retry
counter to zero. We can't easily use the card version 2.1
feature of presenting the admin PIN to allow the terminate
command because there is no machinery in scdaemon to catch
the verify command and ask for the PIN when the "APDU"
command is used. */
/* Here, the length of dummy wrong PIN is 32-byte, also
supporting authentication with KDF DO. */
for (i=0; i < 4; i++)
send_apdu ("0020008120"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff);
for (i=0; i < 4; i++)
send_apdu ("0020008320"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff);
/* Send terminate datafile command. */
err = send_apdu ("00e60000", "TERMINATE DF", 0x6985);
if (err)
goto leave;
}
/* Send activate datafile command. This is used without
confirmation if the card is already in termination state. */
err = send_apdu ("00440000", "ACTIVATE DF", 0);
if (err)
goto leave;
/* Finally we reset the card reader once more. */
err = send_apdu ("reset-keep-lock", "reset", 0);
/* Then, connect the card again. */
if (!err)
err = agent_scd_serialno (NULL, NULL);
leave:
if (locked)
send_apdu ("unlock", "unlocking connection ", 0);
xfree (answer);
agent_release_card_info (&info);
}
#define USER_PIN_DEFAULT "123456"
#define ADMIN_PIN_DEFAULT "12345678"
#define KDF_DATA_LENGTH_MIN 90
#define KDF_DATA_LENGTH_MAX 110
/* Generate KDF data. */
static gpg_error_t
gen_kdf_data (unsigned char *data, int single_salt)
{
const unsigned char h0[] = { 0x81, 0x01, 0x03,
0x82, 0x01, 0x08,
0x83, 0x04 };
const unsigned char h1[] = { 0x84, 0x08 };
const unsigned char h2[] = { 0x85, 0x08 };
const unsigned char h3[] = { 0x86, 0x08 };
const unsigned char h4[] = { 0x87, 0x20 };
const unsigned char h5[] = { 0x88, 0x20 };
unsigned char *p, *salt_user, *salt_admin;
unsigned char s2k_char;
unsigned int iterations;
unsigned char count_4byte[4];
gpg_error_t err = 0;
p = data;
s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
iterations = S2K_DECODE_COUNT (s2k_char);
count_4byte[0] = (iterations >> 24) & 0xff;
count_4byte[1] = (iterations >> 16) & 0xff;
count_4byte[2] = (iterations >> 8) & 0xff;
count_4byte[3] = (iterations & 0xff);
memcpy (p, h0, sizeof h0);
p += sizeof h0;
memcpy (p, count_4byte, sizeof count_4byte);
p += sizeof count_4byte;
memcpy (p, h1, sizeof h1);
salt_user = (p += sizeof h1);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
if (single_salt)
salt_admin = salt_user;
else
{
memcpy (p, h2, sizeof h2);
p += sizeof h2;
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
memcpy (p, h3, sizeof h3);
salt_admin = (p += sizeof h3);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
}
memcpy (p, h4, sizeof h4);
p += sizeof h4;
err = gcry_kdf_derive (USER_PIN_DEFAULT, strlen (USER_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, DIGEST_ALGO_SHA256,
salt_user, 8, iterations, 32, p);
p += 32;
if (!err)
{
memcpy (p, h5, sizeof h5);
p += sizeof h5;
err = gcry_kdf_derive (ADMIN_PIN_DEFAULT, strlen (ADMIN_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, DIGEST_ALGO_SHA256,
salt_admin, 8, iterations, 32, p);
}
return err;
}
/* Setup KDF data object which is used for PIN authentication. */
static void
kdf_setup (const char *args)
{
struct agent_card_info_s info;
gpg_error_t err;
unsigned char kdf_data[KDF_DATA_LENGTH_MAX];
size_t len;
memset (&info, 0, sizeof info);
err = agent_scd_getattr ("EXTCAP", &info);
if (err)
{
log_error (_("error getting card info: %s\n"), gpg_strerror (err));
return;
}
if (!info.extcap.kdf)
{
log_error (_("This command is not supported by this card\n"));
goto leave;
}
if (!strcmp (args, "off"))
{
len = 3;
memcpy (kdf_data, "\x81\x01\x00", len);
}
else
{
int single = 0;
if (*args != 0)
single = 1;
len = single ? KDF_DATA_LENGTH_MIN: KDF_DATA_LENGTH_MAX;
err = gen_kdf_data (kdf_data, single);
if (err)
goto leave_error;
}
err = agent_scd_setattr ("KDF", kdf_data, len);
if (err)
goto leave_error;
err = agent_scd_getattr ("KDF", &info);
leave_error:
if (err)
log_error (_("error for setup KDF: %s\n"), gpg_strerror (err));
leave:
agent_release_card_info (&info);
}
static void
uif (int arg_number, const char *arg_rest)
{
struct agent_card_info_s info;
int feature_available;
gpg_error_t err;
char name[100];
unsigned char data[2];
memset (&info, 0, sizeof info);
err = agent_scd_getattr ("EXTCAP", &info);
if (err)
{
log_error (_("error getting card info: %s\n"), gpg_strerror (err));
return;
}
feature_available = info.extcap.bt;
agent_release_card_info (&info);
if (!feature_available)
{
log_error (_("This command is not supported by this card\n"));
tty_printf ("\n");
return;
}
snprintf (name, sizeof name, "UIF-%d", arg_number);
if ( !strcmp (arg_rest, "off") )
data[0] = 0x00;
else if ( !strcmp (arg_rest, "on") )
data[0] = 0x01;
else if ( !strcmp (arg_rest, "permanent") )
data[0] = 0x02;
data[1] = 0x20;
err = agent_scd_setattr (name, data, 2);
if (err)
log_error (_("error for setup UIF: %s\n"), gpg_strerror (err));
}
/* Data used by the command parser. This needs to be outside of the
function scope to allow readline based command completion. */
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdADMIN, cmdHELP, cmdLIST, cmdDEBUG, cmdVERIFY,
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSEX, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdUNBLOCK, cmdFACTORYRESET, cmdKDFSETUP,
cmdKEYATTR, cmdUIF, cmdOPENPGP,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
int admin_only;
const char *desc;
} cmds[] =
{
{ "quit" , cmdQUIT , 0, N_("quit this menu")},
{ "q" , cmdQUIT , 0, NULL },
{ "admin" , cmdADMIN , 0, N_("show admin commands")},
{ "help" , cmdHELP , 0, N_("show this help")},
{ "?" , cmdHELP , 0, NULL },
{ "list" , cmdLIST , 0, N_("list all available data")},
{ "l" , cmdLIST , 0, NULL },
{ "debug" , cmdDEBUG , 0, NULL },
{ "name" , cmdNAME , 1, N_("change card holder's name")},
{ "url" , cmdURL , 1, N_("change URL to retrieve key")},
{ "fetch" , cmdFETCH , 0, N_("fetch the key specified in the card URL")},
{ "login" , cmdLOGIN , 1, N_("change the login name")},
{ "lang" , cmdLANG , 1, N_("change the language preferences")},
{ "salutation",cmdSEX , 1, N_("change card holder's salutation")},
{ "sex" ,cmdSEX , 1, NULL }, /* Backward compatibility. */
{ "cafpr" , cmdCAFPR , 1, N_("change a CA fingerprint")},
{ "forcesig", cmdFORCESIG, 1, N_("toggle the signature force PIN flag")},
{ "generate", cmdGENERATE, 1, N_("generate new keys")},
{ "passwd" , cmdPASSWD, 0, N_("menu to change or unblock the PIN")},
{ "verify" , cmdVERIFY, 0, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK,0, N_("unblock the PIN using a Reset Code")},
{ "factory-reset", cmdFACTORYRESET, 1, N_("destroy all keys and data")},
{ "kdf-setup", cmdKDFSETUP, 1,
N_("setup KDF for PIN authentication (on/single/off)")},
{ "key-attr", cmdKEYATTR, 1, N_("change the key attribute")},
{ "uif", cmdUIF, 1, N_("change the User Interaction Flag")},
{ "openpgp", cmdOPENPGP, 0, N_("switch to the OpenPGP app")},
/* Note, that we do not announce these command yet. */
{ "privatedo", cmdPRIVATEDO, 0, NULL },
{ "readcert", cmdREADCERT, 0, NULL },
{ "writecert", cmdWRITECERT, 1, NULL },
{ NULL, cmdINVCMD, 0, NULL }
};
#ifdef HAVE_LIBREADLINE
/* These two functions are used by readline for command completion. */
static char *
command_generator(const char *text,int state)
{
static int list_index,len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if(!state)
{
list_index=0;
len=strlen(text);
}
/* Return the next partial match */
while((name=cmds[list_index].name))
{
/* Only complete commands that have help text */
if(cmds[list_index++].desc && strncmp(name,text,len)==0)
return strdup(name);
}
return NULL;
}
static char **
card_edit_completion(const char *text, int start, int end)
{
(void)end;
/* If we are at the start of a line, we try and command-complete.
If not, just do nothing for now. */
if(start==0)
return rl_completion_matches(text,command_generator);
rl_attempted_completion_over=1;
return NULL;
}
#endif /*HAVE_LIBREADLINE*/
/* Menu to edit all user changeable values on an OpenPGP card. Only
Key creation is not handled here. */
void
card_edit (ctrl_t ctrl, strlist_t commands)
{
enum cmdids cmd = cmdNOP;
int have_commands = !!commands;
int redisplay = 1;
char *answer = NULL;
int allow_admin=0;
char serialnobuf[50];
if (opt.command_fd != -1)
;
else if (opt.batch && !have_commands)
{
log_error(_("can't do this in batch mode\n"));
goto leave;
}
for (;;)
{
int arg_number;
const char *arg_string = "";
const char *arg_rest = "";
char *p;
int i;
int cmd_admin_only;
tty_printf("\n");
if (redisplay)
{
if (opt.with_colons)
{
current_card_status (ctrl, es_stdout,
serialnobuf, DIM (serialnobuf));
fflush (stdout);
}
else
{
current_card_status (ctrl, NULL,
serialnobuf, DIM (serialnobuf));
tty_printf("\n");
}
redisplay = 0;
}
do
{
xfree (answer);
if (have_commands)
{
if (commands)
{
answer = xstrdup (commands->d);
commands = commands->next;
}
else if (opt.batch)
{
answer = xstrdup ("quit");
}
else
have_commands = 0;
}
if (!have_commands)
{
tty_enable_completion (card_edit_completion);
answer = cpr_get_no_help("cardedit.prompt", _("gpg/card> "));
cpr_kill_prompt();
tty_disable_completion ();
}
trim_spaces(answer);
}
while ( *answer == '#' );
arg_number = 0; /* Yes, here is the init which egcc complains about */
cmd_admin_only = 0;
if (!*answer)
cmd = cmdLIST; /* Default to the list command */
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else
{
if ((p=strchr (answer,' ')))
{
*p++ = 0;
trim_spaces (answer);
trim_spaces (p);
arg_number = atoi(p);
arg_string = p;
arg_rest = p;
while (digitp (arg_rest))
arg_rest++;
while (spacep (arg_rest))
arg_rest++;
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (answer, cmds[i].name ))
break;
cmd = cmds[i].id;
cmd_admin_only = cmds[i].admin_only;
}
if (!allow_admin && cmd_admin_only)
{
tty_printf ("\n");
tty_printf (_("Admin-only command\n"));
continue;
}
switch (cmd)
{
case cmdHELP:
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc
&& (!cmds[i].admin_only || (cmds[i].admin_only && allow_admin)))
tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
break;
case cmdADMIN:
if ( !strcmp (arg_string, "on") )
allow_admin = 1;
else if ( !strcmp (arg_string, "off") )
allow_admin = 0;
else if ( !strcmp (arg_string, "verify") )
{
/* Force verification of the Admin Command. However,
this is only done if the retry counter is at initial
state. */
char *tmp = xmalloc (strlen (serialnobuf) + 6 + 1);
strcpy (stpcpy (tmp, serialnobuf), "[CHV3]");
allow_admin = !agent_scd_checkpin (tmp);
xfree (tmp);
}
else /* Toggle. */
allow_admin=!allow_admin;
if(allow_admin)
tty_printf(_("Admin commands are allowed\n"));
else
tty_printf(_("Admin commands are not allowed\n"));
break;
case cmdVERIFY:
agent_scd_checkpin (serialnobuf);
redisplay = 1;
break;
case cmdLIST:
redisplay = 1;
break;
case cmdNAME:
change_name ();
break;
case cmdURL:
change_url ();
break;
case cmdFETCH:
fetch_url (ctrl);
break;
case cmdLOGIN:
change_login (arg_string);
break;
case cmdLANG:
change_lang ();
break;
case cmdSEX:
change_sex ();
break;
case cmdCAFPR:
if ( arg_number < 1 || arg_number > 3 )
tty_printf ("usage: cafpr N\n"
" 1 <= N <= 3\n");
else
change_cafpr (arg_number);
break;
case cmdPRIVATEDO:
if ( arg_number < 1 || arg_number > 4 )
tty_printf ("usage: privatedo N\n"
" 1 <= N <= 4\n");
else
change_private_do (arg_string, arg_number);
break;
case cmdWRITECERT:
if ( arg_number != 3 )
tty_printf ("usage: writecert 3 < FILE\n");
else
change_cert (arg_rest);
break;
case cmdREADCERT:
if ( arg_number != 3 )
tty_printf ("usage: readcert 3 > FILE\n");
else
read_cert (arg_rest);
break;
case cmdFORCESIG:
toggle_forcesig ();
break;
case cmdGENERATE:
generate_card_keys (ctrl);
break;
case cmdPASSWD:
change_pin (0, allow_admin);
break;
case cmdUNBLOCK:
change_pin (1, allow_admin);
break;
case cmdFACTORYRESET:
factory_reset ();
break;
case cmdKDFSETUP:
kdf_setup (arg_string);
break;
case cmdKEYATTR:
key_attr ();
break;
case cmdUIF:
if ( arg_number < 1 || arg_number > 3 )
tty_printf ("usage: uif N [on|off|permanent]\n"
" 1 <= N <= 3\n");
else
uif (arg_number, arg_rest);
break;
case cmdOPENPGP:
agent_scd_switchapp ("openpgp");
redisplay = 1;
break;
case cmdQUIT:
goto leave;
case cmdNOP:
break;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
} /* End of main menu loop. */
leave:
xfree (answer);
}
diff --git a/g10/encrypt.c b/g10/encrypt.c
index 8ce6164ce..3fc10a7b8 100644
--- a/g10/encrypt.c
+++ b/g10/encrypt.c
@@ -1,1250 +1,1250 @@
/* encrypt.c - Main encryption driver
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005,
* 2006, 2009 Free Software Foundation, Inc.
* Copyright (C) 2016, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "filter.h"
#include "trustdb.h"
#include "../common/i18n.h"
#include "../common/status.h"
#include "pkglue.h"
#include "../common/compliance.h"
static int encrypt_simple( const char *filename, int mode, int use_seskey );
static int write_pubkey_enc_from_list (ctrl_t ctrl,
PK_LIST pk_list, DEK *dek, iobuf_t out);
/****************
* Encrypt FILENAME with only the symmetric cipher. Take input from
* stdin if FILENAME is NULL. If --force-aead is used we use an SKESK.
*/
int
encrypt_symmetric (const char *filename)
{
return encrypt_simple( filename, 1, opt.force_aead);
}
/****************
* Encrypt FILENAME as a literal data packet only. Take input from
* stdin if FILENAME is NULL.
*/
int
encrypt_store (const char *filename)
{
return encrypt_simple( filename, 0, 0 );
}
-/* Create and setup a DEK structure and print approriate warnings.
+/* Create and setup a DEK structure and print appropriate warnings.
* PK_LIST gives the list of public keys. Always returns a DEK. The
* actual session needs to be added later. */
static DEK *
create_dek_with_warnings (pk_list_t pk_list)
{
DEK *dek;
dek = xmalloc_secure_clear (sizeof *dek);
if (!opt.def_cipher_algo)
{
/* Try to get it from the prefs. */
dek->algo = select_algo_from_prefs (pk_list, PREFTYPE_SYM, -1, NULL);
if (dek->algo == -1)
{
/* If does not make sense to fallback to the rfc4880
* required 3DES if we will reject that algo later. Thus we
* fallback to AES anticipating RFC4880bis rules. */
if (opt.flags.allow_old_cipher_algos)
dek->algo = CIPHER_ALGO_3DES;
else
dek->algo = CIPHER_ALGO_AES;
}
/* In case 3DES has been selected, print a warning if any key
* does not have a preference for AES. This should help to
- * indentify why encrypting to several recipients falls back to
+ * identify why encrypting to several recipients falls back to
* 3DES. */
if (opt.verbose && dek->algo == CIPHER_ALGO_3DES)
warn_missing_aes_from_pklist (pk_list);
}
else
{
if (!opt.expert
&& (select_algo_from_prefs (pk_list, PREFTYPE_SYM,
opt.def_cipher_algo, NULL)
!= opt.def_cipher_algo))
{
log_info(_("WARNING: forcing symmetric cipher %s (%d)"
" violates recipient preferences\n"),
openpgp_cipher_algo_name (opt.def_cipher_algo),
opt.def_cipher_algo);
}
dek->algo = opt.def_cipher_algo;
}
return dek;
}
/* Check whether all encryption keys are compliant with the current
* mode and issue respective status lines. DEK has the info about the
* session key and PK_LIST the list of public keys. */
static gpg_error_t
check_encryption_compliance (DEK *dek, pk_list_t pk_list)
{
gpg_error_t err = 0;
pk_list_t pkr;
int compliant;
/* First check whether we should use the algo at all. */
if (openpgp_cipher_blocklen (dek->algo) < 16
&& !opt.flags.allow_old_cipher_algos)
{
log_error (_("cipher algorithm '%s' may not be used for encryption\n"),
openpgp_cipher_algo_name (dek->algo));
if (!opt.quiet)
log_info (_("(use option \"%s\" to override)\n"),
"--allow-old-cipher-algos");
err = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
/* Now check the compliance. */
if (! gnupg_cipher_is_allowed (opt.compliance, 1, dek->algo,
GCRY_CIPHER_MODE_CFB))
{
log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
openpgp_cipher_algo_name (dek->algo),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
if (!gnupg_rng_is_compliant (opt.compliance))
{
err = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
"RNG",
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", err);
goto leave;
}
/* From here on we only test for CO_DE_VS - if we ever want to
* return other compliance mode values we need to change this to
* loop over all those values. */
compliant = gnupg_gcrypt_is_compliant (CO_DE_VS);
if (!gnupg_cipher_is_compliant (CO_DE_VS, dek->algo, GCRY_CIPHER_MODE_CFB))
compliant = 0;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
PKT_public_key *pk = pkr->pk;
unsigned int nbits = nbits_from_pk (pk);
if (!gnupg_pk_is_compliant (opt.compliance, pk->pubkey_algo, 0,
pk->pkey, nbits, NULL))
log_info (_("WARNING: key %s is not suitable for encryption"
" in %s mode\n"),
keystr_from_pk (pk),
gnupg_compliance_option_string (opt.compliance));
if (compliant
&& !gnupg_pk_is_compliant (CO_DE_VS, pk->pubkey_algo, 0, pk->pkey,
nbits, NULL))
compliant = 0; /* Not compliant - reset flag. */
}
/* If we are compliant print the status for de-vs compliance. */
if (compliant)
write_status_strings (STATUS_ENCRYPTION_COMPLIANCE_MODE,
gnupg_status_compliance_flag (CO_DE_VS),
NULL);
/* Check whether we should fail the operation. */
if (opt.flags.require_compliance
&& opt.compliance == CO_DE_VS
&& !compliant)
{
compliance_failure ();
err = gpg_error (GPG_ERR_FORBIDDEN);
goto leave;
}
leave:
return err;
}
/* Encrypt a session key using DEK and store a pointer to the result
* at R_ENCKEY and its length at R_ENCKEYLEN.
*
* R_SESKEY points to the unencrypted session key (.KEY, .KEYLEN) and
* the algorithm that will be used to encrypt the contents of the
* SKESK packet (.ALGO). If R_SESKEY points to NULL, then a random
* session key that is appropriate for DEK->ALGO is generated and
* stored at R_SESKEY. If AEAD_ALGO is not 0 the given AEAD algorithm
* is used for encryption.
*/
static gpg_error_t
encrypt_seskey (DEK *dek, aead_algo_t aead_algo,
DEK **r_seskey, void **r_enckey, size_t *r_enckeylen)
{
gpg_error_t err;
gcry_cipher_hd_t hd = NULL;
byte *buf = NULL;
DEK *seskey;
*r_enckey = NULL;
*r_enckeylen = 0;
if (*r_seskey)
seskey = *r_seskey;
else
{
seskey = xtrycalloc (1, sizeof(DEK));
if (!seskey)
{
err = gpg_error_from_syserror ();
goto leave;
}
seskey->algo = dek->algo;
make_session_key (seskey);
/*log_hexdump( "thekey", c->key, c->keylen );*/
}
if (aead_algo)
{
unsigned int noncelen;
enum gcry_cipher_modes ciphermode;
byte ad[4];
err = openpgp_aead_algo_info (aead_algo, &ciphermode, &noncelen);
if (err)
goto leave;
/* Allocate space for the nonce, the key, and the authentication
* tag (16). */
buf = xtrymalloc_secure (noncelen + seskey->keylen + 16);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
gcry_randomize (buf, noncelen, GCRY_STRONG_RANDOM);
err = openpgp_cipher_open (&hd, dek->algo,
ciphermode, GCRY_CIPHER_SECURE);
if (!err)
err = gcry_cipher_setkey (hd, dek->key, dek->keylen);
if (!err)
err = gcry_cipher_setiv (hd, buf, noncelen);
if (err)
goto leave;
ad[0] = (0xc0 | PKT_SYMKEY_ENC);
ad[1] = 5;
ad[2] = dek->algo;
ad[3] = aead_algo;
err = gcry_cipher_authenticate (hd, ad, 4);
if (err)
goto leave;
memcpy (buf + noncelen, seskey->key, seskey->keylen);
gcry_cipher_final (hd);
err = gcry_cipher_encrypt (hd, buf + noncelen, seskey->keylen, NULL,0);
if (err)
goto leave;
err = gcry_cipher_gettag (hd, buf + noncelen + seskey->keylen, 16);
if (err)
goto leave;
*r_enckeylen = noncelen + seskey->keylen + 16;
*r_enckey = buf;
buf = NULL;
}
else
{
/* In the old version 4 SKESK the encrypted session key is
* prefixed with a one-octet algorithm id. */
buf = xtrymalloc_secure (1 + seskey->keylen);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
buf[0] = seskey->algo;
memcpy (buf + 1, seskey->key, seskey->keylen);
err = openpgp_cipher_open (&hd, dek->algo, GCRY_CIPHER_MODE_CFB, 1);
if (!err)
err = gcry_cipher_setkey (hd, dek->key, dek->keylen);
if (!err)
err = gcry_cipher_setiv (hd, NULL, 0);
if (!err)
err = gcry_cipher_encrypt (hd, buf, seskey->keylen + 1, NULL, 0);
if (err)
goto leave;
*r_enckeylen = seskey->keylen + 1;
*r_enckey = buf;
buf = NULL;
}
/* Return the session key in case we allocated it. */
*r_seskey = seskey;
seskey = NULL;
leave:
gcry_cipher_close (hd);
if (seskey != *r_seskey)
xfree (seskey);
xfree (buf);
return err;
}
/* Return the AEAD algo if we shall use AEAD mode. Returns 0 if AEAD
* shall not be used. */
aead_algo_t
use_aead (pk_list_t pk_list, int algo)
{
int can_use;
can_use = openpgp_cipher_get_algo_blklen (algo) == 16;
/* With --force-aead we want AEAD. */
if (opt.force_aead)
{
if (!can_use)
{
log_info ("Warning: request to use OCB ignored for cipher '%s'\n",
openpgp_cipher_algo_name (algo));
return 0;
}
return AEAD_ALGO_OCB;
}
/* AEAD does only work with 128 bit cipher blocklength. */
if (!can_use)
return 0;
/* Note the user which keys have no AEAD feature flag set. */
if (opt.verbose)
warn_missing_aead_from_pklist (pk_list);
/* If all keys support AEAD we can use it. */
return select_aead_from_pklist (pk_list);
}
/* Shall we use the MDC? Yes - unless rfc-2440 compatibility is
* requested. */
int
use_mdc (pk_list_t pk_list,int algo)
{
(void)pk_list;
(void)algo;
/* RFC-2440 don't has MDC - this is the only way to create a legacy
* non-MDC encryption packet. */
if (RFC2440)
return 0;
return 1; /* In all other cases we use the MDC */
}
/* We don't want to use use_seskey yet because older gnupg versions
can't handle it, and there isn't really any point unless we're
making a message that can be decrypted by a public key or
passphrase. */
static int
encrypt_simple (const char *filename, int mode, int use_seskey)
{
iobuf_t inp, out;
PACKET pkt;
PKT_plaintext *pt = NULL;
STRING2KEY *s2k = NULL;
void *enckey = NULL;
size_t enckeylen = 0;
int rc = 0;
u32 filesize;
cipher_filter_context_t cfx;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
text_filter_context_t tfx;
progress_filter_context_t *pfx;
int do_compress = !!default_compress_algo();
if (!gnupg_rng_is_compliant (opt.compliance))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
"RNG",
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", rc);
return rc;
}
pfx = new_progress_context ();
memset( &cfx, 0, sizeof cfx);
memset( &zfx, 0, sizeof zfx);
memset( &tfx, 0, sizeof tfx);
init_packet(&pkt);
/* Prepare iobufs. */
inp = iobuf_open(filename);
if (inp)
iobuf_ioctl (inp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error(_("can't open '%s': %s\n"), filename? filename: "[stdin]",
strerror(errno) );
release_progress_context (pfx);
return rc;
}
handle_progress (pfx, inp, filename);
if (opt.textmode)
iobuf_push_filter( inp, text_filter, &tfx );
cfx.dek = NULL;
if ( mode )
{
aead_algo_t aead_algo;
rc = setup_symkey (&s2k, &cfx.dek);
if (rc)
{
iobuf_close (inp);
if (gpg_err_code (rc) == GPG_ERR_CIPHER_ALGO
|| gpg_err_code (rc) == GPG_ERR_DIGEST_ALGO)
; /* Error has already been printed. */
else
log_error (_("error creating passphrase: %s\n"), gpg_strerror (rc));
release_progress_context (pfx);
return rc;
}
if (use_seskey && s2k->mode != 1 && s2k->mode != 3)
{
use_seskey = 0;
log_info (_("can't use a SKESK packet due to the S2K mode\n"));
}
/* See whether we want to use AEAD. */
aead_algo = use_aead (NULL, cfx.dek->algo);
if ( use_seskey )
{
DEK *dek = NULL;
rc = encrypt_seskey (cfx.dek, aead_algo, &dek, &enckey, &enckeylen);
if (rc)
{
xfree (cfx.dek);
xfree (s2k);
iobuf_close (inp);
release_progress_context (pfx);
return rc;
}
/* Replace key in DEK. */
xfree (cfx.dek);
cfx.dek = dek;
}
if (aead_algo)
cfx.dek->use_aead = aead_algo;
else
cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo);
if (opt.verbose)
log_info(_("using cipher %s.%s\n"),
openpgp_cipher_algo_name (cfx.dek->algo),
cfx.dek->use_aead? openpgp_aead_algo_name (cfx.dek->use_aead)
/**/ : "CFB");
}
if (rc || (rc = open_outfile (GNUPG_INVALID_FD, filename, opt.armor? 1:0,
0, &out )))
{
iobuf_cancel (inp);
xfree (cfx.dek);
xfree (s2k);
release_progress_context (pfx);
return rc;
}
if ( opt.armor )
{
afx = new_armor_context ();
push_armor_filter (afx, out);
}
if ( s2k )
{
/* Fixme: This is quite similar to write_symkey_enc. */
PKT_symkey_enc *enc = xmalloc_clear (sizeof *enc + enckeylen);
enc->version = cfx.dek->use_aead ? 5 : 4;
enc->cipher_algo = cfx.dek->algo;
enc->aead_algo = cfx.dek->use_aead;
enc->s2k = *s2k;
if (enckeylen)
{
enc->seskeylen = enckeylen;
memcpy (enc->seskey, enckey, enckeylen);
}
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = enc;
if ((rc = build_packet( out, &pkt )))
log_error("build symkey packet failed: %s\n", gpg_strerror (rc) );
xfree (enc);
xfree (enckey);
enckey = NULL;
}
if (!opt.no_literal)
pt = setup_plaintext_name (filename, inp);
/* Note that PGP 5 has problems decrypting symmetrically encrypted
data if the file length is in the inner packet. It works when
only partial length headers are use. In the past, we always used
partial body length here, but since PGP 2, PGP 6, and PGP 7 need
the file length, and nobody should be using PGP 5 nowadays
anyway, this is now set to the file length. Note also that this
only applies to the RFC-1991 style symmetric messages, and not
the RFC-2440 style. PGP 6 and 7 work with either partial length
or fixed length with the new style messages. */
if ( !iobuf_is_pipe_filename (filename) && *filename && !opt.textmode )
{
uint64_t tmpsize;
tmpsize = iobuf_get_filelength(inp);
if (!tmpsize && opt.verbose)
log_info(_("WARNING: '%s' is an empty file\n"), filename );
/* We can't encode the length of very large files because
OpenPGP uses only 32 bit for file sizes. So if the
size of a file is larger than 2^32 minus some bytes for
packet headers, we switch to partial length encoding. */
if ( tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
filesize = tmpsize;
else
filesize = 0;
}
else
filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
/* Register the cipher filter. */
if (mode)
iobuf_push_filter (out,
cfx.dek->use_aead? cipher_filter_aead
/**/ : cipher_filter_cfb,
&cfx );
if (do_compress
&& cfx.dek
&& (cfx.dek->use_mdc || cfx.dek->use_aead)
&& !opt.explicit_compress_option
&& is_file_compressed (inp))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), filename? filename: "[stdin]");
do_compress = 0;
}
if (!opt.no_literal)
{
/* Note that PT has been initialized above in !no_literal mode. */
pt->timestamp = make_timestamp();
pt->mode = opt.mimemode? 'm' : opt.textmode? 't' : 'b';
pt->len = filesize;
pt->new_ctb = !pt->len;
pt->buf = inp;
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
cfx.datalen = filesize && !do_compress ? calc_packet_length( &pkt ) : 0;
}
else
{
cfx.datalen = filesize && !do_compress ? filesize : 0;
pkt.pkttype = 0;
pkt.pkt.generic = NULL;
}
/* Register the compress filter. */
if ( do_compress )
{
if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead))
zfx.new_ctb = 1;
push_compress_filter (out, &zfx, default_compress_algo());
}
/* Do the work. */
if (!opt.no_literal)
{
if ( (rc = build_packet( out, &pkt )) )
log_error("build_packet failed: %s\n", gpg_strerror (rc) );
}
else
{
/* User requested not to create a literal packet, so we copy the
plain data. */
rc = iobuf_copy (out, inp);
if (rc)
log_error ("copying input to output failed: %s\n", gpg_strerror (rc));
}
/* Finish the stuff. */
iobuf_close (inp);
if (rc)
iobuf_cancel(out);
else
{
iobuf_close (out); /* fixme: check returncode */
if (mode)
write_status ( STATUS_END_ENCRYPTION );
}
if (pt)
pt->buf = NULL;
free_packet (&pkt, NULL);
xfree (enckey);
xfree (cfx.dek);
xfree (s2k);
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
gpg_error_t
setup_symkey (STRING2KEY **symkey_s2k, DEK **symkey_dek)
{
int canceled;
int defcipher;
int s2kdigest;
defcipher = default_cipher_algo ();
if (openpgp_cipher_blocklen (defcipher) < 16
&& !opt.flags.allow_old_cipher_algos)
{
log_error (_("cipher algorithm '%s' may not be used for encryption\n"),
openpgp_cipher_algo_name (defcipher));
if (!opt.quiet)
log_info (_("(use option \"%s\" to override)\n"),
"--allow-old-cipher-algos");
return gpg_error (GPG_ERR_CIPHER_ALGO);
}
if (!gnupg_cipher_is_allowed (opt.compliance, 1, defcipher,
GCRY_CIPHER_MODE_CFB))
{
log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
openpgp_cipher_algo_name (defcipher),
gnupg_compliance_option_string (opt.compliance));
return gpg_error (GPG_ERR_CIPHER_ALGO);
}
s2kdigest = S2K_DIGEST_ALGO;
if (!gnupg_digest_is_allowed (opt.compliance, 1, s2kdigest))
{
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (s2kdigest),
gnupg_compliance_option_string (opt.compliance));
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
*symkey_s2k = xmalloc_clear (sizeof **symkey_s2k);
(*symkey_s2k)->mode = opt.s2k_mode;
(*symkey_s2k)->hash_algo = s2kdigest;
*symkey_dek = passphrase_to_dek (defcipher,
*symkey_s2k, 1, 0, NULL, 0, &canceled);
if (!*symkey_dek || !(*symkey_dek)->keylen)
{
xfree(*symkey_dek);
xfree(*symkey_s2k);
return gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_INV_PASSPHRASE);
}
return 0;
}
static int
write_symkey_enc (STRING2KEY *symkey_s2k, aead_algo_t aead_algo,
DEK *symkey_dek, DEK *dek, iobuf_t out)
{
int rc;
void *enckey;
size_t enckeylen;
PKT_symkey_enc *enc;
PACKET pkt;
rc = encrypt_seskey (symkey_dek, aead_algo, &dek, &enckey, &enckeylen);
if (rc)
return rc;
enc = xtrycalloc (1, sizeof (PKT_symkey_enc) + enckeylen);
if (!enc)
{
rc = gpg_error_from_syserror ();
xfree (enckey);
return rc;
}
enc->version = aead_algo? 5 : 4;
enc->cipher_algo = opt.s2k_cipher_algo;
enc->aead_algo = aead_algo;
enc->s2k = *symkey_s2k;
enc->seskeylen = enckeylen;
memcpy (enc->seskey, enckey, enckeylen);
xfree (enckey);
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = enc;
if ((rc=build_packet(out,&pkt)))
log_error("build symkey_enc packet failed: %s\n",gpg_strerror (rc));
xfree (enc);
return rc;
}
/*
* Encrypt the file with the given userids (or ask if none is
* supplied). Either FILENAME or FILEFD must be given, but not both.
* The caller may provide a checked list of public keys in
* PROVIDED_KEYS; if not the function builds a list of keys on its own.
*
* Note that FILEFD is currently only used by cmd_encrypt in the
* not yet finished server.c.
*/
int
encrypt_crypt (ctrl_t ctrl, gnupg_fd_t filefd, const char *filename,
strlist_t remusr, int use_symkey, pk_list_t provided_keys,
gnupg_fd_t outputfd)
{
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
PKT_plaintext *pt = NULL;
DEK *symkey_dek = NULL;
STRING2KEY *symkey_s2k = NULL;
int rc = 0;
u32 filesize;
cipher_filter_context_t cfx;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
text_filter_context_t tfx;
progress_filter_context_t *pfx;
PK_LIST pk_list;
int do_compress;
if (filefd != GNUPG_INVALID_FD && filename)
return gpg_error (GPG_ERR_INV_ARG); /* Both given. */
do_compress = !!opt.compress_algo;
pfx = new_progress_context ();
memset( &cfx, 0, sizeof cfx);
memset( &zfx, 0, sizeof zfx);
memset( &tfx, 0, sizeof tfx);
init_packet(&pkt);
if (use_symkey
&& (rc=setup_symkey(&symkey_s2k,&symkey_dek)))
{
release_progress_context (pfx);
return rc;
}
if (provided_keys)
pk_list = provided_keys;
else
{
if ((rc = build_pk_list (ctrl, remusr, &pk_list)))
{
release_progress_context (pfx);
return rc;
}
}
/* Prepare iobufs. */
#ifdef HAVE_W32_SYSTEM
if (filefd == GNUPG_INVALID_FD)
inp = iobuf_open (filename);
else
{
inp = NULL;
gpg_err_set_errno (ENOSYS);
}
#else
if (filefd == GNUPG_INVALID_FD)
inp = iobuf_open (filename);
else
inp = iobuf_fdopen_nc (filefd, "rb");
#endif
if (inp)
iobuf_ioctl (inp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
char xname[64];
rc = gpg_error_from_syserror ();
if (filefd != GNUPG_INVALID_FD)
snprintf (xname, sizeof xname, "[fd %d]", FD_DBG (filefd));
else if (!filename)
strcpy (xname, "[stdin]");
else
*xname = 0;
log_error (_("can't open '%s': %s\n"),
*xname? xname : filename, gpg_strerror (rc) );
goto leave;
}
if (opt.verbose)
log_info (_("reading from '%s'\n"), iobuf_get_fname_nonnull (inp));
handle_progress (pfx, inp, filename);
if (opt.textmode)
iobuf_push_filter (inp, text_filter, &tfx);
rc = open_outfile (outputfd, filename, opt.armor? 1:0, 0, &out);
if (rc)
goto leave;
if (opt.armor)
{
afx = new_armor_context ();
push_armor_filter (afx, out);
}
/* Create a session key. */
cfx.dek = create_dek_with_warnings (pk_list);
rc = check_encryption_compliance (cfx.dek, pk_list);
if (rc)
goto leave;
cfx.dek->use_aead = use_aead (pk_list, cfx.dek->algo);
if (!cfx.dek->use_aead)
cfx.dek->use_mdc = !!use_mdc (pk_list, cfx.dek->algo);
make_session_key (cfx.dek);
if (DBG_CRYPTO)
log_printhex (cfx.dek->key, cfx.dek->keylen, "DEK is: ");
rc = write_pubkey_enc_from_list (ctrl, pk_list, cfx.dek, out);
if (rc)
goto leave;
/* We put the passphrase (if any) after any public keys as this
* seems to be the most useful on the recipient side - there is no
* point in prompting a user for a passphrase if they have the
* secret key needed to decrypt. */
if (use_symkey && (rc = write_symkey_enc (symkey_s2k, cfx.dek->use_aead,
symkey_dek, cfx.dek, out)))
goto leave;
if (!opt.no_literal)
pt = setup_plaintext_name (filename, inp);
/* Get the size of the file if possible, i.e., if it is a real file. */
if (filename && *filename
&& !iobuf_is_pipe_filename (filename) && !opt.textmode )
{
uint64_t tmpsize;
tmpsize = iobuf_get_filelength (inp);
if (!tmpsize && opt.verbose)
log_info(_("WARNING: '%s' is an empty file\n"), filename );
/* We can't encode the length of very large files because
OpenPGP uses only 32 bit for file sizes. So if the size
of a file is larger than 2^32 minus some bytes for packet
headers, we switch to partial length encoding. */
if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536) )
filesize = tmpsize;
else
filesize = 0;
}
else
filesize = opt.set_filesize ? opt.set_filesize : 0; /* stdin */
/* Register the cipher filter. */
iobuf_push_filter (out,
cfx.dek->use_aead? cipher_filter_aead
/**/ : cipher_filter_cfb,
&cfx);
/* Only do the is-file-already-compressed check if we are using a
* MDC or AEAD. This forces compressed files to be re-compressed if
* we do not have a MDC to give some protection against chosen
* ciphertext attacks. */
if (do_compress
&& (cfx.dek->use_mdc || cfx.dek->use_aead)
&& !opt.explicit_compress_option
&& is_file_compressed (inp))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), filename? filename: "[stdin]");
do_compress = 0;
}
if (!opt.no_literal)
{
pt->timestamp = make_timestamp();
pt->mode = opt.mimemode? 'm' : opt.textmode ? 't' : 'b';
pt->len = filesize;
pt->new_ctb = !pt->len;
pt->buf = inp;
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
cfx.datalen = filesize && !do_compress? calc_packet_length( &pkt ) : 0;
}
else
cfx.datalen = filesize && !do_compress ? filesize : 0;
/* Register the compress filter. */
if (do_compress)
{
int compr_algo = opt.compress_algo;
if (compr_algo == -1)
{
compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP, -1, NULL);
if (compr_algo == -1)
compr_algo = DEFAULT_COMPRESS_ALGO;
/* Theoretically impossible to get here since uncompressed
is implicit. */
}
else if (!opt.expert
&& select_algo_from_prefs(pk_list, PREFTYPE_ZIP,
compr_algo, NULL) != compr_algo)
{
log_info (_("WARNING: forcing compression algorithm %s (%d)"
" violates recipient preferences\n"),
compress_algo_to_string(compr_algo), compr_algo);
}
/* Algo 0 means no compression. */
if (compr_algo)
{
if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead))
zfx.new_ctb = 1;
push_compress_filter (out,&zfx,compr_algo);
}
}
/* Do the work. */
if (!opt.no_literal)
{
if ((rc = build_packet( out, &pkt )))
log_error ("build_packet failed: %s\n", gpg_strerror (rc));
}
else
{
/* User requested not to create a literal packet, so we copy the
plain data. */
byte copy_buffer[4096];
int bytes_copied;
while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1)
{
rc = iobuf_write (out, copy_buffer, bytes_copied);
if (rc)
{
log_error ("copying input to output failed: %s\n",
gpg_strerror (rc));
break;
}
}
wipememory (copy_buffer, 4096); /* Burn the buffer. */
}
/* Finish the stuff. */
leave:
iobuf_close (inp);
if (rc)
iobuf_cancel (out);
else
{
iobuf_close (out); /* fixme: check returncode */
write_status (STATUS_END_ENCRYPTION);
}
if (pt)
pt->buf = NULL;
free_packet (&pkt, NULL);
xfree (cfx.dek);
xfree (symkey_dek);
xfree (symkey_s2k);
if (!provided_keys)
release_pk_list (pk_list);
release_armor_context (afx);
release_progress_context (pfx);
return rc;
}
/*
* Filter to do a complete public key encryption.
*/
int
encrypt_filter (void *opaque, int control,
iobuf_t a, byte *buf, size_t *ret_len)
{
size_t size = *ret_len;
encrypt_filter_context_t *efx = opaque;
int rc = 0;
if (control == IOBUFCTRL_UNDERFLOW) /* decrypt */
{
BUG(); /* not used */
}
else if ( control == IOBUFCTRL_FLUSH ) /* encrypt */
{
if ( !efx->header_okay )
{
efx->header_okay = 1;
efx->cfx.dek = create_dek_with_warnings (efx->pk_list);
rc = check_encryption_compliance (efx->cfx.dek, efx->pk_list);
if (rc)
return rc;
efx->cfx.dek->use_aead = use_aead (efx->pk_list, efx->cfx.dek->algo);
if (!efx->cfx.dek->use_aead)
efx->cfx.dek->use_mdc = !!use_mdc (efx->pk_list,efx->cfx.dek->algo);
make_session_key ( efx->cfx.dek );
if (DBG_CRYPTO)
log_printhex (efx->cfx.dek->key, efx->cfx.dek->keylen, "DEK is: ");
rc = write_pubkey_enc_from_list (efx->ctrl,
efx->pk_list, efx->cfx.dek, a);
if (rc)
return rc;
if(efx->symkey_s2k && efx->symkey_dek)
{
rc = write_symkey_enc (efx->symkey_s2k, efx->cfx.dek->use_aead,
efx->symkey_dek, efx->cfx.dek, a);
if (rc)
return rc;
}
iobuf_push_filter (a,
efx->cfx.dek->use_aead? cipher_filter_aead
/**/ : cipher_filter_cfb,
&efx->cfx);
}
rc = iobuf_write (a, buf, size);
}
else if (control == IOBUFCTRL_FREE)
{
xfree (efx->symkey_dek);
xfree (efx->symkey_s2k);
}
else if ( control == IOBUFCTRL_DESC )
{
mem2str (buf, "encrypt_filter", *ret_len);
}
return rc;
}
/*
* Write a pubkey-enc packet for the public key PK to OUT.
*/
int
write_pubkey_enc (ctrl_t ctrl,
PKT_public_key *pk, int throw_keyid, DEK *dek, iobuf_t out)
{
PACKET pkt;
PKT_pubkey_enc *enc;
int rc;
gcry_mpi_t frame;
print_pubkey_algo_note ( pk->pubkey_algo );
enc = xmalloc_clear ( sizeof *enc );
enc->pubkey_algo = pk->pubkey_algo;
keyid_from_pk( pk, enc->keyid );
enc->throw_keyid = throw_keyid;
enc->seskey_algo = dek->algo; /* (Used only by PUBKEY_ALGO_KYBER.) */
/* Okay, what's going on: We have the session key somewhere in
* the structure DEK and want to encode this session key in an
* integer value of n bits. pubkey_nbits gives us the number of
* bits we have to use. We then encode the session key in some
- * way and we get it back in the big intger value FRAME. Then
+ * way and we get it back in the big integer value FRAME. Then
* we use FRAME, the public key PK->PKEY and the algorithm
* number PK->PUBKEY_ALGO and pass it to pubkey_encrypt which
* returns the encrypted value in the array ENC->DATA. This
* array has a size which depends on the used algorithm (e.g. 2
* for Elgamal). We don't need frame anymore because we have
* everything now in enc->data which is the passed to
* build_packet(). */
frame = encode_session_key (pk->pubkey_algo, dek,
pubkey_nbits (pk->pubkey_algo, pk->pkey));
rc = pk_encrypt (pk, frame, dek->algo, enc->data);
gcry_mpi_release (frame);
if (rc)
log_error ("pubkey_encrypt failed: %s\n", gpg_strerror (rc) );
else
{
if ( opt.verbose )
{
char *ustr = get_user_id_string_native (ctrl, enc->keyid);
if ((pk->pubkey_usage & PUBKEY_USAGE_RENC))
{
char *tmpustr = xstrconcat (ustr, " [ADSK]", NULL);
xfree (ustr);
ustr = tmpustr;
}
log_info (_("%s/%s.%s encrypted for: \"%s\"\n"),
openpgp_pk_algo_name (enc->pubkey_algo),
openpgp_cipher_algo_name (dek->algo),
dek->use_aead? openpgp_aead_algo_name (dek->use_aead)
/**/ : "CFB",
ustr );
xfree (ustr);
}
/* And write it. */
init_packet (&pkt);
pkt.pkttype = PKT_PUBKEY_ENC;
pkt.pkt.pubkey_enc = enc;
rc = build_packet (out, &pkt);
if (rc)
log_error ("build_packet(pubkey_enc) failed: %s\n",
gpg_strerror (rc));
}
free_pubkey_enc(enc);
return rc;
}
/*
* Write pubkey-enc packets from the list of PKs to OUT.
*/
static int
write_pubkey_enc_from_list (ctrl_t ctrl, PK_LIST pk_list, DEK *dek, iobuf_t out)
{
if (opt.throw_keyids && (PGP7 || PGP8))
{
log_info(_("option '%s' may not be used in %s mode\n"),
"--throw-keyids",
gnupg_compliance_option_string (opt.compliance));
compliance_failure();
}
for ( ; pk_list; pk_list = pk_list->next )
{
PKT_public_key *pk = pk_list->pk;
int throw_keyid = (opt.throw_keyids || (pk_list->flags&1));
int rc = write_pubkey_enc (ctrl, pk, throw_keyid, dek, out);
if (rc)
return rc;
}
return 0;
}
void
encrypt_crypt_files (ctrl_t ctrl, int nfiles, char **files, strlist_t remusr)
{
int rc = 0;
if (opt.outfile)
{
log_error(_("--output doesn't work for this command\n"));
return;
}
if (!nfiles)
{
char line[2048];
unsigned int lno = 0;
while ( fgets(line, DIM(line), stdin) )
{
lno++;
if (!*line || line[strlen(line)-1] != '\n')
{
log_error("input line %u too long or missing LF\n", lno);
return;
}
line[strlen(line)-1] = '\0';
print_file_status(STATUS_FILE_START, line, 2);
rc = encrypt_crypt (ctrl, GNUPG_INVALID_FD, line, remusr,
0, NULL, GNUPG_INVALID_FD);
if (rc)
log_error ("encryption of '%s' failed: %s\n",
print_fname_stdin(line), gpg_strerror (rc) );
write_status( STATUS_FILE_DONE );
}
}
else
{
while (nfiles--)
{
print_file_status(STATUS_FILE_START, *files, 2);
if ((rc = encrypt_crypt (ctrl, GNUPG_INVALID_FD, *files, remusr,
0, NULL, GNUPG_INVALID_FD)))
log_error("encryption of '%s' failed: %s\n",
print_fname_stdin(*files), gpg_strerror (rc) );
write_status( STATUS_FILE_DONE );
files++;
}
}
}
diff --git a/g10/export.c b/g10/export.c
index 2417edef1..5865ae23a 100644
--- a/g10/export.c
+++ b/g10/export.c
@@ -1,3181 +1,3181 @@
/* export.c - Export keys in the OpenPGP defined format.
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004,
* 2005, 2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2016 Werner Koch
* Copyright (C) 2022 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/membuf.h"
#include "../common/host2net.h"
#include "../common/zb32.h"
#include "../common/recsel.h"
#include "../common/mbox-util.h"
#include "../common/init.h"
#include "trustdb.h"
#include "call-agent.h"
#include "key-clean.h"
#include "pkglue.h"
/* An object to keep track of subkeys. */
struct subkey_list_s
{
struct subkey_list_s *next;
u32 kid[2];
};
typedef struct subkey_list_s *subkey_list_t;
/* An object to track statistics for export operations. */
struct export_stats_s
{
ulong count; /* Number of processed keys. */
ulong secret_count; /* Number of secret keys seen. */
ulong exported; /* Number of actual exported keys. */
};
/* Global variables to store the selectors created from
* --export-filter keep-uid=EXPR.
* --export-filter drop-subkey=EXPR.
* --export-filter select=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now.
*/
static recsel_expr_t export_keep_uid;
static recsel_expr_t export_drop_subkey;
static recsel_expr_t export_select_filter;
/* An object used for a linked list to implement the
* push_export_filter/pop_export_filters functions. */
struct export_filter_attic_s
{
struct export_filter_attic_s *next;
recsel_expr_t export_keep_uid;
recsel_expr_t export_drop_subkey;
recsel_expr_t export_select_filter;
};
static struct export_filter_attic_s *export_filter_attic;
/* Local prototypes. */
static int do_export (ctrl_t ctrl, strlist_t users, int secret,
unsigned int options, export_stats_t stats);
static int do_export_stream (ctrl_t ctrl, iobuf_t out,
strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any);
static gpg_error_t print_dane_records
/**/ (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen);
static void
cleanup_export_globals (void)
{
recsel_release (export_keep_uid);
export_keep_uid = NULL;
recsel_release (export_drop_subkey);
export_drop_subkey = NULL;
recsel_release (export_select_filter);
export_select_filter = NULL;
}
/* Option parser for export options. See parse_options for
details. */
int
parse_export_options(char *str,unsigned int *options,int noisy)
{
struct parse_options export_opts[]=
{
{"export-local-sigs",EXPORT_LOCAL_SIGS,NULL,
N_("export signatures that are marked as local-only")},
{"export-attributes",EXPORT_ATTRIBUTES,NULL,
N_("export attribute user IDs (generally photo IDs)")},
{"export-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,
N_("export revocation keys marked as \"sensitive\"")},
{"export-clean",EXPORT_CLEAN,NULL,
N_("remove unusable parts from key during export")},
{"export-realclean",EXPORT_MINIMAL|EXPORT_REALCLEAN|EXPORT_CLEAN,NULL,
NULL},
{"export-minimal",EXPORT_MINIMAL|EXPORT_CLEAN,NULL,
N_("remove as much as possible from key during export")},
{"export-dane", EXPORT_DANE_FORMAT, NULL, NULL },
{"export-revocs", EXPORT_REVOCS, NULL,
N_("export only revocation certificates") },
{"backup", EXPORT_BACKUP, NULL,
N_("use the GnuPG key backup format")},
{"export-backup", EXPORT_BACKUP, NULL, NULL },
{"mode1003", EXPORT_MODE1003, NULL,
N_("export secret keys using the GnuPG format") },
/* Aliases for backward compatibility */
{"include-local-sigs",EXPORT_LOCAL_SIGS,NULL,NULL},
{"include-attributes",EXPORT_ATTRIBUTES,NULL,NULL},
{"include-sensitive-revkeys",EXPORT_SENSITIVE_REVKEYS,NULL,NULL},
/* dummy */
{"export-unusable-sigs",0,NULL,NULL},
{"export-clean-sigs",0,NULL,NULL},
{"export-clean-uids",0,NULL,NULL},
{NULL,0,NULL,NULL}
/* add tags for include revoked and disabled? */
};
int rc;
rc = parse_options (str, options, export_opts, noisy);
if (!rc)
return 0;
/* Alter other options we want or don't want for restore. */
if ((*options & EXPORT_BACKUP))
{
*options |= (EXPORT_LOCAL_SIGS | EXPORT_ATTRIBUTES
| EXPORT_SENSITIVE_REVKEYS);
*options &= ~(EXPORT_CLEAN | EXPORT_MINIMAL | EXPORT_REALCLEAN
| EXPORT_DANE_FORMAT);
}
return rc;
}
/* Parse and set an export filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is called several
* times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be exported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*
* - drop-subkey :: If the expression evaluates to true for a subkey
* packet that subkey and all it dependencies will be
* remove from the keyblock. The expression may use these
* variables:
*
* - secret :: 1 for a secret subkey, else 0.
* - key_algo :: Public key algorithm id
*
* - select :: The key is only exported if the filter returns true.
*/
gpg_error_t
parse_and_set_export_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_export_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&export_keep_uid, string+9);
else if (!strncmp (string, "drop-subkey=", 12))
err = recsel_parse_expr (&export_drop_subkey, string+12);
else if (!strncmp (string, "select=", 7))
err = recsel_parse_expr (&export_select_filter, string+7);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Push the current export filters onto a stack so that new export
* filters can be defined which will be active until the next
* pop_export_filters or another push_export_filters. */
void
push_export_filters (void)
{
struct export_filter_attic_s *item;
item = xcalloc (1, sizeof *item);
item->export_keep_uid = export_keep_uid;
export_keep_uid = NULL;
item->export_drop_subkey = export_drop_subkey;
export_drop_subkey = NULL;
item->export_select_filter = export_select_filter;
export_select_filter = NULL;
item->next = export_filter_attic;
export_filter_attic = item;
}
/* Revert the last push_export_filters. */
void
pop_export_filters (void)
{
struct export_filter_attic_s *item;
item = export_filter_attic;
if (!item)
BUG (); /* No corresponding push. */
export_filter_attic = item->next;
cleanup_export_globals ();
export_keep_uid = item->export_keep_uid;
export_drop_subkey = item->export_drop_subkey;
export_select_filter = item->export_select_filter;
}
/* Create a new export stats object initialized to zero. On error
returns NULL and sets ERRNO. */
export_stats_t
export_new_stats (void)
{
export_stats_t stats;
return xtrycalloc (1, sizeof *stats);
}
/* Release an export stats object. */
void
export_release_stats (export_stats_t stats)
{
xfree (stats);
}
/* Print export statistics using the status interface. */
void
export_print_stats (export_stats_t stats)
{
if (!stats)
return;
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf, "%lu %lu %lu",
stats->count,
stats->secret_count,
stats->exported );
write_status_text (STATUS_EXPORT_RES, buf);
}
}
/*
* Export public keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all keys will be exported. STATS is
* either an export stats object for update or NULL.
*
* This function is the core of "gpg --export".
*/
int
export_pubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 0, options, stats);
}
/*
* Export secret keys (to stdout or to --output FILE).
*
* Depending on opt.armor the output is armored. OPTIONS are defined
* in main.h. If USERS is NULL, all secret keys will be exported.
* STATS is either an export stats object for update or NULL.
*
* This function is the core of "gpg --export-secret-keys".
*/
int
export_seckeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 1, options, stats);
}
/*
* Export secret sub keys (to stdout or to --output FILE).
*
* This is the same as export_seckeys but replaces the primary key by
* a stub key. Depending on opt.armor the output is armored. OPTIONS
* are defined in main.h. If USERS is NULL, all secret subkeys will
* be exported. STATS is either an export stats object for update or
* NULL.
*
* This function is the core of "gpg --export-secret-subkeys".
*/
int
export_secsubkeys (ctrl_t ctrl, strlist_t users, unsigned int options,
export_stats_t stats)
{
return do_export (ctrl, users, 2, options, stats);
}
/*
* Export a single key into a memory buffer. STATS is either an
* export stats object for update or NULL. If PREFIX is not NULL
* PREFIXLEN bytes from PREFIX are prepended to the R_DATA.
*/
gpg_error_t
export_pubkey_buffer (ctrl_t ctrl, const char *keyspec, unsigned int options,
const void *prefix, size_t prefixlen,
export_stats_t stats,
kbnode_t *r_keyblock, void **r_data, size_t *r_datalen)
{
gpg_error_t err;
iobuf_t iobuf;
int any;
strlist_t helplist;
*r_keyblock = NULL;
*r_data = NULL;
*r_datalen = 0;
helplist = NULL;
if (!add_to_strlist_try (&helplist, keyspec))
return gpg_error_from_syserror ();
iobuf = iobuf_temp ();
if (prefix && prefixlen)
iobuf_write (iobuf, prefix, prefixlen);
err = do_export_stream (ctrl, iobuf, helplist, 0, r_keyblock, options,
stats, &any);
if (!err && !any)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (!err)
{
const void *src;
size_t datalen;
iobuf_flush_temp (iobuf);
src = iobuf_get_temp_buffer (iobuf);
datalen = iobuf_get_temp_length (iobuf);
if (!datalen)
err = gpg_error (GPG_ERR_NO_PUBKEY);
else if (!(*r_data = xtrymalloc (datalen)))
err = gpg_error_from_syserror ();
else
{
memcpy (*r_data, src, datalen);
*r_datalen = datalen;
}
}
iobuf_close (iobuf);
free_strlist (helplist);
if (err && *r_keyblock)
{
release_kbnode (*r_keyblock);
*r_keyblock = NULL;
}
return err;
}
/* Export the keys identified by the list of strings in USERS. If
Secret is false public keys will be exported. With secret true
secret keys will be exported; in this case 1 means the entire
secret keyblock and 2 only the subkeys. OPTIONS are the export
options to apply. */
static int
do_export (ctrl_t ctrl, strlist_t users, int secret, unsigned int options,
export_stats_t stats)
{
IOBUF out = NULL;
int any, rc;
armor_filter_context_t *afx = NULL;
compress_filter_context_t zfx;
memset( &zfx, 0, sizeof zfx);
rc = open_outfile (GNUPG_INVALID_FD, NULL, 0, !!secret, &out);
if (rc)
return rc;
if ( opt.armor && !(options & EXPORT_DANE_FORMAT) )
{
afx = new_armor_context ();
afx->what = secret? 5 : 1;
push_armor_filter (afx, out);
}
rc = do_export_stream (ctrl, out, users, secret, NULL, options, stats, &any);
if ( rc || !any )
iobuf_cancel (out);
else
iobuf_close (out);
release_armor_context (afx);
return rc;
}
/* Release an entire subkey list. */
static void
release_subkey_list (subkey_list_t list)
{
while (list)
{
subkey_list_t tmp = list->next;;
xfree (list);
list = tmp;
}
}
/* Returns true if NODE is a subkey and contained in LIST. */
static int
subkey_in_list_p (subkey_list_t list, KBNODE node)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
u32 kid[2];
keyid_from_pk (node->pkt->pkt.public_key, kid);
for (; list; list = list->next)
if (list->kid[0] == kid[0] && list->kid[1] == kid[1])
return 1;
}
return 0;
}
/* Allocate a new subkey list item from NODE. */
static subkey_list_t
new_subkey_list_item (KBNODE node)
{
subkey_list_t list = xcalloc (1, sizeof *list);
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
keyid_from_pk (node->pkt->pkt.public_key, list->kid);
return list;
}
/* Helper function to check whether the subkey at NODE actually
matches the description at DESC. The function returns true if the
key under question has been specified by an exact specification
(keyID or fingerprint) and does match the one at NODE. It is
assumed that the packet at NODE is either a public or secret
subkey. */
int
exact_subkey_match_p (KEYDB_SEARCH_DESC *desc, kbnode_t node)
{
u32 kid[2];
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
int result = 0;
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
case KEYDB_SEARCH_MODE_LONG_KID:
keyid_from_pk (node->pkt->pkt.public_key, kid);
break;
case KEYDB_SEARCH_MODE_FPR:
fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen);
break;
default:
break;
}
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_SHORT_KID:
if (desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_LONG_KID:
if (desc->u.kid[0] == kid[0] && desc->u.kid[1] == kid[1])
result = 1;
break;
case KEYDB_SEARCH_MODE_FPR:
if (fprlen == desc->fprlen && !memcmp (desc->u.fpr, fpr, desc->fprlen))
result = 1;
break;
default:
break;
}
return result;
}
/* Return an error if the key represented by the S-expression S_KEY
* and the OpenPGP key represented by PK do not use the same curve. */
static gpg_error_t
match_curve_skey_pk (gcry_sexp_t s_key, PKT_public_key *pk)
{
gcry_sexp_t curve = NULL;
gcry_sexp_t flags = NULL;
char *curve_str = NULL;
char *flag;
const char *oidstr = NULL;
gcry_mpi_t curve_as_mpi = NULL;
gpg_error_t err;
int is_eddsa = 0;
int idx = 0;
if (!(pk->pubkey_algo==PUBKEY_ALGO_ECDH
|| pk->pubkey_algo==PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo==PUBKEY_ALGO_EDDSA))
return gpg_error (GPG_ERR_PUBKEY_ALGO);
curve = gcry_sexp_find_token (s_key, "curve", 0);
if (!curve)
{
log_error ("no reported curve\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
curve_str = gcry_sexp_nth_string (curve, 1);
gcry_sexp_release (curve); curve = NULL;
if (!curve_str)
{
log_error ("no curve name\n");
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
if (!strcmp (curve_str, "Ed448"))
is_eddsa = 1;
oidstr = openpgp_curve_to_oid (curve_str, NULL, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve_str);
xfree (curve_str);
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
}
xfree (curve_str);
err = openpgp_oid_from_str (oidstr, &curve_as_mpi);
if (err)
return err;
if (gcry_mpi_cmp (pk->pkey[0], curve_as_mpi))
{
log_error ("curves do not match\n");
gcry_mpi_release (curve_as_mpi);
return gpg_error (GPG_ERR_INV_CURVE);
}
gcry_mpi_release (curve_as_mpi);
flags = gcry_sexp_find_token (s_key, "flags", 0);
if (flags)
{
for (idx = 1; idx < gcry_sexp_length (flags); idx++)
{
flag = gcry_sexp_nth_string (flags, idx);
if (flag && (strcmp ("eddsa", flag) == 0))
is_eddsa = 1;
gcry_free (flag);
}
}
if (is_eddsa != (pk->pubkey_algo == PUBKEY_ALGO_EDDSA))
{
log_error ("disagreement about EdDSA\n");
err = gpg_error (GPG_ERR_INV_CURVE);
}
return err;
}
/* Return a canonicalized public key algorithms. This is used to
compare different flavors of algorithms (e.g. ELG and ELG_E are
considered the same). */
static enum gcry_pk_algos
canon_pk_algo (enum gcry_pk_algos algo)
{
switch (algo)
{
case GCRY_PK_RSA:
case GCRY_PK_RSA_E:
case GCRY_PK_RSA_S: return GCRY_PK_RSA;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E: return GCRY_PK_ELG;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH: return GCRY_PK_ECC;
default: return algo;
}
}
/* Take an s-expression with the public and private key and change the
* parameter array in PK to include the secret parameters. */
static gpg_error_t
secret_key_to_mode1003 (gcry_sexp_t s_key, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t list = NULL;
gcry_sexp_t l2;
enum gcry_pk_algos pk_algo;
struct seckey_info *ski;
int idx;
char *string;
size_t npkey, nskey;
gcry_mpi_t pub_params[10] = { NULL };
/* We look for a private-key, then the first element in it tells us
the type */
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)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
log_assert (!pk->seckey_info);
/* Parse the gcrypt PK algo and check that it is okay. */
l2 = gcry_sexp_cadr (list);
if (!l2)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
gcry_sexp_release (list);
list = l2;
string = gcry_sexp_nth_string (list, 0);
if (!string)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
pk_algo = gcry_pk_map_name (string);
xfree (string); string = NULL;
if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
|| gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
|| !npkey || npkey >= nskey)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Check that the pubkey algo and the received parameters matches
* those from the public key. */
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo) || npkey != 2)
err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */
else
err = gcry_sexp_extract_param (list, NULL, "ne",
&pub_params[0],
&pub_params[1],
NULL);
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo) || npkey != 4)
err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */
else
err = gcry_sexp_extract_param (list, NULL, "pqgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
&pub_params[3],
NULL);
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo) || npkey != 3)
err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */
else
err = gcry_sexp_extract_param (list, NULL, "pgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
NULL);
break;
case GCRY_PK_ECC:
err = 0;
if (!(pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA))
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Does not match. */
goto leave;
}
npkey = 2;
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
npkey++;
/* Dedicated check of the curve. */
pub_params[0] = NULL;
err = match_curve_skey_pk (list, pk);
if (err)
goto leave;
/* ... and of the Q parameter. */
err = sexp_extract_param_sos (list, "q", &pub_params[1]);
if (!err && (gcry_mpi_cmp (pk->pkey[1], pub_params[1])))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO); /* Unknown. */
break;
}
if (err)
goto leave;
nskey = npkey + 1; /* We only have one skey param. */
if (nskey > PUBKEY_MAX_NSKEY)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Check that the public key parameters match. For ECC we already
* did this in the switch above. */
if (canon_pk_algo (pk_algo) != GCRY_PK_ECC)
{
for (idx=0; idx < npkey; idx++)
if (gcry_mpi_cmp (pk->pkey[idx], pub_params[idx]))
{
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
}
/* Store the maybe protected secret key as an s-expression. */
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
ski->s2k.mode = 1003;
{
unsigned char *buf;
size_t buflen;
err = make_canon_sexp (s_key, &buf, &buflen);
if (err)
goto leave;
pk->pkey[npkey] = gcry_mpi_set_opaque (NULL, buf, buflen*8);
for (idx=npkey+1; idx < PUBKEY_MAX_NSKEY; idx++)
pk->pkey[idx] = NULL;
}
leave:
gcry_sexp_release (list);
for (idx=0; idx < DIM(pub_params); idx++)
gcry_mpi_release (pub_params[idx]);
return err;
}
/* Take a cleartext dump of a secret key in PK and change the
* parameter array in PK to include the secret parameters. */
static gpg_error_t
cleartext_secret_key_to_openpgp (gcry_sexp_t s_key, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t top_list;
gcry_sexp_t key = NULL;
char *key_type = NULL;
enum gcry_pk_algos pk_algo;
struct seckey_info *ski;
int idx, sec_start;
gcry_mpi_t pub_params[10] = { NULL };
/* we look for a private-key, then the first element in it tells us
the type */
top_list = gcry_sexp_find_token (s_key, "private-key", 0);
if (!top_list)
goto bad_seckey;
/* ignore all S-expression after the first sublist -- we assume that
they are comments or otherwise irrelevant to OpenPGP */
if (gcry_sexp_length(top_list) < 2)
goto bad_seckey;
key = gcry_sexp_nth (top_list, 1);
if (!key)
goto bad_seckey;
key_type = gcry_sexp_nth_string(key, 0);
pk_algo = gcry_pk_map_name (key_type);
log_assert (!pk->seckey_info);
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "ne",
&pub_params[0],
&pub_params[1],
NULL);
for (idx=0; idx < 2 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
for (idx = 2; idx < 6 && !err; idx++)
{
gcry_mpi_release (pk->pkey[idx]);
pk->pkey[idx] = NULL;
}
err = gcry_sexp_extract_param (key, NULL, "dpqu",
&pk->pkey[2],
&pk->pkey[3],
&pk->pkey[4],
&pk->pkey[5],
NULL);
}
if (!err)
{
for (idx = 2; idx < 6; idx++)
ski->csum += checksum_mpi (pk->pkey[idx]);
}
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pqgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
&pub_params[3],
NULL);
for (idx=0; idx < 4 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[4]);
pk->pkey[4] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[4],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[4]);
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
goto bad_pubkey_algo;
err = gcry_sexp_extract_param (key, NULL, "pgy",
&pub_params[0],
&pub_params[1],
&pub_params[2],
NULL);
for (idx=0; idx < 3 && !err; idx++)
if (gcry_mpi_cmp(pk->pkey[idx], pub_params[idx]))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
if (!err)
{
gcry_mpi_release (pk->pkey[3]);
pk->pkey[3] = NULL;
err = gcry_sexp_extract_param (key, NULL, "x",
&pk->pkey[3],
NULL);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[3]);
break;
case GCRY_PK_ECC:
err = match_curve_skey_pk (key, pk);
if (err)
goto leave;
else
err = sexp_extract_param_sos (key, "q", &pub_params[0]);
if (!err && (gcry_mpi_cmp(pk->pkey[1], pub_params[0])))
err = gpg_error (GPG_ERR_BAD_PUBKEY);
sec_start = 2;
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
sec_start += 1;
if (!err)
{
gcry_mpi_release (pk->pkey[sec_start]);
pk->pkey[sec_start] = NULL;
err = sexp_extract_param_sos (key, "d", &pk->pkey[sec_start]);
}
if (!err)
ski->csum += checksum_mpi (pk->pkey[sec_start]);
break;
default:
pk->seckey_info = NULL;
xfree (ski);
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
leave:
gcry_sexp_release (top_list);
gcry_sexp_release (key);
gcry_free (key_type);
for (idx=0; idx < DIM(pub_params); idx++)
gcry_mpi_release (pub_params[idx]);
return err;
bad_pubkey_algo:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Use the key transfer format given in S_PGP to create the secinfo
structure in PK and change the parameter array in PK to include the
secret parameters. */
static gpg_error_t
transfer_format_to_openpgp (gcry_sexp_t s_pgp, PKT_public_key *pk)
{
gpg_error_t err;
gcry_sexp_t top_list;
gcry_sexp_t list = NULL;
char *curve = NULL;
const char *value;
size_t valuelen;
char *string;
int idx;
int is_v4, is_protected;
enum gcry_pk_algos pk_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;
int is_ecdh = 0;
size_t npkey, nskey;
gcry_mpi_t skey[10]; /* We support up to 9 parameters. */
int skeyidx = 0;
struct seckey_info *ski;
/* gcry_log_debugsxp ("transferkey", s_pgp); */
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);
}
/* Parse the gcrypt PK algo and check that it is okay. */
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;
pk_algo = gcry_pk_map_name (string);
xfree (string); string = NULL;
if (gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NPKEY, NULL, &npkey)
|| gcry_pk_algo_info (pk_algo, GCRYCTL_GET_ALGO_NSKEY, NULL, &nskey)
|| !npkey || npkey >= nskey)
goto bad_seckey;
/* Check that the pubkey algo matches the one from the public key. */
switch (canon_pk_algo (pk_algo))
{
case GCRY_PK_RSA:
if (!is_RSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_DSA:
if (!is_DSA (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ELG:
if (!is_ELGAMAL (pk->pubkey_algo))
pk_algo = 0; /* Does not match. */
break;
case GCRY_PK_ECC:
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
;
else if (pk->pubkey_algo == PUBKEY_ALGO_ECDH)
is_ecdh = 1;
else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
;
else
pk_algo = 0; /* Does not match. */
/* For ECC we do not have the domain parameters thus fix our info. */
npkey = 1;
nskey = 2;
break;
default:
pk_algo = 0; /* Oops. */
break;
}
if (!pk_algo)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
/* This check has to go after the ecc adjustments. */
if (nskey > PUBKEY_MAX_NSKEY)
goto bad_seckey;
/* Parse the key parameters. */
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
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
unsigned int nbits = valuelen*8;
const unsigned char *p = value;
if (*p && nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
skey[skeyidx] = gcry_mpi_set_opaque_copy (NULL, value, nbits);
if (!skey[skeyidx])
goto outofmem;
if (is_enc)
gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER1);
else
gcry_mpi_set_flag (skey[skeyidx], GCRYMPI_FLAG_USER2);
}
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 = NULL;
/* We have no need for the CSUM value thus we don't parse it. */
/* 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; */
/* Get the curve name if any, */
list = gcry_sexp_find_token (top_list, "curve", 0);
if (list)
{
curve = gcry_sexp_nth_string (list, 1);
gcry_sexp_release (list); list = NULL;
}
gcry_sexp_release (top_list); top_list = NULL;
/* 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); */
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* int is_enc = gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE); */
/* log_info ("XXX skey[%d]%s:", idx, is_enc? " (enc)":""); */
/* if (is_enc) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
if (!is_v4 || is_protected != 2 )
{
/* We only support the v4 format and a SHA-1 checksum. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto leave;
}
/* We need to change the received parameters for ECC algorithms.
The transfer format has the curve name and the parameters
separate. We put them all into the SKEY array. */
if (canon_pk_algo (pk_algo) == GCRY_PK_ECC)
{
const char *oidstr;
/* Assert that all required parameters are available. We also
check that the array does not contain more parameters than
needed (this was used by some beta versions of 2.1. */
if (!curve || !skey[0] || !skey[1] || skey[2])
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
oidstr = openpgp_curve_to_oid (curve, NULL, NULL);
if (!oidstr)
{
log_error ("no OID known for curve '%s'\n", curve);
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto leave;
}
/* Put the curve's OID into the MPI array. This requires
that we shift Q and D. For ECDH also insert the KDF parms. */
if (is_ecdh)
{
skey[4] = NULL;
skey[3] = skey[1];
skey[2] = gcry_mpi_copy (pk->pkey[2]);
}
else
{
skey[3] = NULL;
skey[2] = skey[1];
}
skey[1] = skey[0];
skey[0] = NULL;
err = openpgp_oid_from_str (oidstr, skey + 0);
if (err)
goto leave;
/* Fixup the NPKEY and NSKEY to match OpenPGP reality. */
npkey = 2 + is_ecdh;
nskey = 3 + is_ecdh;
/* for (idx=0; skey[idx]; idx++) */
/* { */
/* log_info ("YYY skey[%d]:", idx); */
/* if (gcry_mpi_get_flag (skey[idx], GCRYMPI_FLAG_OPAQUE)) */
/* { */
/* void *p; */
/* unsigned int nbits; */
/* p = gcry_mpi_get_opaque (skey[idx], &nbits); */
/* log_printhex (NULL, p, (nbits+7)/8); */
/* } */
/* else */
/* gcry_mpi_dump (skey[idx]); */
/* log_printf ("\n"); */
/* } */
}
/* Do some sanity checks. */
if (s2k_count > 255)
{
/* We expect an already encoded S2K count. */
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
err = openpgp_cipher_test_algo (protect_algo);
if (err)
goto leave;
err = openpgp_md_test_algo (s2k_algo);
if (err)
goto leave;
/* Check that the public key parameters match. Note that since
Libgcrypt 1.5 gcry_mpi_cmp handles opaque MPI correctly. */
for (idx=0; idx < npkey; idx++)
if (gcry_mpi_cmp (pk->pkey[idx], skey[idx]))
{
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* Check that the first secret key parameter in SKEY is encrypted
and that there are no more secret key parameters. The latter is
guaranteed by the v4 packet format. */
if (!gcry_mpi_get_flag (skey[npkey], GCRYMPI_FLAG_USER1))
goto bad_seckey;
if (npkey+1 < DIM (skey) && skey[npkey+1])
goto bad_seckey;
/* Check that the secret key parameters in PK are all set to NULL. */
for (idx=npkey; idx < nskey; idx++)
if (pk->pkey[idx])
goto bad_seckey;
/* Now build the protection info. */
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
ski->sha1chk = 1;
ski->algo = protect_algo;
ski->s2k.mode = s2k_mode;
ski->s2k.hash_algo = s2k_algo;
log_assert (sizeof ski->s2k.salt == sizeof s2k_salt);
memcpy (ski->s2k.salt, s2k_salt, sizeof s2k_salt);
ski->s2k.count = s2k_count;
log_assert (ivlen <= sizeof ski->iv);
memcpy (ski->iv, iv, ivlen);
ski->ivlen = ivlen;
/* Store the protected secret key parameter. */
pk->pkey[npkey] = skey[npkey];
skey[npkey] = NULL;
/* That's it. */
leave:
gcry_free (curve);
gcry_sexp_release (list);
gcry_sexp_release (top_list);
for (idx=0; idx < skeyidx; idx++)
gcry_mpi_release (skey[idx]);
return err;
bad_seckey:
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
outofmem:
err = gpg_error (GPG_ERR_ENOMEM);
goto leave;
}
/* Print an "EXPORTED" status line. PK is the primary public key. */
static void
print_status_exported (PKT_public_key *pk)
{
char *hexfpr;
if (!is_status_enabled ())
return;
hexfpr = hexfingerprint (pk, NULL, 0);
write_status_text (STATUS_EXPORTED, hexfpr? hexfpr : "[?]");
xfree (hexfpr);
}
/*
* Receive a secret key from agent specified by HEXGRIP.
*
* Since the key data from the agent is encrypted, decrypt it using
* CIPHERHD context. Then, parse the decrypted key data into transfer
* format, and put secret parameters into PK.
*
* If CLEARTEXT is 0, store the secret key material
* passphrase-protected. Otherwise, store secret key material in the
* clear.
*
* If MODE1003 is set, the key is requested in raw GnuPG format from
* the agent. This usually does not require a passphrase unless the
* gpg-agent has not yet used the key and needs to convert it to its
* internal format first.
*
* CACHE_NONCE_ADDR is used to share nonce for multiple key retrievals.
*
* If PK is NULL, the raw key is returned (e.g. for ssh export) at
* R_KEY. CLEARTEXT and CACHE_NONCE_ADDR ared ignored in this case.
*/
gpg_error_t
receive_seckey_from_agent (ctrl_t ctrl, gcry_cipher_hd_t cipherhd,
int cleartext, int mode1003,
char **cache_nonce_addr, const char *hexgrip,
PKT_public_key *pk, gcry_sexp_t *r_key)
{
gpg_error_t err = 0;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
unsigned char *key = NULL;
size_t keylen, realkeylen;
gcry_sexp_t s_skey = NULL;
char *prompt;
if (r_key)
*r_key = NULL;
if (opt.verbose)
log_info ("key %s: asking agent for the secret parts\n", hexgrip);
if (pk)
{
prompt = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_EXPORT, 1);
err = agent_export_key (ctrl, hexgrip, prompt, !cleartext, mode1003,
cache_nonce_addr,
&wrappedkey, &wrappedkeylen,
pk->keyid, pk->main_keyid, pk->pubkey_algo);
}
else
{
prompt = gpg_format_keydesc (ctrl, NULL, FORMAT_KEYDESC_KEYGRIP, 1);
err = agent_export_key (ctrl, hexgrip, prompt, 0, 0,
NULL,
&wrappedkey, &wrappedkeylen,
NULL, NULL, 0);
}
xfree (prompt);
if (err)
goto unwraperror;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto unwraperror;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto unwraperror;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto unwraperror;
realkeylen = gcry_sexp_canon_len (key, keylen, NULL, &err);
if (!realkeylen)
goto unwraperror; /* Invalid csexp. */
err = gcry_sexp_sscan (&s_skey, NULL, key, realkeylen);
if (!err)
{
if (pk && mode1003)
err = secret_key_to_mode1003 (s_skey, pk);
else if (pk && cleartext)
err = cleartext_secret_key_to_openpgp (s_skey, pk);
else if (pk)
err = transfer_format_to_openpgp (s_skey, pk);
else if (r_key)
{
*r_key = s_skey;
s_skey = NULL;
}
}
unwraperror:
gcry_sexp_release (s_skey);
xfree (key);
xfree (wrappedkey);
if (err)
{
log_error ("key %s: error receiving key from agent:"
" %s%s\n", hexgrip, gpg_strerror (err),
gpg_err_code (err) == GPG_ERR_FULLY_CANCELED?
"":_(" - skipped"));
}
return err;
}
/* Write KEYBLOCK either to stdout or to the file set with the
* --output option. This is a simplified version of do_export_stream
* which supports only a few export options. */
gpg_error_t
write_keyblock_to_output (kbnode_t keyblock, int with_armor,
unsigned int options)
{
gpg_error_t err;
const char *fname;
iobuf_t out;
kbnode_t node;
armor_filter_context_t *afx = NULL;
iobuf_t out_help = NULL;
PKT_public_key *pk = NULL;
fname = opt.outfile? opt.outfile : "-";
if (is_secured_filename (fname) )
return gpg_error (GPG_ERR_EPERM);
out = iobuf_create (fname, 0);
if (!out)
{
err = gpg_error_from_syserror ();
log_error(_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info (_("writing to '%s'\n"), iobuf_get_fname_nonnull (out));
if ((options & EXPORT_DANE_FORMAT))
{
with_armor = 0;
out_help = iobuf_temp ();
}
if (with_armor)
{
afx = new_armor_context ();
afx->what = 1;
push_armor_filter (afx, out);
}
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_RING_TRUST)
continue; /* Skip - they should not be here anyway. */
if (!pk && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY))
pk = node->pkt->pkt.public_key;
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out_help? out_help : out, node->pkt);
else
err = build_packet (out_help? out_help : out, node->pkt);
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err) );
goto leave;
}
}
err = 0;
if (out_help && pk && (options & EXPORT_DANE_FORMAT))
{
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_dane_records (out, keyblock, pk, data, datalen);
}
leave:
if (err)
iobuf_cancel (out);
else
iobuf_close (out);
iobuf_cancel (out_help);
release_armor_context (afx);
return err;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
parm.node = node;
if (!recsel_select (selector, impex_filter_getval, &parm))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-subkey filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_subkey_filter (ctrl_t ctrl, kbnode_t keyblock,
recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
parm.node = node;
if (recsel_select (selector, impex_filter_getval, &parm))
{
/*log_debug ("drop-subkey: deleting a key\n");*/
/* The subkey packet and all following packets up to the
* next subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
}
}
}
/* Print DANErecords for all user IDs in KEYBLOCK to OUT. The data
* for the record is taken from (DATA,DATELEN). PK is the public key
* packet with the primary key. */
static gpg_error_t
print_dane_records (iobuf_t out, kbnode_t keyblock, PKT_public_key *pk,
const void *data, size_t datalen)
{
gpg_error_t err = 0;
kbnode_t kbctx, node;
PKT_user_id *uid;
char *mbox = NULL;
char hashbuf[32];
char *hash = NULL;
char *domain;
const char *s;
unsigned int len;
estream_t fp = NULL;
char *hexdata = NULL;
char *hexfpr;
hexfpr = hexfingerprint (pk, NULL, 0);
if (!hexfpr)
{
err = gpg_error_from_syserror ();
goto leave;
}
hexdata = bin2hex (data, datalen, NULL);
if (!hexdata)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hexdata);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (kbctx = NULL; (node = walk_kbnode (keyblock, &kbctx, 0));)
{
if (node->pkt->pkttype != PKT_USER_ID)
continue;
uid = node->pkt->pkt.user_id;
if (uid->flags.expired || uid->flags.revoked)
continue;
xfree (mbox);
mbox = mailbox_from_userid (uid->name, 0);
if (!mbox)
continue;
domain = strchr (mbox, '@');
*domain++ = 0;
if (1)
{
es_fprintf (fp, "$ORIGIN _openpgpkey.%s.\n; %s\n; ", domain, hexfpr);
print_utf8_buffer (fp, uid->name, uid->len);
es_putc ('\n', fp);
gcry_md_hash_buffer (GCRY_MD_SHA256, hashbuf, mbox, strlen (mbox));
xfree (hash);
hash = bin2hex (hashbuf, 28, NULL);
if (!hash)
{
err = gpg_error_from_syserror ();
goto leave;
}
ascii_strlwr (hash);
len = strlen (hexdata)/2;
es_fprintf (fp, "%s TYPE61 \\# %u (\n", hash, len);
for (s = hexdata; ;)
{
es_fprintf (fp, "\t%.64s\n", s);
if (strlen (s) < 64)
break;
s += 64;
}
es_fputs ("\t)\n\n", fp);
}
}
/* Make sure it is a string and write it. */
es_fputc (0, fp);
{
void *vp;
if (es_fclose_snatch (fp, &vp, NULL))
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = NULL;
iobuf_writestr (out, vp);
es_free (vp);
}
err = 0;
leave:
xfree (hash);
xfree (mbox);
es_fclose (fp);
xfree (hexdata);
xfree (hexfpr);
return err;
}
/* Helper for do_export_stream which writes one keyblock to OUT. */
static gpg_error_t
do_export_one_keyblock (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
iobuf_t out, int secret, unsigned int options,
export_stats_t stats, int *any,
KEYDB_SEARCH_DESC *desc, size_t ndesc,
size_t descindex, gcry_cipher_hd_t cipherhd)
{
gpg_error_t err = gpg_error (GPG_ERR_NOT_FOUND);
char *cache_nonce = NULL;
subkey_list_t subkey_list = NULL; /* Track already processed subkeys. */
int skip_until_subkey = 0;
int cleartext = 0;
char *hexgrip = NULL;
char *serialno = NULL;
PKT_public_key *pk;
u32 subkidbuf[2], *subkid;
kbnode_t kbctx, node;
/* NB: walk_kbnode skips packets marked as deleted. */
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (skip_until_subkey)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
skip_until_subkey = 0;
else
continue;
}
/* We used to use comment packets, but not any longer. In
* case we still have comments on a key, strip them here
* before we call build_packet(). */
if (node->pkt->pkttype == PKT_COMMENT)
continue;
/* Skip ring trust packets - they should not be here anyway. */
if (node->pkt->pkttype == PKT_RING_TRUST)
continue;
/* If exact is set, then we only export what was requested
* (plus the primary key, if the user didn't specifically
* request it). */
if (desc[descindex].exact && node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (!exact_subkey_match_p (desc+descindex, node))
{
/* Before skipping this subkey, check whether any
* other description wants an exact match on a
* subkey and include that subkey into the output
* too. Need to add this subkey to a list so that
* it won't get processed a second time.
*
* So the first step here is to check that list and
* skip in any case if the key is in that list.
*
* We need this whole mess because the import
* function of GnuPG < 2.1 is not able to merge
* secret keys and thus it is useless to output them
* as two separate keys and have import merge them.
*/
if (subkey_in_list_p (subkey_list, node))
skip_until_subkey = 1; /* Already processed this one. */
else
{
size_t j;
for (j=0; j < ndesc; j++)
if (j != descindex && desc[j].exact
&& exact_subkey_match_p (desc+j, node))
break;
if (!(j < ndesc))
skip_until_subkey = 1; /* No other one matching. */
}
}
if (skip_until_subkey)
continue;
/* Mark this one as processed. */
{
subkey_list_t tmp = new_subkey_list_item (node);
tmp->next = subkey_list;
subkey_list = tmp;
}
}
if (node->pkt->pkttype == PKT_SIGNATURE)
{
/* Do not export packets which are marked as not
* exportable. */
if (!(options & EXPORT_LOCAL_SIGS)
&& !node->pkt->pkt.signature->flags.exportable)
continue; /* not exportable */
/* Do not export packets with a "sensitive" revocation key
* unless the user wants us to. Note that we do export
* these when issuing the actual revocation (see revoke.c). */
if (!(options & EXPORT_SENSITIVE_REVKEYS)
&& node->pkt->pkt.signature->revkey)
{
int i;
for (i = 0; i < node->pkt->pkt.signature->numrevkeys; i++)
if ((node->pkt->pkt.signature->revkey[i].class & 0x40))
break;
if (i < node->pkt->pkt.signature->numrevkeys)
continue;
}
}
/* Don't export attribs? */
if (!(options & EXPORT_ATTRIBUTES)
&& node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data)
{
/* Skip until we get to something that is not an attrib or a
* signature on an attrib. */
while (kbctx->next && kbctx->next->pkt->pkttype == PKT_SIGNATURE)
kbctx = kbctx->next;
continue;
}
if (secret && (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
pk = node->pkt->pkt.public_key;
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
subkid = NULL;
else
{
keyid_from_pk (pk, subkidbuf);
subkid = subkidbuf;
}
if (pk->seckey_info)
{
log_error ("key %s: oops: seckey_info already set"
" - skipped\n", keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
continue;
}
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("key %s: error computing keygrip: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
continue;
}
if (strchr (hexgrip, ','))
{
log_error ("exporting a secret dual key is not yet supported\n");
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
xfree (serialno);
serialno = NULL;
if (secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* We are asked not to export the secret parts of the
* primary key. Make up an error code to create the
* stub. */
err = GPG_ERR_NOT_FOUND;
}
else
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, &cleartext);
if ((!err && serialno)
&& secret == 2 && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* It does not make sense to export a key with its
* primary key on card using a non-key stub. Thus we
* skip those keys when used with --export-secret-subkeys. */
log_info (_("key %s: key material on-card - skipped\n"),
keystr_with_sub (keyid, subkid));
skip_until_subkey = 1;
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND
|| (!err && serialno))
{
/* Create a key stub. */
struct seckey_info *ski;
const char *s;
pk->seckey_info = ski = xtrycalloc (1, sizeof *ski);
if (!ski)
{
err = gpg_error_from_syserror ();
goto leave;
}
ski->is_protected = 1;
if (err)
ski->s2k.mode = 1001; /* GNU dummy (no secret key). */
else
{
ski->s2k.mode = 1002; /* GNU-divert-to-card. */
for (s=serialno; sizeof (ski->ivlen) && *s && s[1];
ski->ivlen++, s += 2)
ski->iv[ski->ivlen] = xtoi_2 (s);
}
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
else if (!err)
{
err = receive_seckey_from_agent (ctrl, cipherhd,
cleartext,
!!(options & EXPORT_MODE1003),
&cache_nonce,
hexgrip, pk, NULL);
if (err)
{
/* If we receive a fully canceled error we stop
* immediately. If we receive a cancel for a public
* key we also stop immediately because a
* public/secret key is always required first
* (right, we could instead write a stub key but
* that is also kind of surprising). If we receive
* a subkey we skip to the next subkey. */
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED
|| (node->pkt->pkttype == PKT_PUBLIC_KEY
&& gpg_err_code (err) == GPG_ERR_CANCELED))
goto leave;
write_status_error ("export_keys.secret", err);
skip_until_subkey = 1;
err = 0;
}
else
{
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
}
else
{
log_error ("key %s: error getting keyinfo from agent: %s"
" - skipped\n", keystr_with_sub (keyid, subkid),
gpg_strerror (err));
skip_until_subkey = 1;
err = 0;
}
xfree (pk->seckey_info);
pk->seckey_info = NULL;
{
int i;
for (i = pubkey_get_npkey (pk->pubkey_algo);
i < pubkey_get_nskey (pk->pubkey_algo); i++)
{
gcry_mpi_release (pk->pkey[i]);
pk->pkey[i] = NULL;
}
}
}
else /* Not secret or common packets. */
{
if ((options & EXPORT_BACKUP))
err = build_packet_and_meta (out, node->pkt);
else
err = build_packet (out, node->pkt);
if (!err && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
stats->exported++;
print_status_exported (node->pkt->pkt.public_key);
}
}
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err));
goto leave;
}
if (!skip_until_subkey)
*any = 1;
}
leave:
release_subkey_list (subkey_list);
xfree (serialno);
xfree (hexgrip);
xfree (cache_nonce);
return err;
}
/* Helper for do_export_stream which writes the own revocations
* certificates (if any) from KEYBLOCK to OUT. */
static gpg_error_t
do_export_revocs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
iobuf_t out, unsigned int options, int *any)
{
gpg_error_t err = 0;
kbnode_t kbctx, node;
PKT_signature *sig;
(void)ctrl;
/* NB: walk_kbnode skips packets marked as deleted. */
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
- /* We are only interested in revocation certifcates. */
+ /* We are only interested in revocation certificates. */
if (!(IS_KEY_REV (sig) || IS_UID_REV (sig) || IS_SUBKEY_REV (sig)))
continue;
if (!(sig->keyid[0] == keyid[0] && sig->keyid[1] == keyid[1]))
continue; /* Not a self-signature. */
/* Do not export signature packets which are marked as not
* exportable. */
if (!(options & EXPORT_LOCAL_SIGS)
&& !sig->flags.exportable)
continue; /* not exportable */
/* Do not export packets with a "sensitive" revocation key
* unless the user wants us to. */
if (!(options & EXPORT_SENSITIVE_REVKEYS)
&& sig->revkey)
{
int i;
for (i = 0; i < sig->numrevkeys; i++)
if ((sig->revkey[i].class & 0x40))
break;
if (i < sig->numrevkeys)
continue;
}
if (!sig->flags.checked)
{
log_info ("signature not marked as checked - ignored\n");
continue;
}
if (!sig->flags.valid)
{
log_info ("signature not not valid - ignored\n");
continue;
}
err = build_packet (out, node->pkt);
if (err)
{
log_error ("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (err));
goto leave;
}
*any = 1;
}
leave:
return err;
}
/* For secret key export we need to setup a decryption context.
* Returns 0 and the context at r_cipherhd. */
static gpg_error_t
get_keywrap_key (ctrl_t ctrl, gcry_cipher_hd_t *r_cipherhd)
{
#ifdef ENABLE_SELINUX_HACKS
(void)ctrl;
*r_cipherhd = NULL;
log_error (_("exporting secret keys not allowed\n"));
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#else
gpg_error_t err;
void *kek = NULL;
size_t keklen;
gcry_cipher_hd_t cipherhd;
*r_cipherhd = NULL;
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
return err;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
if (!err)
*r_cipherhd = cipherhd;
else
gcry_cipher_close (cipherhd);
xfree (kek);
return err;
#endif
}
/* Export the keys identified by the list of strings in USERS to the
stream OUT. If SECRET is false public keys will be exported. With
secret true secret keys will be exported; in this case 1 means the
entire secret keyblock and 2 only the subkeys. OPTIONS are the
export options to apply. If KEYBLOCK_OUT is not NULL, AND the exit
code is zero, a pointer to the first keyblock found and exported
will be stored at this address; no other keyblocks are exported in
this case. The caller must free the returned keyblock. If any
key has been exported true is stored at ANY. */
static int
do_export_stream (ctrl_t ctrl, iobuf_t out, strlist_t users, int secret,
kbnode_t *keyblock_out, unsigned int options,
export_stats_t stats, int *any)
{
gpg_error_t err = 0;
PACKET pkt;
kbnode_t keyblock = NULL;
kbnode_t node;
size_t ndesc, descindex;
KEYDB_SEARCH_DESC *desc = NULL;
KEYDB_HANDLE kdbhd;
strlist_t sl;
gcry_cipher_hd_t cipherhd = NULL;
struct export_stats_s dummystats;
iobuf_t out_help = NULL;
if (!stats)
stats = &dummystats;
*any = 0;
init_packet (&pkt);
kdbhd = keydb_new (ctrl);
if (!kdbhd)
return gpg_error_from_syserror ();
/* For the DANE format open a helper iobuf and
* enforce some options. */
if ((options & EXPORT_DANE_FORMAT))
{
out_help = iobuf_temp ();
options |= EXPORT_MINIMAL | EXPORT_CLEAN;
}
if (!users)
{
ndesc = 1;
desc = xcalloc (ndesc, sizeof *desc);
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
}
else
{
for (ndesc=0, sl=users; sl; sl = sl->next, ndesc++)
;
desc = xmalloc ( ndesc * sizeof *desc);
for (ndesc=0, sl=users; sl; sl = sl->next)
{
if (!(err=classify_user_id (sl->d, desc+ndesc, 1)))
ndesc++;
else
log_error (_("key \"%s\" not found: %s\n"),
sl->d, gpg_strerror (err));
}
keydb_disable_caching (kdbhd); /* We are looping the search. */
/* It would be nice to see which of the given users did actually
match one in the keyring. To implement this we need to have
a found flag for each entry in desc. To set this flag we
must check all those entries after a match to mark all
matched one - currently we stop at the first match. To do
this we need an extra flag to enable this feature. */
}
/* For secret key export we need to setup a decryption context. */
if (secret && (err = get_keywrap_key (ctrl, &cipherhd)))
goto leave;
for (;;)
{
u32 keyid[2];
PKT_public_key *pk;
err = keydb_search (kdbhd, desc, ndesc, &descindex);
if (!users)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
if (err)
break;
/* Read the keyblock. */
release_kbnode (keyblock);
keyblock = NULL;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("public key packet not found in keyblock - skipped\n");
continue;
}
stats->count++;
setup_main_keyids (keyblock); /* gpg_format_keydesc needs it. */
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* If a secret key export is required we need to check whether
we have a secret key at all and if so create the seckey_info
structure. */
if (secret)
{
if (agent_probe_any_secret_key (ctrl, keyblock))
continue; /* No secret key (neither primary nor subkey). */
/* No v3 keys with GNU mode 1001. */
if (secret == 2 && pk->version == 3)
{
log_info (_("key %s: PGP 2.x style key - skipped\n"),
keystr (keyid));
continue;
}
/* The agent does not yet allow export of v3 packets. It is
actually questionable whether we should allow them at
all. */
if (pk->version == 3)
{
log_info ("key %s: PGP 2.x style key (v3) export "
"not yet supported - skipped\n", keystr (keyid));
continue;
}
stats->secret_count++;
}
/* Always do the cleaning on the public key part if requested.
* A designated revocation is never stripped, even with
* export-minimal set. */
if ((options & EXPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock, opt.verbose, options, NULL, NULL);
clean_all_subkeys (ctrl, keyblock, opt.verbose,
(options&EXPORT_MINIMAL)? KEY_CLEAN_ALL
/**/ : KEY_CLEAN_AUTHENCR,
NULL, NULL);
commit_kbnode (&keyblock);
}
else if (export_keep_uid || export_drop_subkey || export_select_filter)
{
/* Need to merge so that for example the "usage" property
* has been setup. */
merge_keys_and_selfsig (ctrl, keyblock);
}
if (export_select_filter)
{
int selected = 0;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (parm.node = keyblock; parm.node; parm.node = parm.node->next)
{
if (recsel_select (export_select_filter,
impex_filter_getval, &parm))
{
selected = 1;
break;
}
}
if (!selected)
continue; /* Skip this keyblock. */
}
if (export_keep_uid)
{
commit_kbnode (&keyblock);
apply_keep_uid_filter (ctrl, keyblock, export_keep_uid);
commit_kbnode (&keyblock);
}
if (export_drop_subkey)
{
commit_kbnode (&keyblock);
apply_drop_subkey_filter (ctrl, keyblock, export_drop_subkey);
commit_kbnode (&keyblock);
}
/* And write it. */
if ((options & EXPORT_REVOCS))
err = do_export_revocs (ctrl, keyblock, keyid,
out_help? out_help : out,
options, any);
else
err = do_export_one_keyblock (ctrl, keyblock, keyid,
out_help? out_help : out,
secret, options, stats, any,
desc, ndesc, descindex, cipherhd);
if (err)
break;
if (keyblock_out)
{
*keyblock_out = keyblock;
break;
}
if (out_help && (options & EXPORT_DANE_FORMAT))
{
/* We want to write DANE records. OUT_HELP has the
* keyblock and we print a record for each uid to OUT. */
const void *data;
size_t datalen;
iobuf_flush_temp (out_help);
data = iobuf_get_temp_buffer (out_help);
datalen = iobuf_get_temp_length (out_help);
err = print_dane_records (out, keyblock, pk, data, datalen);
if (err)
goto leave;
iobuf_close (out_help);
out_help = iobuf_temp ();
}
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
leave:
iobuf_cancel (out_help);
gcry_cipher_close (cipherhd);
xfree(desc);
keydb_release (kdbhd);
if (err || !keyblock_out)
release_kbnode( keyblock );
if( !*any && !opt.quiet)
log_info (_("WARNING: nothing exported\n"));
return err;
}
/* Write the uint32 VALUE to MB in networ byte order. */
static void
mb_write_uint32 (membuf_t *mb, u32 value)
{
unsigned char buffer[4];
ulongtobuf (buffer, (ulong)value);
put_membuf (mb, buffer, 4);
}
/* Write the byte C to MB. */
static void
mb_write_uint8 (membuf_t *mb, int c)
{
unsigned char buf[1];
buf[0] = c;
put_membuf (mb, buf, 1);
}
/* Simple wrapper around put_membuf. */
static void
mb_write_data (membuf_t *mb, const void *data, size_t datalen)
{
put_membuf (mb, data, datalen);
}
/* Write STRING with terminating Nul to MB. */
static void
mb_write_cstring (membuf_t *mb, const char *string)
{
put_membuf (mb, string, strlen (string)+1);
}
/* Write an SSH style string to MB. */
static void
mb_write_string (membuf_t *mb, const void *string, size_t n)
{
mb_write_uint32 (mb, (u32)n);
mb_write_data (mb, string, n);
}
/* Write an MPI as SSH style string to MB */
static void
mb_write_mpi (membuf_t *mb, gcry_mpi_t mpi, int strip_prefix)
{
unsigned int nbits;
const unsigned char *p;
size_t n;
if (gcry_mpi_get_flag (mpi, GCRYMPI_FLAG_OPAQUE))
{
p = gcry_mpi_get_opaque (mpi, &nbits);
n = (nbits + 7) / 8;
if (strip_prefix && n > 1 && p[0] == 0x40)
{
/* We need to strip our 0x40 prefix. */
p++;
n--;
}
mb_write_string (mb, p, n);
}
else
{
gpg_error_t err;
unsigned char *buf;
err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &n, mpi);
if (err)
set_membuf_err (mb, err);
else
{
mb_write_data (mb, buf, n);
gcry_free (buf);
}
}
}
static gpg_error_t
key_to_sshblob (membuf_t *mb, const char *identifier, ...)
{
va_list arg_ptr;
gpg_error_t err = 0;
unsigned char nbuf[4];
unsigned char *buf;
size_t buflen;
gcry_mpi_t a;
buflen = strlen (identifier);
ulongtobuf (nbuf, (ulong)buflen);
put_membuf (mb, nbuf, 4);
put_membuf (mb, identifier, buflen);
if (buflen > 11 && !memcmp (identifier, "ecdsa-sha2-", 11))
{
/* Store the name of the curve taken from the identifier. */
ulongtobuf (nbuf, (ulong)(buflen - 11));
put_membuf (mb, nbuf, 4);
put_membuf (mb, identifier+11, buflen - 11);
}
va_start (arg_ptr, identifier);
while ((a = va_arg (arg_ptr, gcry_mpi_t)))
{
if (gcry_mpi_get_flag (a, GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
const unsigned char *p;
p = gcry_mpi_get_opaque (a, &nbits);
buflen = (nbits + 7) / 8;
if (!strcmp (identifier, "ssh-ed25519")
&& buflen > 1 && p[0] == 0x40)
{
/* We need to strip our 0x40 prefix. */
put_membuf (mb, "\x00\x00\x00\x20", 4);
put_membuf (mb, p+1, buflen-1);
}
else
{
unsigned char c;
c = buflen >> 24;
put_membuf (mb, &c, 1);
c = buflen >> 16;
put_membuf (mb, &c, 1);
c = buflen >> 8;
put_membuf (mb, &c, 1);
c = buflen;
put_membuf (mb, &c, 1);
put_membuf (mb, p, buflen);
}
}
else
{
err = gcry_mpi_aprint (GCRYMPI_FMT_SSH, &buf, &buflen, a);
if (err)
break;
put_membuf (mb, buf, buflen);
gcry_free (buf);
}
}
va_end (arg_ptr);
return err;
}
static gpg_error_t
export_one_ssh_key (estream_t fp, PKT_public_key *pk)
{
gpg_error_t err;
const char *identifier = NULL;
membuf_t mb;
void *blob;
size_t bloblen;
init_membuf (&mb, 4096);
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_DSA:
identifier = "ssh-dss";
err = key_to_sshblob (&mb, identifier,
pk->pkey[0], pk->pkey[1], pk->pkey[2], pk->pkey[3],
NULL);
break;
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_S:
identifier = "ssh-rsa";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], pk->pkey[0], NULL);
break;
case PUBKEY_ALGO_ECDSA:
{
char *curveoid;
const char *curve;
curveoid = openpgp_oid_to_str (pk->pkey[0]);
if (!curveoid)
err = gpg_error_from_syserror ();
else if (!(curve = openpgp_oid_to_curve (curveoid, 0)))
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
{
if (!strcmp (curve, "nistp256"))
identifier = "ecdsa-sha2-nistp256";
else if (!strcmp (curve, "nistp384"))
identifier = "ecdsa-sha2-nistp384";
else if (!strcmp (curve, "nistp521"))
identifier = "ecdsa-sha2-nistp521";
if (!identifier)
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
else
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
xfree (curveoid);
}
break;
case PUBKEY_ALGO_EDDSA:
if (openpgp_oid_is_ed25519 (pk->pkey[0]))
{
identifier = "ssh-ed25519";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
else if (openpgp_oid_is_ed448 (pk->pkey[0]))
{
identifier = "ssh-ed448";
err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL);
}
else
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
break;
case PUBKEY_ALGO_ELGAMAL_E:
case PUBKEY_ALGO_ELGAMAL:
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
break;
default:
err = GPG_ERR_PUBKEY_ALGO;
break;
}
if (err)
goto leave;
blob = get_membuf (&mb, &bloblen);
if (blob)
{
gpgrt_b64state_t b64_state;
es_fprintf (fp, "%s ", identifier);
b64_state = gpgrt_b64enc_start (fp, "");
if (!b64_state)
{
xfree (blob);
goto leave;
}
err = gpgrt_b64enc_write (b64_state, blob, bloblen);
gpgrt_b64enc_finish (b64_state);
es_fprintf (fp, " openpgp:0x%08lX\n", (ulong)keyid_from_pk (pk, NULL));
xfree (blob);
}
leave:
xfree (get_membuf (&mb, NULL));
return err;
}
/* Export the key identified by USERID in the SSH public key format.
The function exports the latest subkey with Authentication
capability unless the '!' suffix is used to export a specific
key. */
gpg_error_t
export_ssh_key (ctrl_t ctrl, const char *userid)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_SEARCH_DESC desc;
u32 latest_date;
u32 curtime = make_timestamp ();
kbnode_t latest_key, node;
PKT_public_key *pk;
estream_t fp = NULL;
const char *fname = "-";
/* We need to know whether the key has been specified using the
exact syntax ('!' suffix). Thus we need to run a
classify_user_id on our own. */
err = classify_user_id (userid, &desc, 1);
/* Get the public key. */
if (!err)
{
getkey_ctx_t getkeyctx;
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
&getkeyctx, NULL, userid, &keyblock,
NULL,
0 /* Only usable keys or given exact. */);
if (!err)
{
err = getkey_next (ctrl, getkeyctx, NULL, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
err = 0;
}
getkey_end (ctrl, getkeyctx);
}
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
return err;
}
/* The finish_lookup code in getkey.c does not handle auth keys,
thus we have to duplicate the code here to find the latest
subkey. However, if the key has been found using an exact match
('!' notation) we use that key without any further checks and
even allow the use of the primary key. */
latest_date = 0;
latest_key = NULL;
for (node = keyblock; node; node = node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_PUBLIC_KEY)
&& node->pkt->pkt.public_key->flags.exact)
{
latest_key = node;
break;
}
}
if (!latest_key)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not usable for authentication\n");
continue;
}
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
continue;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure that it
is used. A better change would be to compare ">=" but that
might also change the selected keys and is as such a more
intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = node;
}
}
/* If no subkey was suitable check the primary key. */
if (!latest_key
&& (node = keyblock) && node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking primary key %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!(pk->pubkey_usage & PUBKEY_USAGE_AUTH))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not usable for authentication\n");
}
else if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not yet valid\n");
}
else
{
if (DBG_LOOKUP)
log_debug ("\tprimary key is fine\n");
latest_date = pk->timestamp;
latest_key = node;
}
}
}
if (!latest_key)
{
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
log_error (_("key \"%s\" not found: %s\n"), userid, gpg_strerror (err));
goto leave;
}
pk = latest_key->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n", (ulong) keyid_from_pk (pk, NULL));
if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-"))
fp = es_fopen ((fname = opt.outfile), "w");
else
fp = es_stdout;
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
err = export_one_ssh_key (fp, pk);
if (err)
goto leave;
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
{
if (fp != es_stdout && es_fclose (fp))
err = gpg_error_from_syserror ();
fp = NULL;
}
if (err)
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
leave:
if (fp != es_stdout)
es_fclose (fp);
release_kbnode (keyblock);
return err;
}
/* Export the key identified by USERID in the SSH secret key format.
* The USERID must be given in keygrip format (prefixed with a '&')
* and thus no OpenPGP key is required. The exported key is not
* protected. */
gpg_error_t
export_secret_ssh_key (ctrl_t ctrl, const char *userid)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
estream_t fp = NULL;
const char *fname = "-";
gcry_cipher_hd_t cipherhd = NULL;
char hexgrip[KEYGRIP_LEN * 2 + 1];
gcry_sexp_t skey = NULL;
gcry_sexp_t skeyalgo = NULL;
const char *identifier = NULL;
membuf_t mb;
membuf_t mb2;
void *blob = NULL;
size_t bloblen;
const char *s;
size_t n;
char *p;
int pkalgo;
int i;
gcry_mpi_t keyparam[10] = { NULL };
gpgrt_b64state_t b64_state;
init_membuf_secure (&mb, 1024);
init_membuf_secure (&mb2, 1024);
/* Check that a keygrip has been given. */
err = classify_user_id (userid, &desc, 1);
if (err || desc.mode != KEYDB_SEARCH_MODE_KEYGRIP )
{
log_error (_("key \"%s\" not found: %s\n"), userid,
err? gpg_strerror (err) : "Not a Keygrip" );
return err;
}
bin2hex (desc.u.grip, KEYGRIP_LEN, hexgrip);
if ((err = get_keywrap_key (ctrl, &cipherhd)))
goto leave;
err = receive_seckey_from_agent (ctrl, cipherhd, 0, 0, NULL, hexgrip, NULL,
&skey);
if (err)
goto leave;
/* Get the type of the key expression. */
s = gcry_sexp_nth_data (skey, 0, &n);
if (!s || !(n == 11 && !memcmp (s, "private-key", 11)))
{
log_info ("Note: only on-disk secret keys may be exported\n");
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
mb_write_cstring (&mb, "openssh-key-v1"); /* Auth_Magic. */
mb_write_string (&mb, "none", 4); /* ciphername */
mb_write_string (&mb, "none", 4); /* kdfname */
mb_write_uint32 (&mb, 0); /* kdfoptions */
mb_write_uint32 (&mb, 1); /* number of keys */
pkalgo = get_pk_algo_from_key (skey);
switch (pkalgo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_S:
identifier = "ssh-rsa";
err = gcry_sexp_extract_param (skey, NULL, "nedpq",
&keyparam[0],
&keyparam[1],
&keyparam[2],
&keyparam[3],
&keyparam[4],
NULL);
if (err)
goto leave;
mb_write_string (&mb2, identifier, strlen (identifier));
mb_write_mpi (&mb2, keyparam[1], 0); /* e (right, e is first here) */
mb_write_mpi (&mb2, keyparam[0], 0); /* n */
/* Append public to the output block as an SSH string. */
p = get_membuf (&mb2, &n);
if (!p)
{
err = gpg_error_from_syserror ();
goto leave;
}
mb_write_string (&mb, p, n);
xfree (p);
init_membuf_secure (&mb2, 1024);
mb_write_string (&mb2, identifier, strlen (identifier));
{
char checkbytes[4];
gcry_create_nonce (checkbytes, sizeof checkbytes);
mb_write_data (&mb2, checkbytes, sizeof checkbytes);
mb_write_data (&mb2, checkbytes, sizeof checkbytes);
}
mb_write_mpi (&mb2, keyparam[0], 0); /* n */
mb_write_mpi (&mb2, keyparam[1], 0); /* e */
/*FIXME: Fixup u,p,q to match the OpenSSH format. */
mb_write_mpi (&mb2, keyparam[2], 0); /* d */
mb_write_mpi (&mb2, keyparam[1], 0); /* iqmp1 */
mb_write_mpi (&mb2, keyparam[3], 0); /* p */
mb_write_mpi (&mb2, keyparam[4], 0); /* q */
/* Fixme: take the comment from skey. */
mb_write_string (&mb2, "<comment>", 9);
/* Pad to a blocksize of 8 (for cipher "none"). */
i = 0;
while (peek_membuf (&mb2, &n) && (n % 8))
mb_write_uint8 (&mb2, ++i);
/* Append encrypted block to the output as an SSH string. */
p = get_membuf (&mb2, &n);
if (!p)
{
err = gpg_error_from_syserror ();
goto leave;
}
mb_write_string (&mb, p, n);
xfree (p);
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
/* case PUBKEY_ALGO_ECDSA: */
/* { */
/* char *curveoid; */
/* const char *curve; */
/* curveoid = openpgp_oid_to_str (pk->pkey[0]); */
/* if (!curveoid) */
/* err = gpg_error_from_syserror (); */
/* else if (!(curve = openpgp_oid_to_curve (curveoid, 0))) */
/* err = gpg_error (GPG_ERR_UNKNOWN_CURVE); */
/* else */
/* { */
/* if (!strcmp (curve, "nistp256")) */
/* identifier = "ecdsa-sha2-nistp256"; */
/* else if (!strcmp (curve, "nistp384")) */
/* identifier = "ecdsa-sha2-nistp384"; */
/* else if (!strcmp (curve, "nistp521")) */
/* identifier = "ecdsa-sha2-nistp521"; */
/* if (!identifier) */
/* err = gpg_error (GPG_ERR_UNKNOWN_CURVE); */
/* else */
/* err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); */
/* } */
/* xfree (curveoid); */
/* } */
/* break; */
/* case PUBKEY_ALGO_EDDSA: */
/* if (openpgp_oid_is_ed25519 (pk->pkey[0])) */
/* { */
/* identifier = "ssh-ed25519"; */
/* err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); */
/* } */
/* else if (openpgp_oid_is_ed448 (pk->pkey[0])) */
/* { */
/* identifier = "ssh-ed448"; */
/* err = key_to_sshblob (&mb, identifier, pk->pkey[1], NULL); */
/* } */
/* else */
/* err = gpg_error (GPG_ERR_UNKNOWN_CURVE); */
/* break; */
case PUBKEY_ALGO_DSA:
log_info ("Note: export of ssh-dsa keys is not supported\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
break;
case PUBKEY_ALGO_ELGAMAL_E:
case PUBKEY_ALGO_ELGAMAL:
err = gpg_error (GPG_ERR_UNUSABLE_SECKEY);
break;
default:
err = GPG_ERR_PUBKEY_ALGO;
break;
}
if (err)
goto leave;
blob = get_membuf (&mb, &bloblen);
if (!blob)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt.outfile && *opt.outfile && strcmp (opt.outfile, "-"))
fp = es_fopen ((fname = opt.outfile), "w");
else
fp = es_stdout;
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error creating '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
b64_state = gpgrt_b64enc_start (fp, "OPENSSH PRIVATE_KEY");
if (!b64_state)
goto leave;
err = gpgrt_b64enc_write (b64_state, blob, bloblen);
gpgrt_b64enc_finish (b64_state);
if (err)
goto leave;
if (es_ferror (fp))
err = gpg_error_from_syserror ();
else
{
if (fp != es_stdout && es_fclose (fp))
err = gpg_error_from_syserror ();
fp = NULL;
}
log_info ("Beware: the private key is not protected;"
" use \"ssh-keygen -p\" to protect it\n");
if (err)
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
leave:
xfree (blob);
gcry_sexp_release (skey);
gcry_sexp_release (skeyalgo);
gcry_cipher_close (cipherhd);
xfree (get_membuf (&mb2, NULL));
xfree (get_membuf (&mb, NULL));
if (fp != es_stdout)
es_fclose (fp);
return err;
}
diff --git a/g10/getkey.c b/g10/getkey.c
index f2d1e7d7b..015420173 100644
--- a/g10/getkey.c
+++ b/g10/getkey.c
@@ -1,4564 +1,4564 @@
/* getkey.c - Get a key from the database
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2008, 2010 Free Software Foundation, Inc.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "options.h"
#include "main.h"
#include "trustdb.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "objcache.h"
#include "../common/host2net.h"
#include "../common/mbox-util.h"
#include "../common/status.h"
#define MAX_PK_CACHE_ENTRIES PK_UID_CACHE_SIZE
#define MAX_UID_CACHE_ENTRIES PK_UID_CACHE_SIZE
#if MAX_PK_CACHE_ENTRIES < 2
#error We need the cache for key creation
#endif
/* Flags values returned by the lookup code. Note that the values are
* directly used by the KEY_CONSIDERED status line. */
#define LOOKUP_NOT_SELECTED (1<<0)
#define LOOKUP_ALL_SUBKEYS_EXPIRED (1<<1) /* or revoked */
/* A context object used by the lookup functions. */
struct getkey_ctx_s
{
/* Part of the search criteria: whether the search is an exact
search or not. A search that is exact requires that a key or
subkey meet all of the specified criteria. A search that is not
exact allows selecting a different key or subkey from the
keyblock that matched the criteria. Further, an exact search
returns the key or subkey that matched whereas a non-exact search
typically returns the primary key. See finish_lookup for
details. */
int exact;
/* Part of the search criteria: Whether the caller only wants keys
with an available secret key. This is used by getkey_next to get
the next result with the same initial criteria. */
int want_secret;
/* Part of the search criteria: The type of the requested key. A
mask of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT.
If non-zero, then for a key to match, it must implement one of
the required uses. */
int req_usage;
/* The database handle. */
KEYDB_HANDLE kr_handle;
/* Whether we should call xfree() on the context when the context is
released using getkey_end()). */
int not_allocated;
/* This variable is used as backing store for strings which have
their address used in ITEMS. */
strlist_t extra_list;
/* Hack to return the mechanism (AKL_foo) used to find the key. */
int found_via_akl;
/* Part of the search criteria: The low-level search specification
as passed to keydb_search. */
int nitems;
/* This must be the last element in the structure. When we allocate
the structure, we allocate it so that ITEMS can hold NITEMS. */
KEYDB_SEARCH_DESC items[1];
};
#if 0
static struct
{
int any;
int okay_count;
int nokey_count;
int error_count;
} lkup_stats[21];
#endif
typedef struct keyid_list
{
struct keyid_list *next;
byte fprlen;
char fpr[MAX_FINGERPRINT_LEN];
u32 keyid[2];
} *keyid_list_t;
#if MAX_PK_CACHE_ENTRIES
typedef struct pk_cache_entry
{
struct pk_cache_entry *next;
u32 keyid[2];
PKT_public_key *pk;
} *pk_cache_entry_t;
static pk_cache_entry_t pk_cache;
static int pk_cache_entries; /* Number of entries in pk cache. */
static int pk_cache_disabled;
#endif
#if MAX_UID_CACHE_ENTRIES < 5
#error we really need the userid cache
#endif
static void merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock);
static int lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret,
kbnode_t *ret_keyblock, kbnode_t *ret_found_key);
static kbnode_t finish_lookup (kbnode_t keyblock,
unsigned int req_usage, int want_exact,
int want_secret, unsigned int *r_flags);
static void print_status_key_considered (kbnode_t keyblock, unsigned int flags);
#if 0
static void
print_stats ()
{
int i;
for (i = 0; i < DIM (lkup_stats); i++)
{
if (lkup_stats[i].any)
es_fprintf (es_stderr,
"lookup stats: mode=%-2d ok=%-6d nokey=%-6d err=%-6d\n",
i,
lkup_stats[i].okay_count,
lkup_stats[i].nokey_count, lkup_stats[i].error_count);
}
}
#endif
/* Cache a copy of a public key in the public key cache. PK is not
* cached if caching is disabled (via getkey_disable_caches), if
* PK->FLAGS.DONT_CACHE is set, we don't know how to derive a key id
* from the public key (e.g., unsupported algorithm), or a key with
* the key id is already in the cache.
*
* The public key packet is copied into the cache using
* copy_public_key. Thus, any secret parts are not copied, for
* instance.
*
* This cache is filled by get_pubkey and is read by get_pubkey and
* get_pubkey_fast. */
void
cache_public_key (PKT_public_key * pk)
{
#if MAX_PK_CACHE_ENTRIES
pk_cache_entry_t ce, ce2;
u32 keyid[2];
if (pk_cache_disabled)
return;
if (pk->flags.dont_cache)
return;
if (is_ELGAMAL (pk->pubkey_algo)
|| pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH
|| is_RSA (pk->pubkey_algo))
{
keyid_from_pk (pk, keyid);
}
else
return; /* Don't know how to get the keyid. */
for (ce = pk_cache; ce; ce = ce->next)
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
{
if (DBG_CACHE)
log_debug ("cache_public_key: already in cache\n");
return;
}
if (pk_cache_entries >= MAX_PK_CACHE_ENTRIES)
{
int n;
/* Remove the last 50% of the entries. */
for (ce = pk_cache, n = 0; ce && n < pk_cache_entries/2; n++)
ce = ce->next;
if (ce && ce != pk_cache && ce->next)
{
ce2 = ce->next;
ce->next = NULL;
ce = ce2;
for (; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
pk_cache_entries--;
}
}
log_assert (pk_cache_entries < MAX_PK_CACHE_ENTRIES);
}
pk_cache_entries++;
ce = xmalloc (sizeof *ce);
ce->next = pk_cache;
pk_cache = ce;
ce->pk = copy_public_key (NULL, pk);
ce->keyid[0] = keyid[0];
ce->keyid[1] = keyid[1];
#endif
}
/* Return a const utf-8 string with the text "[User ID not found]".
This function is required so that we don't need to switch gettext's
encoding temporary. */
static const char *
user_id_not_found_utf8 (void)
{
static char *text;
if (!text)
text = native_to_utf8 (_("[User ID not found]"));
return text;
}
/* Disable and drop the public key cache (which is filled by
cache_public_key and get_pubkey). Note: there is currently no way
to re-enable this cache. */
void
getkey_disable_caches (void)
{
#if MAX_PK_CACHE_ENTRIES
{
pk_cache_entry_t ce, ce2;
for (ce = pk_cache; ce; ce = ce2)
{
ce2 = ce->next;
free_public_key (ce->pk);
xfree (ce);
}
pk_cache_disabled = 1;
pk_cache_entries = 0;
pk_cache = NULL;
}
#endif
/* fixme: disable user id cache ? */
}
/* Free a list of pubkey_t objects. */
void
pubkeys_free (pubkey_t keys)
{
while (keys)
{
pubkey_t next = keys->next;
xfree (keys->pk);
release_kbnode (keys->keyblock);
xfree (keys);
keys = next;
}
}
static void
pk_from_block (PKT_public_key *pk, kbnode_t keyblock, kbnode_t found_key)
{
kbnode_t a = found_key ? found_key : keyblock;
log_assert (a->pkt->pkttype == PKT_PUBLIC_KEY
|| a->pkt->pkttype == PKT_PUBLIC_SUBKEY);
copy_public_key (pk, a->pkt->pkt.public_key);
}
/* Specialized version of get_pubkey which retrieves the key based on
* information in SIG. In contrast to get_pubkey PK is required. IF
* FORCED_PK is not NULL, this public key is used and copied to PK. */
gpg_error_t
get_pubkey_for_sig (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig,
PKT_public_key *forced_pk)
{
const byte *fpr;
size_t fprlen;
if (forced_pk)
{
copy_public_key (pk, forced_pk);
return 0;
}
/* First try the ISSUER_FPR info. */
fpr = issuer_fpr_raw (sig, &fprlen);
if (fpr && !get_pubkey_byfprint (ctrl, pk, NULL, fpr, fprlen))
return 0;
/* Fallback to use the ISSUER_KEYID. */
return get_pubkey (ctrl, pk, sig->keyid);
}
/* Return the public key with the key id KEYID and store it at PK.
* The resources in *PK should be released using
* release_public_key_parts(). This function also stores a copy of
* the public key in the user id cache (see cache_public_key).
*
* If PK is NULL, this function just stores the public key in the
* cache and returns the usual return code.
*
* PK->REQ_USAGE (which is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT) is passed through to the
* lookup function. If this is non-zero, only keys with the specified
* usage will be returned. As such, it is essential that
* PK->REQ_USAGE be correctly initialized!
*
* Returns 0 on success, GPG_ERR_NO_PUBKEY if there is no public key
* with the specified key id, or another error code if an error
* occurs.
*
* If the data was not read from the cache, then the self-signed data
* has definitely been merged into the public key using
* merge_selfsigs. */
int
get_pubkey (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid)
{
int internal = 0;
int rc = 0;
#if MAX_PK_CACHE_ENTRIES
if (pk)
{
/* Try to get it from the cache. We don't do this when pk is
NULL as it does not guarantee that the user IDs are
cached. */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1])
/* XXX: We don't check PK->REQ_USAGE here, but if we don't
read from the cache, we do check it! */
{
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
/* More init stuff. */
if (!pk)
{
internal++;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
/* Do a lookup. */
{
struct getkey_ctx_s ctx;
kbnode_t kb = NULL;
kbnode_t found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
if (ctrl && ctrl->cached_getkey_kdb)
{
ctx.kr_handle = ctrl->cached_getkey_kdb;
ctrl->cached_getkey_kdb = NULL;
keydb_search_reset (ctx.kr_handle);
}
else
{
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
{
rc = gpg_error_from_syserror ();
goto leave;
}
}
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
rc = lookup (ctrl, &ctx, 0, &kb, &found_key);
if (!rc)
{
pk_from_block (pk, kb, found_key);
}
getkey_end (ctrl, &ctx);
release_kbnode (kb);
}
if (!rc)
goto leave;
rc = GPG_ERR_NO_PUBKEY;
leave:
if (!rc)
cache_public_key (pk);
if (internal)
free_public_key (pk);
return rc;
}
/* Same as get_pubkey but if the key was not found the function tries
- * to import it from LDAP. FIXME: We should not need this but swicth
+ * to import it from LDAP. FIXME: We should not need this but switch
* to a fingerprint lookup. */
gpg_error_t
get_pubkey_with_ldap_fallback (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid)
{
gpg_error_t err;
err = get_pubkey (ctrl, pk, keyid);
if (!err)
return 0;
if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY)
return err;
/* Note that this code does not handle the case for two readers
* having both openpgp encryption keys. Only one will be tried. */
if (opt.debug)
log_debug ("using LDAP to find a public key\n");
err = keyserver_import_keyid (ctrl, keyid,
opt.keyserver, KEYSERVER_IMPORT_FLAG_LDAP);
if (gpg_err_code (err) == GPG_ERR_NO_DATA
|| gpg_err_code (err) == GPG_ERR_NO_KEYSERVER)
{
/* Dirmngr returns NO DATA is the selected keyserver
* does not have the requested key. It returns NO
* KEYSERVER if no LDAP keyservers are configured. */
err = gpg_error (GPG_ERR_NO_PUBKEY);
}
if (err)
return err;
return get_pubkey (ctrl, pk, keyid);
}
/* Similar to get_pubkey, but it does not take PK->REQ_USAGE into
* account nor does it merge in the self-signed data. This function
* also only considers primary keys. It is intended to be used as a
* quick check of the key to avoid recursion. It should only be used
* in very certain cases. Like get_pubkey and unlike any of the other
* lookup functions, this function also consults the user id cache
* (see cache_public_key).
*
* Return the public key in *PK. The resources in *PK should be
* released using release_public_key_parts(). */
int
get_pubkey_fast (ctrl_t ctrl, PKT_public_key * pk, u32 * keyid)
{
int rc = 0;
KEYDB_HANDLE hd;
KBNODE keyblock;
u32 pkid[2];
log_assert (pk);
#if MAX_PK_CACHE_ENTRIES
{
/* Try to get it from the cache */
pk_cache_entry_t ce;
for (ce = pk_cache; ce; ce = ce->next)
{
if (ce->keyid[0] == keyid[0] && ce->keyid[1] == keyid[1]
/* Only consider primary keys. */
&& ce->pk->keyid[0] == ce->pk->main_keyid[0]
&& ce->pk->keyid[1] == ce->pk->main_keyid[1])
{
if (pk)
copy_public_key (pk, ce->pk);
return 0;
}
}
}
#endif
hd = keydb_new (ctrl);
if (!hd)
return gpg_error_from_syserror ();
rc = keydb_search_kid (hd, keyid);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
keydb_release (hd);
return GPG_ERR_NO_PUBKEY;
}
rc = keydb_get_keyblock (hd, &keyblock);
keydb_release (hd);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
return GPG_ERR_NO_PUBKEY;
}
log_assert (keyblock && keyblock->pkt
&& keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* We return the primary key. If KEYID matched a subkey, then we
return an error. */
keyid_from_pk (keyblock->pkt->pkt.public_key, pkid);
if (keyid[0] == pkid[0] && keyid[1] == pkid[1])
copy_public_key (pk, keyblock->pkt->pkt.public_key);
else
rc = GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
/* Not caching key here since it won't have all of the fields
properly set. */
return rc;
}
/* Return the entire keyblock used to create SIG. This is a
* specialized version of get_pubkeyblock.
*
* FIXME: This is a hack because get_pubkey_for_sig was already called
* and it could have used a cache to hold the key. */
kbnode_t
get_pubkeyblock_for_sig (ctrl_t ctrl, PKT_signature *sig)
{
const byte *fpr;
size_t fprlen;
kbnode_t keyblock;
/* First try the ISSUER_FPR info. */
fpr = issuer_fpr_raw (sig, &fprlen);
if (fpr && !get_pubkey_byfprint (ctrl, NULL, &keyblock, fpr, fprlen))
return keyblock;
/* Fallback to use the ISSUER_KEYID. */
return get_pubkeyblock (ctrl, sig->keyid);
}
/* Return the key block for the key with key id KEYID or NULL, if an
* error occurs. Use release_kbnode() to release the key block.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
kbnode_t
get_pubkeyblock (ctrl_t ctrl, u32 * keyid)
{
struct getkey_ctx_s ctx;
int rc = 0;
KBNODE keyblock = NULL;
memset (&ctx, 0, sizeof ctx);
/* No need to set exact here because we want the entire block. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return NULL;
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
rc = lookup (ctrl, &ctx, 0, &keyblock, NULL);
getkey_end (ctrl, &ctx);
return rc ? NULL : keyblock;
}
/* Return the public key with the key id KEYID iff the secret key is
* available and store it at PK. The resources should be released
* using release_public_key_parts().
*
* Unlike other lookup functions, PK may not be NULL. PK->REQ_USAGE
* is passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. Thus, it
* must be valid! If this is non-zero, only keys with the specified
* usage will be returned.
*
* Returns 0 on success. If a public key with the specified key id is
* not found or a secret key is not available for that public key, an
* error code is returned. Note: this function ignores legacy keys.
* An error code is also return if an error occurs.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
get_seckey (ctrl_t ctrl, PKT_public_key *pk, u32 *keyid)
{
gpg_error_t err;
struct getkey_ctx_s ctx;
kbnode_t keyblock = NULL;
kbnode_t found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1; /* Use the key ID exactly as given. */
ctx.not_allocated = 1;
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx.items[0].u.kid[0] = keyid[0];
ctx.items[0].u.kid[1] = keyid[1];
ctx.req_usage = pk->req_usage;
err = lookup (ctrl, &ctx, 1, &keyblock, &found_key);
if (!err)
{
pk_from_block (pk, keyblock, found_key);
}
getkey_end (ctrl, &ctx);
release_kbnode (keyblock);
if (!err)
{
if (!agent_probe_secret_key (/*ctrl*/NULL, pk))
{
release_public_key_parts (pk);
err = gpg_error (GPG_ERR_NO_SECKEY);
}
}
return err;
}
/* Skip unusable keys. A key is unusable if it is revoked, expired or
disabled or if the selected user id is revoked or expired. */
static int
skip_unusable (void *opaque, u32 * keyid, int uid_no)
{
ctrl_t ctrl = opaque;
int unusable = 0;
KBNODE keyblock;
PKT_public_key *pk;
keyblock = get_pubkeyblock (ctrl, keyid);
if (!keyblock)
{
log_error ("error checking usability status of %s\n", keystr (keyid));
goto leave;
}
pk = keyblock->pkt->pkt.public_key;
/* Is the key revoked or expired? */
if (pk->flags.revoked || (pk->has_expired && !opt.ignore_expiration))
unusable = 1;
/* Is the user ID in question revoked or expired? */
if (!unusable && uid_no)
{
KBNODE node;
int uids_seen = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *user_id = node->pkt->pkt.user_id;
uids_seen ++;
if (uids_seen != uid_no)
continue;
if (user_id->flags.revoked
|| (user_id->flags.expired && !opt.ignore_expiration))
unusable = 1;
break;
}
}
/* If UID_NO is non-zero, then the keyblock better have at least
that many UIDs. */
log_assert (uids_seen == uid_no);
}
if (!unusable)
unusable = pk_is_disabled (pk);
leave:
release_kbnode (keyblock);
return unusable;
}
/* Search for keys matching some criteria.
If RETCTX is not NULL, then the constructed context is returned in
*RETCTX so that getpubkey_next can be used to get subsequent
results. In this case, getkey_end() must be used to free the
search context. If RETCTX is not NULL, then RET_KDBHD must be
NULL.
If NAMELIST is not NULL, then a search query is constructed using
classify_user_id on each of the strings in the list. (Recall: the
database does an OR of the terms, not an AND.) If NAMELIST is
NULL, then all results are returned.
If PK is not NULL, the public key of the first result is returned
in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
set, it is used to filter the search results. See the
documentation for finish_lookup to understand exactly how this is
used. Note: The self-signed data has already been merged into the
public key using merge_selfsigs. Free *PK by calling
release_public_key_parts (or, if PK was allocated using xfree, you
can use free_public_key, which calls release_public_key_parts(PK)
and then xfree(PK)).
If WANT_SECRET is set, then only keys with an available secret key
(either locally or via key registered on a smartcard) are returned.
If INCLUDE_UNUSABLE is set, then unusable keys (see the
documentation for skip_unusable for an exact definition) are
skipped unless they are looked up by key id or by fingerprint.
If RET_KB is not NULL, the keyblock is returned in *RET_KB. This
should be freed using release_kbnode().
If RET_KDBHD is not NULL, then the new database handle used to
conduct the search is returned in *RET_KDBHD. This can be used to
get subsequent results using keydb_search_next. Note: in this
case, no advanced filtering is done for subsequent results (e.g.,
WANT_SECRET and PK->REQ_USAGE are not respected).
This function returns 0 on success. Otherwise, an error code is
returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
(if want_secret is set) is returned if the key is not found. */
static int
key_byname (ctrl_t ctrl, GETKEY_CTX *retctx, strlist_t namelist,
PKT_public_key *pk,
int want_secret, int include_unusable,
KBNODE * ret_kb, KEYDB_HANDLE * ret_kdbhd)
{
int rc = 0;
int n;
strlist_t r;
strlist_t namelist_expanded = NULL;
GETKEY_CTX ctx;
KBNODE help_kb = NULL;
KBNODE found_key = NULL;
if (retctx)
{
/* Reset the returned context in case of error. */
log_assert (!ret_kdbhd); /* Not allowed because the handle is stored
in the context. */
*retctx = NULL;
}
if (ret_kdbhd)
*ret_kdbhd = NULL;
if (!namelist)
/* No search terms: iterate over the whole DB. */
{
ctx = xmalloc_clear (sizeof *ctx);
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_FIRST;
if (!include_unusable)
{
ctx->items[0].skipfnc = skip_unusable;
ctx->items[0].skipfncvalue = ctrl;
}
}
else
{
namelist_expanded = expand_group (namelist, 1);
namelist = namelist_expanded;
/* Build the search context. */
for (n = 0, r = namelist; r; r = r->next)
n++;
/* CTX has space for a single search term at the end. Thus, we
need to allocate sizeof *CTX plus (n - 1) sizeof
CTX->ITEMS. */
ctx = xmalloc_clear (sizeof *ctx + (n - 1) * sizeof ctx->items);
ctx->nitems = n;
for (n = 0, r = namelist; r; r = r->next, n++)
{
gpg_error_t err;
err = classify_user_id (r->d, &ctx->items[n], 1);
if (ctx->items[n].exact)
ctx->exact = 1;
if (err)
{
xfree (ctx);
rc = gpg_err_code (err); /* FIXME: remove gpg_err_code. */
goto leave;
}
if (!include_unusable
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_SHORT_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_LONG_KID
&& ctx->items[n].mode != KEYDB_SEARCH_MODE_FPR)
{
ctx->items[n].skipfnc = skip_unusable;
ctx->items[n].skipfncvalue = ctrl;
}
}
}
ctx->want_secret = want_secret;
ctx->kr_handle = keydb_new (ctrl);
if (!ctx->kr_handle)
{
rc = gpg_error_from_syserror ();
getkey_end (ctrl, ctx);
goto leave;
}
if (!ret_kb)
ret_kb = &help_kb;
if (pk)
{
ctx->req_usage = pk->req_usage;
}
rc = lookup (ctrl, ctx, want_secret, ret_kb, &found_key);
if (!rc && pk)
{
pk_from_block (pk, *ret_kb, found_key);
}
release_kbnode (help_kb);
if (retctx) /* Caller wants the context. */
{
if (ctx->extra_list)
{
for (r=ctx->extra_list; r->next; r = r->next)
;
r->next = namelist_expanded;
}
else
ctx->extra_list = namelist_expanded;
namelist_expanded = NULL;
*retctx = ctx;
}
else
{
if (ret_kdbhd)
{
*ret_kdbhd = ctx->kr_handle;
ctx->kr_handle = NULL;
}
getkey_end (ctrl, ctx);
}
leave:
free_strlist (namelist_expanded);
return rc;
}
/* Find a public key identified by NAME.
*
* If name appears to be a valid RFC822 mailbox (i.e., email address)
* and auto key lookup is enabled (mode != GET_PUBKEY_NO_AKL), then
* the specified auto key lookup methods (--auto-key-lookup) are used
* to import the key into the local keyring. Otherwise, just the
* local keyring is consulted.
*
* MODE can be one of:
* GET_PUBKEY_NORMAL - The standard mode
* GET_PUBKEY_NO_AKL - The auto key locate functionality is
* disabled and only the local key ring is
* considered. Note: the local key ring is
* consulted even if local is not in the
* auto-key-locate option list!
* GET_PUBKEY_NO_LOCAL - Only the auto key locate functionality is
* used and no local search is done.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! PK->REQ_USAGE is
* passed through to the lookup function and is a mask of
* PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this
* is non-zero, only keys with the specified usage will be returned.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* NAME is a string, which is turned into a search query using
* classify_user_id.
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* If RET_KDBHD is not NULL, then the new database handle used to
* conduct the search is returned in *RET_KDBHD. This can be used to
* get subsequent results using keydb_search_next or to modify the
* returned record. Note: in this case, no advanced filtering is done
* for subsequent results (e.g., PK->REQ_USAGE is not respected).
* Unlike RETCTX, this is always returned.
*
* If INCLUDE_UNUSABLE is set, then unusable keys (see the
* documentation for skip_unusable for an exact definition) are
* skipped unless they are looked up by key id or by fingerprint.
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
int
get_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
GETKEY_CTX * retctx, PKT_public_key * pk,
const char *name, KBNODE * ret_keyblock,
KEYDB_HANDLE * ret_kdbhd, int include_unusable)
{
int rc;
strlist_t namelist = NULL;
struct akl *akl;
int is_mbox, is_fpr;
KEYDB_SEARCH_DESC fprbuf;
int nodefault = 0;
int anylocalfirst = 0;
int mechanism_type = AKL_NODEFAULT;
/* If RETCTX is not NULL, then RET_KDBHD must be NULL. */
log_assert (retctx == NULL || ret_kdbhd == NULL);
if (retctx)
*retctx = NULL;
/* Does NAME appear to be a mailbox (mail address)? */
is_mbox = is_valid_mailbox (name);
if (!is_mbox && *name == '<' && name[1] && name[strlen(name)-1]=='>'
&& name[1] != '>'
&& is_valid_mailbox_mem (name+1, strlen (name)-2))
{
/* The mailbox is in the form "<foo@example.org>" which is not
* detected by is_valid_mailbox. Set the flag but keep name as
* it is because the bracketed name is actual the better
* specification for a local search and the other methods
* extract the mail address anyway. */
is_mbox = 1;
}
/* If we are called due to --locate-external-key Check whether NAME
* is a fingerprint and then try to lookup that key by configured
* method which support lookup by fingerprint. FPRBUF carries the
- * parsed fingerpint iff IS_FPR is true. */
+ * parsed fingerprint iff IS_FPR is true. */
is_fpr = 0;
if (!is_mbox && mode == GET_PUBKEY_NO_LOCAL)
{
if (!classify_user_id (name, &fprbuf, 1)
&& fprbuf.mode == KEYDB_SEARCH_MODE_FPR)
is_fpr = 1;
}
/* The auto-key-locate feature works as follows: there are a number
* of methods to look up keys. By default, the local keyring is
* tried first. Then, each method listed in the --auto-key-locate is
* tried in the order it appears.
*
* This can be changed as follows:
*
* - if nodefault appears anywhere in the list of options, then
* the local keyring is not tried first, or,
*
* - if local appears anywhere in the list of options, then the
* local keyring is not tried first, but in the order in which
* it was listed in the --auto-key-locate option.
*
* Note: we only save the search context in RETCTX if the local
* method is the first method tried (either explicitly or
* implicitly). */
if (mode == GET_PUBKEY_NO_LOCAL)
nodefault = 1; /* Auto-key-locate but ignore "local". */
else if (mode != GET_PUBKEY_NO_AKL)
{
/* auto-key-locate is enabled. */
/* nodefault is true if "nodefault" or "local" appear. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type == AKL_NODEFAULT || akl->type == AKL_LOCAL)
{
nodefault = 1;
break;
}
/* anylocalfirst is true if "local" appears before any other
search methods (except "nodefault"). */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type != AKL_NODEFAULT)
{
if (akl->type == AKL_LOCAL)
anylocalfirst = 1;
break;
}
}
if (!nodefault)
{
/* "nodefault" didn't occur. Thus, "local" is implicitly the
* first method to try. */
anylocalfirst = 1;
}
if (mode == GET_PUBKEY_NO_LOCAL)
{
/* Force using the AKL. If IS_MBOX is not set this is the final
* error code. */
rc = GPG_ERR_NO_PUBKEY;
}
else if (nodefault && is_mbox)
{
/* Either "nodefault" or "local" (explicitly) appeared in the
* auto key locate list and NAME appears to be an email address.
* Don't try the local keyring. */
rc = GPG_ERR_NO_PUBKEY;
}
else
{
/* Either "nodefault" and "local" don't appear in the auto key
* locate list (in which case we try the local keyring first) or
* NAME does not appear to be an email address (in which case we
* only try the local keyring). In this case, lookup NAME in
* the local keyring. */
add_to_strlist (&namelist, name);
rc = key_byname (ctrl, retctx, namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
/* If the requested name resembles a valid mailbox and automatic
retrieval has been enabled, we try to import the key. */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY
&& mode != GET_PUBKEY_NO_AKL
&& (is_mbox || is_fpr))
{
/* NAME wasn't present in the local keyring (or we didn't try
* the local keyring). Since the auto key locate feature is
* enabled and NAME appears to be an email address, try the auto
* locate feature. */
for (akl = opt.auto_key_locate; akl; akl = akl->next)
{
unsigned char *fpr = NULL;
size_t fpr_len;
int did_akl_local = 0;
int no_fingerprint = 0;
const char *mechanism_string = "?";
mechanism_type = akl->type;
switch (mechanism_type)
{
case AKL_NODEFAULT:
/* This is a dummy mechanism. */
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
break;
case AKL_LOCAL:
if (mode == GET_PUBKEY_NO_LOCAL)
{
/* Note that we get here in is_fpr more, so there is
* no extra check for it required. */
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
}
else
{
mechanism_string = "Local";
did_akl_local = 1;
if (retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
add_to_strlist (&namelist, name);
rc = key_byname (ctrl, anylocalfirst ? retctx : NULL,
namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
break;
case AKL_CERT:
if (is_fpr)
{
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
}
else
{
mechanism_string = "DNS CERT";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
}
break;
case AKL_PKA:
/* This is now obsolete. */
break;
case AKL_DANE:
if (is_fpr)
{
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
break;
}
else
{
mechanism_string = "DANE";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_cert (ctrl, name, 1, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
}
break;
case AKL_WKD:
if (is_fpr)
{
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
}
else
{
mechanism_string = "WKD";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_wkd (ctrl, name, 0, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
}
break;
case AKL_LDAP:
if (is_fpr)
{
mechanism_string = "";
rc = GPG_ERR_NO_PUBKEY;
}
else
{
mechanism_string = "LDAP";
glo_ctrl.in_auto_key_retrieve++;
rc = keyserver_import_ldap (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
}
break;
case AKL_NTDS:
mechanism_string = "NTDS";
glo_ctrl.in_auto_key_retrieve++;
if (is_fpr)
rc = keyserver_import_fprint_ntds (ctrl,
fprbuf.u.fpr, fprbuf.fprlen);
else
rc = keyserver_import_ntds (ctrl, name, &fpr, &fpr_len);
glo_ctrl.in_auto_key_retrieve--;
break;
case AKL_KEYSERVER:
/* Strictly speaking, we don't need to only use a valid
* mailbox for the getname search, but it helps cut down
* on the problem of searching for something like "john"
* and getting a whole lot of keys back. */
if (keyserver_any_configured (ctrl))
{
mechanism_string = "keyserver";
glo_ctrl.in_auto_key_retrieve++;
if (is_fpr)
{
rc = keyserver_import_fprint (ctrl,
fprbuf.u.fpr, fprbuf.fprlen,
opt.keyserver,
KEYSERVER_IMPORT_FLAG_LDAP);
/* Map error codes because Dirmngr returns NO
* DATA if the keyserver does not have the
* requested key. It returns NO KEYSERVER if no
* LDAP keyservers are configured. */
if (gpg_err_code (rc) == GPG_ERR_NO_DATA
|| gpg_err_code (rc) == GPG_ERR_NO_KEYSERVER)
rc = gpg_error (GPG_ERR_NO_PUBKEY);
}
else
{
rc = keyserver_import_mbox (ctrl, name, &fpr, &fpr_len,
opt.keyserver);
}
glo_ctrl.in_auto_key_retrieve--;
}
else
{
mechanism_string = "Unconfigured keyserver";
rc = GPG_ERR_NO_PUBKEY;
}
break;
case AKL_SPEC:
{
struct keyserver_spec *keyserver;
mechanism_string = akl->spec->uri;
keyserver = keyserver_match (akl->spec);
glo_ctrl.in_auto_key_retrieve++;
if (is_fpr)
{
rc = keyserver_import_fprint (ctrl,
fprbuf.u.fpr, fprbuf.fprlen,
opt.keyserver,
KEYSERVER_IMPORT_FLAG_LDAP);
if (gpg_err_code (rc) == GPG_ERR_NO_DATA
|| gpg_err_code (rc) == GPG_ERR_NO_KEYSERVER)
rc = gpg_error (GPG_ERR_NO_PUBKEY);
}
else
{
rc = keyserver_import_mbox (ctrl, name,
&fpr, &fpr_len, keyserver);
}
glo_ctrl.in_auto_key_retrieve--;
}
break;
}
/* Use the fingerprint of the key that we actually fetched.
* This helps prevent problems where the key that we fetched
* doesn't have the same name that we used to fetch it. In
* the case of CERT, this is an actual security
* requirement as the URL might point to a key put in by an
* attacker. By forcing the use of the fingerprint, we
* won't use the attacker's key here. */
if (!rc && (fpr || is_fpr))
{
char fpr_string[MAX_FINGERPRINT_LEN * 2 + 1];
if (is_fpr)
{
log_assert (fprbuf.fprlen <= MAX_FINGERPRINT_LEN);
bin2hex (fprbuf.u.fpr, fprbuf.fprlen, fpr_string);
}
else
{
log_assert (fpr_len <= MAX_FINGERPRINT_LEN);
bin2hex (fpr, fpr_len, fpr_string);
}
if (opt.verbose)
log_info ("auto-key-locate found fingerprint %s\n",
fpr_string);
free_strlist (namelist);
namelist = NULL;
add_to_strlist (&namelist, fpr_string);
}
else if (!rc && !fpr && !did_akl_local)
{ /* The acquisition method said no failure occurred, but
* it didn't return a fingerprint. That's a failure. */
no_fingerprint = 1;
rc = GPG_ERR_NO_PUBKEY;
}
xfree (fpr);
fpr = NULL;
if (!rc && !did_akl_local)
{ /* There was no error and we didn't do a local lookup.
* This means that we imported a key into the local
* keyring. Try to read the imported key from the
* keyring. */
if (retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
rc = key_byname (ctrl, anylocalfirst ? retctx : NULL,
namelist, pk, 0,
include_unusable, ret_keyblock, ret_kdbhd);
}
if (!rc)
{
/* Key found. */
if (opt.verbose)
log_info (_("automatically retrieved '%s' via %s\n"),
name, mechanism_string);
break;
}
if ((gpg_err_code (rc) != GPG_ERR_NO_PUBKEY
|| opt.verbose || no_fingerprint) && *mechanism_string)
log_info (_("error retrieving '%s' via %s: %s\n"),
name, mechanism_string,
no_fingerprint ? _("No fingerprint") : gpg_strerror (rc));
}
}
if (rc && retctx)
{
getkey_end (ctrl, *retctx);
*retctx = NULL;
}
if (retctx && *retctx)
{
GETKEY_CTX ctx = *retctx;
strlist_t sl;
if (ctx->extra_list)
{
for (sl=ctx->extra_list; sl->next; sl = sl->next)
;
sl->next = namelist;
}
else
ctx->extra_list = namelist;
(*retctx)->found_via_akl = mechanism_type;
}
else
free_strlist (namelist);
return rc;
}
/* Comparison machinery for get_best_pubkey_byname. */
/* First we have a struct to cache computed information about the key
* in question. */
struct pubkey_cmp_cookie
{
int valid; /* Is this cookie valid? */
PKT_public_key key; /* The key. */
PKT_user_id *uid; /* The matching UID packet. */
unsigned int validity; /* Computed validity of (KEY, UID). */
u32 creation_time; /* Creation time of the newest subkey
capable of encryption. */
};
/* Then we have a series of helper functions. */
static int
key_is_ok (const PKT_public_key *key)
{
return (! key->has_expired && ! key->flags.revoked
&& key->flags.valid && ! key->flags.disabled);
}
static int
uid_is_ok (const PKT_public_key *key, const PKT_user_id *uid)
{
return key_is_ok (key) && ! uid->flags.revoked;
}
static int
subkey_is_ok (const PKT_public_key *sub)
{
return ! sub->flags.revoked && sub->flags.valid && ! sub->flags.disabled;
}
/* Return true if KEYBLOCK has only expired encryption subkeys. Note
* that the function returns false if the key has no encryption
* subkeys at all or the subkeys are revoked. */
static int
only_expired_enc_subkeys (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *sub;
int any = 0;
for (node = find_next_kbnode (keyblock, PKT_PUBLIC_SUBKEY);
node; node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY))
{
sub = node->pkt->pkt.public_key;
if (!(sub->pubkey_usage & PUBKEY_USAGE_ENC))
continue;
if (!subkey_is_ok (sub))
continue;
any = 1;
if (!sub->has_expired)
return 0;
}
return any? 1 : 0;
}
/* Finally this function compares a NEW key to the former candidate
* OLD. Returns < 0 if the old key is worse, > 0 if the old key is
* better, == 0 if it is a tie. */
static int
pubkey_cmp (ctrl_t ctrl, const char *name, struct pubkey_cmp_cookie *old,
struct pubkey_cmp_cookie *new, KBNODE new_keyblock)
{
kbnode_t n;
if ((new->key.pubkey_usage & PUBKEY_USAGE_ENC) == 0)
new->creation_time = 0;
else
new->creation_time = new->key.timestamp;
for (n = find_next_kbnode (new_keyblock, PKT_PUBLIC_SUBKEY);
n; n = find_next_kbnode (n, PKT_PUBLIC_SUBKEY))
{
PKT_public_key *sub = n->pkt->pkt.public_key;
if ((sub->pubkey_usage & PUBKEY_USAGE_ENC) == 0)
continue;
if (! subkey_is_ok (sub))
continue;
if (sub->timestamp > new->creation_time)
new->creation_time = sub->timestamp;
}
/* When new key has no encryption key, use OLD key. */
if (new->creation_time == 0)
return 1;
for (n = find_next_kbnode (new_keyblock, PKT_USER_ID);
n; n = find_next_kbnode (n, PKT_USER_ID))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
char *mbox = mailbox_from_userid (uid->name, 0);
int match = mbox ? strcasecmp (name, mbox) == 0 : 0;
xfree (mbox);
if (! match)
continue;
new->uid = scopy_user_id (uid);
new->validity =
get_validity (ctrl, new_keyblock, &new->key, uid, NULL, 0) & TRUST_MASK;
new->valid = 1;
if (! old->valid)
return -1; /* No OLD key. */
if (! uid_is_ok (&old->key, old->uid) && uid_is_ok (&new->key, uid))
return -1; /* Validity of the NEW key is better. */
if (new->validity != TRUST_EXPIRED && old->validity < new->validity)
return -1; /* Validity of the NEW key is better. */
if (old->validity == TRUST_EXPIRED && new->validity != TRUST_EXPIRED)
return -1; /* Validity of the NEW key is better. */
if (old->validity == new->validity && uid_is_ok (&new->key, uid)
&& old->creation_time < new->creation_time)
return -1; /* Both keys are of the same validity, but the
NEW key is newer. */
}
/* Stick with the OLD key. */
return 1;
}
/* This function works like get_pubkey_byname, but if the name
* resembles a mail address, the results are ranked and only the best
* result is returned. */
gpg_error_t
get_best_pubkey_byname (ctrl_t ctrl, enum get_pubkey_modes mode,
GETKEY_CTX *retctx, PKT_public_key *pk,
const char *name, KBNODE *ret_keyblock,
int include_unusable)
{
gpg_error_t err;
struct getkey_ctx_s *ctx = NULL;
int is_mbox;
int wkd_tried = 0;
PKT_public_key pk0;
log_assert (ret_keyblock != NULL);
if (retctx)
*retctx = NULL;
memset (&pk0, 0, sizeof pk0);
pk0.req_usage = pk? pk->req_usage : 0;
is_mbox = is_valid_mailbox (name);
if (!is_mbox && *name == '<' && name[1] && name[strlen(name)-1]=='>'
&& name[1] != '>'
&& is_valid_mailbox_mem (name+1, strlen (name)-2))
{
/* The mailbox is in the form "<foo@example.org>" which is not
* detected by is_valid_mailbox. Set the flag but keep name as
* it is because get_pubkey_byname does an is_valid_mailbox_mem
* itself. */
is_mbox = 1;
}
start_over:
if (ctx) /* Clear in case of a start over. */
{
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
getkey_end (ctrl, ctx);
ctx = NULL;
}
err = get_pubkey_byname (ctrl, mode,
&ctx, &pk0, name, ret_keyblock,
NULL, include_unusable);
if (err)
{
goto leave;
}
/* If the keyblock was retrieved from the local database and the key
* has expired, do further checks. However, we can do this only if
* the caller requested a keyblock. */
if (is_mbox && ctx && ctx->found_via_akl == AKL_LOCAL)
{
u32 now = make_timestamp ();
int found;
/* If the key has expired and its origin was the WKD then try to
* get a fresh key from the WKD. We also try this if the key
* has any only expired encryption subkeys. In case we checked
* for a fresh copy in the last 3 hours we won't do that again.
* Unfortunately that does not yet work because KEYUPDATE is
* only updated during import iff the key has actually changed
* (see import.c:import_one). */
if (!wkd_tried && pk0.keyorg == KEYORG_WKD
&& (pk0.keyupdate + 3*3600) < now
&& (pk0.has_expired || only_expired_enc_subkeys (*ret_keyblock)))
{
if (opt.verbose)
log_info (_("checking for a fresh copy of an expired key via %s\n"),
"WKD");
wkd_tried = 1;
glo_ctrl.in_auto_key_retrieve++;
found = !keyserver_import_wkd (ctrl, name, 0, NULL, NULL);
glo_ctrl.in_auto_key_retrieve--;
if (found)
{
release_public_key_parts (&pk0);
goto start_over;
}
}
}
if (is_mbox && ctx)
{
/* Rank results and return only the most relevant key for encryption. */
struct pubkey_cmp_cookie best = { 0 };
struct pubkey_cmp_cookie new = { 0 };
kbnode_t new_keyblock;
copy_public_key (&new.key, &pk0);
if (pubkey_cmp (ctrl, name, &best, &new, *ret_keyblock) >= 0)
{
release_public_key_parts (&new.key);
free_user_id (new.uid);
}
else
best = new;
new.uid = NULL;
while (getkey_next (ctrl, ctx, &new.key, &new_keyblock) == 0)
{
int diff = pubkey_cmp (ctrl, name, &best, &new, new_keyblock);
release_kbnode (new_keyblock);
if (diff < 0)
{
/* New key is better. */
release_public_key_parts (&best.key);
free_user_id (best.uid);
best = new;
}
else if (diff > 0)
{
/* Old key is better. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
}
else
{
/* A tie. Keep the old key. */
release_public_key_parts (&new.key);
free_user_id (new.uid);
}
new.uid = NULL;
}
getkey_end (ctrl, ctx);
ctx = NULL;
free_user_id (best.uid);
best.uid = NULL;
if (best.valid)
{
ctx = xtrycalloc (1, sizeof **retctx);
if (! ctx)
err = gpg_error_from_syserror ();
else
{
ctx->kr_handle = keydb_new (ctrl);
if (! ctx->kr_handle)
{
err = gpg_error_from_syserror ();
xfree (ctx);
ctx = NULL;
if (retctx)
*retctx = NULL;
}
else
{
u32 *keyid = pk_keyid (&best.key);
ctx->exact = 1;
ctx->nitems = 1;
ctx->items[0].mode = KEYDB_SEARCH_MODE_LONG_KID;
ctx->items[0].u.kid[0] = keyid[0];
ctx->items[0].u.kid[1] = keyid[1];
release_kbnode (*ret_keyblock);
*ret_keyblock = NULL;
err = getkey_next (ctrl, ctx, NULL, ret_keyblock);
}
}
if (pk)
*pk = best.key;
else
release_public_key_parts (&best.key);
release_public_key_parts (&pk0);
}
else
{
if (pk)
*pk = pk0;
else
release_public_key_parts (&pk0);
}
}
else
{
if (pk)
*pk = pk0;
else
release_public_key_parts (&pk0);
}
if (err && ctx)
{
getkey_end (ctrl, ctx);
ctx = NULL;
}
if (retctx && ctx)
{
*retctx = ctx;
ctx = NULL;
}
leave:
getkey_end (ctrl, ctx);
return err;
}
/* Get a public key from a file.
*
* PK is the buffer to store the key. The caller needs to make sure
* that PK->REQ_USAGE is valid. PK->REQ_USAGE is passed through to
* the lookup function and is a mask of PUBKEY_USAGE_SIG,
* PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT. If this is non-zero, only
* keys with the specified usage will be returned.
*
* FNAME is the file name. That file should contain exactly one
* keyblock.
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY is returned if the key
* is not found. If R_KEYBLOCK is not NULL and a key was found the
* keyblock is stored there; otherwiese NULL is stored there.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. The caller must release the content of PK by
* calling release_public_key_parts (or, if PK was malloced, using
* free_public_key).
*/
gpg_error_t
get_pubkey_fromfile (ctrl_t ctrl, PKT_public_key *pk, const char *fname,
kbnode_t *r_keyblock)
{
gpg_error_t err;
kbnode_t keyblock;
kbnode_t found_key;
unsigned int infoflags;
if (r_keyblock)
*r_keyblock = NULL;
err = read_key_from_file_or_buffer (ctrl, fname, NULL, 0, &keyblock);
if (!err)
{
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. FIXME: Check whether this still holds. */
merge_selfsigs (ctrl, keyblock);
found_key = finish_lookup (keyblock, pk->req_usage, 0, 0, &infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
pk_from_block (pk, keyblock, found_key);
else
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
}
if (!err && r_keyblock)
*r_keyblock = keyblock;
else
release_kbnode (keyblock);
return err;
}
/* Return a public key from the buffer (BUFFER, BUFLEN). The key is
* onlyretruned if it matches the keyid given in WANT_KEYID. On
* success the key is stored at the caller provided PKBUF structure.
* The caller must release the content of PK by calling
* release_public_key_parts (or, if PKBUF was malloced, using
* free_public_key). If R_KEYBLOCK is not NULL the full keyblock is
* also stored there. */
gpg_error_t
get_pubkey_from_buffer (ctrl_t ctrl, PKT_public_key *pkbuf,
const void *buffer, size_t buflen, u32 *want_keyid,
kbnode_t *r_keyblock)
{
gpg_error_t err;
kbnode_t keyblock;
kbnode_t node;
PKT_public_key *pk;
if (r_keyblock)
*r_keyblock = NULL;
err = read_key_from_file_or_buffer (ctrl, NULL, buffer, buflen, &keyblock);
if (!err)
{
merge_selfsigs (ctrl, keyblock);
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, NULL);
if (pk->keyid[0] == want_keyid[0]
&& pk->keyid[1] == want_keyid[1])
break;
}
}
if (node)
copy_public_key (pkbuf, pk);
else
err = gpg_error (GPG_ERR_NO_PUBKEY);
}
if (!err && r_keyblock)
*r_keyblock = keyblock;
else
release_kbnode (keyblock);
return err;
}
/* Lookup a key with the specified fingerprint.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: this function does an exact search and thus the
* returned public key may be a subkey rather than the primary key.
* Note: The self-signed data has already been merged into the public
* key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xmalloc, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If PK->REQ_USAGE is set, it is used to filter the search results.
* Thus, if PK is not NULL, PK->REQ_USAGE must be valid! See the
* documentation for finish_lookup to understand exactly how this is
* used.
*
* If R_KEYBLOCK is not NULL, then the first result's keyblock is
* returned in *R_KEYBLOCK. This should be freed using
* release_kbnode().
*
* FPRINT is a byte array whose contents is the fingerprint to use as
* the search term. FPRINT_LEN specifies the length of the
* fingerprint (in bytes). Currently, only 16, 20, and 32-byte
* fingerprints are supported.
*
* FIXME: We should replace this with the _byname function. This can
* be done by creating a userID conforming to the unified fingerprint
* style. */
int
get_pubkey_byfprint (ctrl_t ctrl, PKT_public_key *pk, kbnode_t *r_keyblock,
const byte * fprint, size_t fprint_len)
{
int rc;
if (r_keyblock)
*r_keyblock = NULL;
if (fprint_len == 32 || fprint_len == 20 || fprint_len == 16)
{
struct getkey_ctx_s ctx;
KBNODE kb = NULL;
KBNODE found_key = NULL;
memset (&ctx, 0, sizeof ctx);
ctx.exact = 1;
ctx.not_allocated = 1;
/* FIXME: We should get the handle from the cache like we do in
* get_pubkey. */
ctx.kr_handle = keydb_new (ctrl);
if (!ctx.kr_handle)
return gpg_error_from_syserror ();
ctx.nitems = 1;
ctx.items[0].mode = KEYDB_SEARCH_MODE_FPR;
memcpy (ctx.items[0].u.fpr, fprint, fprint_len);
ctx.items[0].fprlen = fprint_len;
if (pk)
ctx.req_usage = pk->req_usage;
rc = lookup (ctrl, &ctx, 0, &kb, &found_key);
if (!rc && pk)
pk_from_block (pk, kb, found_key);
if (!rc && r_keyblock)
{
*r_keyblock = kb;
kb = NULL;
}
release_kbnode (kb);
getkey_end (ctrl, &ctx);
}
else
rc = GPG_ERR_GENERAL; /* Oops */
return rc;
}
/* This function is similar to get_pubkey_byfprint, but it doesn't
* merge the self-signed data into the public key and subkeys or into
* the user ids. It also doesn't add the key to the user id cache.
* Further, this function ignores PK->REQ_USAGE.
*
* This function is intended to avoid recursion and, as such, should
* only be used in very specific situations.
*
* Like get_pubkey_byfprint, PK may be NULL. In that case, this
* function effectively just checks for the existence of the key. */
gpg_error_t
get_pubkey_byfprint_fast (ctrl_t ctrl, PKT_public_key * pk,
const byte * fprint, size_t fprint_len)
{
gpg_error_t err;
KBNODE keyblock;
err = get_keyblock_byfprint_fast (ctrl,
&keyblock, NULL, fprint, fprint_len, 0);
if (!err)
{
if (pk)
copy_public_key (pk, keyblock->pkt->pkt.public_key);
release_kbnode (keyblock);
}
return err;
}
/* This function is similar to get_pubkey_byfprint_fast but returns a
* keydb handle at R_HD and the keyblock at R_KEYBLOCK. R_KEYBLOCK or
* R_HD may be NULL. If LOCK is set the handle has been opend in
* locked mode and keydb_disable_caching () has been called. On error
* R_KEYBLOCK is set to NULL but R_HD must be released by the caller;
* it may have a value of NULL, though. This allows one to do an insert
* operation on a locked keydb handle. */
gpg_error_t
get_keyblock_byfprint_fast (ctrl_t ctrl,
kbnode_t *r_keyblock, KEYDB_HANDLE *r_hd,
const byte *fprint, size_t fprint_len, int lock)
{
gpg_error_t err;
KEYDB_HANDLE hd;
kbnode_t keyblock;
byte fprbuf[MAX_FINGERPRINT_LEN];
int i;
if (r_keyblock)
*r_keyblock = NULL;
if (r_hd)
*r_hd = NULL;
for (i = 0; i < MAX_FINGERPRINT_LEN && i < fprint_len; i++)
fprbuf[i] = fprint[i];
hd = keydb_new (ctrl);
if (!hd)
return gpg_error_from_syserror ();
if (lock)
{
err = keydb_lock (hd);
if (err)
{
/* If locking did not work, we better don't return a handle
* at all - there was a reason that locking has been
* requested. */
keydb_release (hd);
return err;
}
keydb_disable_caching (hd);
}
/* For all other errors we return the handle. */
if (r_hd)
*r_hd = hd;
err = keydb_search_fpr (hd, fprbuf, fprint_len);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
if (!r_hd)
keydb_release (hd);
return gpg_error (GPG_ERR_NO_PUBKEY);
}
err = keydb_get_keyblock (hd, &keyblock);
if (err)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (err));
if (!r_hd)
keydb_release (hd);
return gpg_error (GPG_ERR_NO_PUBKEY);
}
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_PUBLIC_SUBKEY);
/* Not caching key here since it won't have all of the fields
properly set. */
if (r_keyblock)
*r_keyblock = keyblock;
else
release_kbnode (keyblock);
if (!r_hd)
keydb_release (hd);
return 0;
}
const char *
parse_def_secret_key (ctrl_t ctrl)
{
KEYDB_HANDLE hd = NULL;
strlist_t t;
static int warned;
for (t = opt.def_secret_key; t; t = t->next)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
kbnode_t node;
int any_revoked, any_expired, any_disabled;
err = classify_user_id (t->d, &desc, 1);
if (err)
{
log_error (_("secret key \"%s\" not found: %s\n"),
t->d, gpg_strerror (err));
if (!opt.quiet)
log_info (_("(check argument of option '%s')\n"), "--default-key");
continue;
}
if (! hd)
{
hd = keydb_new (ctrl);
if (!hd)
return NULL;
}
else
keydb_search_reset (hd);
err = keydb_search (hd, &desc, 1, NULL);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
continue;
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), t->d, gpg_strerror (err));
t = NULL;
break;
}
err = keydb_get_keyblock (hd, &kb);
if (err)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (err));
continue;
}
merge_selfsigs (ctrl, kb);
any_revoked = any_expired = any_disabled = 0;
err = gpg_error (GPG_ERR_NO_SECKEY);
node = kb;
do
{
PKT_public_key *pk = node->pkt->pkt.public_key;
/* Check if the key is valid. */
if (pk->flags.revoked)
{
any_revoked = 1;
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "revoked");
continue;
}
if (pk->has_expired)
{
any_expired = 1;
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "expired");
continue;
}
if (pk_is_disabled (pk))
{
any_disabled = 1;
if (DBG_LOOKUP)
log_debug ("not using %s as default key, %s",
keystr_from_pk (pk), "disabled");
continue;
}
if (agent_probe_secret_key (ctrl, pk))
{
/* This is a valid key. */
err = 0;
break;
}
}
while ((node = find_next_kbnode (node, PKT_PUBLIC_SUBKEY)));
release_kbnode (kb);
if (err)
{
if (! warned && ! opt.quiet)
{
gpg_err_code_t ec;
/* Try to get a better error than no secret key if we
* only know that the public key is not usable. */
if (any_revoked)
ec = GPG_ERR_CERT_REVOKED;
else if (any_expired)
ec = GPG_ERR_KEY_EXPIRED;
else if (any_disabled)
ec = GPG_ERR_KEY_DISABLED;
else
ec = GPG_ERR_NO_SECKEY;
log_info (_("Warning: not using '%s' as default key: %s\n"),
t->d, gpg_strerror (ec));
print_reported_error (err, ec);
}
}
else
{
if (! warned && ! opt.quiet)
log_info (_("using \"%s\" as default secret key for signing\n"),
t->d);
break;
}
}
if (! warned && opt.def_secret_key && ! t)
log_info (_("all values passed to '%s' ignored\n"),
"--default-key");
warned = 1;
if (hd)
keydb_release (hd);
if (t)
return t->d;
return NULL;
}
/* Look up a secret key.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If --default-key was set, then the specified key is looked up. (In
* this case, the default key is returned even if it is considered
* unusable. See the documentation for skip_unusable for exactly what
* this means.)
*
* Otherwise, this initiates a DB scan that returns all keys that are
* usable (see previous paragraph for exactly what usable means) and
* for which a secret key is available.
*
* This function returns the first match. Additional results can be
* returned using getkey_next. */
gpg_error_t
get_seckey_default (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
strlist_t namelist = NULL;
int include_unusable = 1;
const char *def_secret_key = parse_def_secret_key (ctrl);
if (def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else
include_unusable = 0;
err = key_byname (ctrl, NULL, namelist, pk, 1, include_unusable, NULL, NULL);
free_strlist (namelist);
return err;
}
/* Search for keys matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAMES is not NULL, then a search query is constructed using
* classify_user_id on each of the strings in the list. (Recall: the
* database does an OR of the terms, not an AND.) If NAMES is
* NULL, then all results are returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found. */
gpg_error_t
getkey_bynames (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk,
strlist_t names, int want_secret, kbnode_t *ret_keyblock)
{
return key_byname (ctrl, retctx, names, pk, want_secret, 1,
ret_keyblock, NULL);
}
/* Search for one key matching some criteria.
*
* If RETCTX is not NULL, then the constructed context is returned in
* *RETCTX so that getpubkey_next can be used to get subsequent
* results. In this case, getkey_end() must be used to free the
* search context. If RETCTX is not NULL, then RET_KDBHD must be
* NULL.
*
* If PK is not NULL, the public key of the first result is returned
* in *PK. Note: PK->REQ_USAGE must be valid!!! If PK->REQ_USAGE is
* set, it is used to filter the search results. See the
* documentation for finish_lookup to understand exactly how this is
* used. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xfree, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* If NAME is not NULL, then a search query is constructed using
* classify_user_id on the string. In this case, even unusable keys
* (see the documentation for skip_unusable for an exact definition of
* unusable) are returned. Otherwise, if --default-key was set, then
* that key is returned (even if it is unusable). If neither of these
* conditions holds, then the first usable key is returned.
*
* If WANT_SECRET is set, then only keys with an available secret key
* (either locally or via key registered on a smartcard) are returned.
*
* This function does not skip unusable keys (see the documentation
* for skip_unusable for an exact definition).
*
* If RET_KEYBLOCK is not NULL, the keyblock is returned in
* *RET_KEYBLOCK. This should be freed using release_kbnode().
*
* This function returns 0 on success. Otherwise, an error code is
* returned. In particular, GPG_ERR_NO_PUBKEY or GPG_ERR_NO_SECKEY
* (if want_secret is set) is returned if the key is not found.
*
* FIXME: We also have the get_pubkey_byname function which has a
* different semantic. Should be merged with this one. */
gpg_error_t
getkey_byname (ctrl_t ctrl, getkey_ctx_t *retctx, PKT_public_key *pk,
const char *name, int want_secret, kbnode_t *ret_keyblock)
{
gpg_error_t err;
strlist_t namelist = NULL;
int with_unusable = 1;
const char *def_secret_key = NULL;
if (want_secret && !name)
def_secret_key = parse_def_secret_key (ctrl);
if (want_secret && !name && def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else if (name)
add_to_strlist (&namelist, name);
else
with_unusable = 0;
err = key_byname (ctrl, retctx, namelist, pk, want_secret, with_unusable,
ret_keyblock, NULL);
/* FIXME: Check that we really return GPG_ERR_NO_SECKEY if
WANT_SECRET has been used. */
free_strlist (namelist);
return err;
}
/* Return the next search result.
*
* If PK is not NULL, the public key of the next result is returned in
* *PK. Note: The self-signed data has already been merged into the
* public key using merge_selfsigs. Free *PK by calling
* release_public_key_parts (or, if PK was allocated using xmalloc, you
* can use free_public_key, which calls release_public_key_parts(PK)
* and then xfree(PK)).
*
* RET_KEYBLOCK can be given as NULL; if it is not NULL it the entire
* found keyblock is returned which must be released with
* release_kbnode. If the function returns an error NULL is stored at
* RET_KEYBLOCK.
*
* The self-signed data has already been merged into the public key
* using merge_selfsigs. */
gpg_error_t
getkey_next (ctrl_t ctrl, getkey_ctx_t ctx,
PKT_public_key *pk, kbnode_t *ret_keyblock)
{
int rc; /* Fixme: Make sure this is proper gpg_error */
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
/* We need to disable the caching so that for an exact key search we
won't get the result back from the cache and thus end up in an
endless loop. The endless loop can occur, because the cache is
used without respecting the current file pointer! */
keydb_disable_caching (ctx->kr_handle);
/* FOUND_KEY is only valid as long as RET_KEYBLOCK is. If the
* caller wants PK, but not RET_KEYBLOCK, we need hand in our own
* keyblock. */
if (pk && ret_keyblock == NULL)
ret_keyblock = &keyblock;
rc = lookup (ctrl, ctx, ctx->want_secret,
ret_keyblock, pk ? &found_key : NULL);
if (!rc && pk)
{
log_assert (found_key);
pk_from_block (pk, NULL, found_key);
release_kbnode (keyblock);
}
return rc;
}
/* Release any resources used by a key listing context. This must be
* called on the context returned by, e.g., getkey_byname. */
void
getkey_end (ctrl_t ctrl, getkey_ctx_t ctx)
{
if (ctx)
{
#ifdef HAVE_W32_SYSTEM
/* FIXME: This creates a big regression for Windows because the
* keyring is only released after the global ctrl is released.
* So if an operation does a getkey and then tries to modify the
* keyring it will fail on Windows with a sharing violation. We
* need to modify all keyring write operations to also take the
* ctrl and close the cached_getkey_kdb handle to make writing
* work. See: GnuPG-bug-id: 3097 */
(void)ctrl;
keydb_release (ctx->kr_handle);
#else /*!HAVE_W32_SYSTEM*/
if (ctrl && !ctrl->cached_getkey_kdb)
ctrl->cached_getkey_kdb = ctx->kr_handle;
else
keydb_release (ctx->kr_handle);
#endif /*!HAVE_W32_SYSTEM*/
free_strlist (ctx->extra_list);
if (!ctx->not_allocated)
xfree (ctx);
}
}
/************************************************
************* Merging stuff ********************
************************************************/
/* Set the mainkey_id fields for all keys in KEYBLOCK. This is
* usually done by merge_selfsigs but at some places we only need the
* main_kid not a full merge. The function also guarantees that all
* pk->keyids are computed. */
void
setup_main_keyids (kbnode_t keyblock)
{
u32 kid[2], mainkid[2];
kbnode_t kbctx, node;
PKT_public_key *pk;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
BUG ();
pk = keyblock->pkt->pkt.public_key;
keyid_from_pk (pk, mainkid);
for (kbctx=NULL; (node = walk_kbnode (keyblock, &kbctx, 0)); )
{
if (!(node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
continue;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, kid); /* Make sure pk->keyid is set. */
if (!pk->main_keyid[0] && !pk->main_keyid[1])
{
pk->main_keyid[0] = mainkid[0];
pk->main_keyid[1] = mainkid[1];
}
}
}
/* KEYBLOCK corresponds to a public key block. This function merges
* much of the information from the self-signed data into the public
* key, public subkey and user id data structures. If you use the
* high-level search API (e.g., get_pubkey) for looking up key blocks,
* then you don't need to call this function. This function is
* useful, however, if you change the keyblock, e.g., by adding or
* removing a self-signed data packet. */
void
merge_keys_and_selfsig (ctrl_t ctrl, kbnode_t keyblock)
{
if (!keyblock)
;
else if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
merge_selfsigs (ctrl, keyblock);
else
log_debug ("FIXME: merging secret key blocks is not anymore available\n");
}
/* This function parses the key flags and returns PUBKEY_USAGE_ flags. */
unsigned int
parse_key_usage (PKT_signature * sig)
{
int key_usage = 0;
const byte *p;
size_t n;
byte flags;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_FLAGS, &n);
if (p && n)
{
/* First octet of the keyflags. */
flags = *p;
if (flags & 1)
{
key_usage |= PUBKEY_USAGE_CERT;
flags &= ~1;
}
if (flags & 2)
{
key_usage |= PUBKEY_USAGE_SIG;
flags &= ~2;
}
/* We do not distinguish between encrypting communications and
encrypting storage. */
if (flags & (0x04 | 0x08))
{
key_usage |= PUBKEY_USAGE_ENC;
flags &= ~(0x04 | 0x08);
}
if (flags & 0x20)
{
key_usage |= PUBKEY_USAGE_AUTH;
flags &= ~0x20;
}
if ((flags & 0x80))
{
key_usage |= PUBKEY_USAGE_GROUP;
flags &= ~0x80;
}
if (flags)
key_usage |= PUBKEY_USAGE_UNKNOWN;
n--;
p++;
if (n)
{
flags = *p;
if ((flags & 0x04))
key_usage |= PUBKEY_USAGE_RENC;
if ((flags & 0x08))
key_usage |= PUBKEY_USAGE_TIME;
}
if (!key_usage)
key_usage |= PUBKEY_USAGE_NONE;
}
else if (p) /* Key flags of length zero. */
key_usage |= PUBKEY_USAGE_NONE;
/* We set PUBKEY_USAGE_UNKNOWN to indicate that this key has a
capability that we do not handle. This serves to distinguish
between a zero key usage which we handle as the default
capabilities for that algorithm, and a usage that we do not
handle. Likewise we use PUBKEY_USAGE_NONE to indicate that
key_flags have been given but they do not specify any usage. */
return key_usage;
}
/* Apply information from SIGNODE (which is the valid self-signature
* associated with that UID) to the UIDNODE:
- * - wether the UID has been revoked
+ * - whether the UID has been revoked
* - assumed creation date of the UID
* - temporary store the keyflags here
* - temporary store the key expiration time here
* - mark whether the primary user ID flag hat been set.
* - store the preferences
*/
static void
fixup_uidnode (KBNODE uidnode, KBNODE signode, u32 keycreated)
{
PKT_user_id *uid = uidnode->pkt->pkt.user_id;
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p, *sym, *aead, *hash, *zip;
size_t n, nsym, naead, nhash, nzip;
sig->flags.chosen_selfsig = 1;/* We chose this one. */
uid->created = 0; /* Not created == invalid. */
if (IS_UID_REV (sig))
{
uid->flags.revoked = 1;
return; /* Has been revoked. */
}
else
uid->flags.revoked = 0;
uid->expiredate = sig->expiredate;
if (sig->flags.expired)
{
uid->flags.expired = 1;
return; /* Has expired. */
}
else
uid->flags.expired = 0;
uid->created = sig->timestamp; /* This one is okay. */
uid->selfsigversion = sig->version;
/* If we got this far, it's not expired :) */
uid->flags.expired = 0;
/* Store the key flags in the helper variable for later processing. */
uid->help_key_usage = parse_key_usage (sig);
/* Ditto for the key expiration. */
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
uid->help_key_expire = keycreated + buf32_to_u32 (p);
else
uid->help_key_expire = 0;
/* Set the primary user ID flag - we will later wipe out some
* of them to only have one in our keyblock. */
uid->flags.primary = 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p)
uid->flags.primary = 2;
/* We could also query this from the unhashed area if it is not in
* the hased area and then later try to decide which is the better
* there should be no security problem with this.
* For now we only look at the hashed one. */
/* Now build the preferences list. These must come from the
hashed section so nobody can modify the ciphers a key is
willing to accept. */
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_SYM, &n);
sym = p;
nsym = p ? n : 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_AEAD, &n);
aead = p;
naead = p ? n : 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_HASH, &n);
hash = p;
nhash = p ? n : 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_COMPR, &n);
zip = p;
nzip = p ? n : 0;
if (uid->prefs)
xfree (uid->prefs);
n = nsym + naead + nhash + nzip;
if (!n)
uid->prefs = NULL;
else
{
uid->prefs = xmalloc (sizeof (*uid->prefs) * (n + 1));
n = 0;
for (; nsym; nsym--, n++)
{
uid->prefs[n].type = PREFTYPE_SYM;
uid->prefs[n].value = *sym++;
}
for (; naead; naead--, n++)
{
uid->prefs[n].type = PREFTYPE_AEAD;
uid->prefs[n].value = *aead++;
}
for (; nhash; nhash--, n++)
{
uid->prefs[n].type = PREFTYPE_HASH;
uid->prefs[n].value = *hash++;
}
for (; nzip; nzip--, n++)
{
uid->prefs[n].type = PREFTYPE_ZIP;
uid->prefs[n].value = *zip++;
}
uid->prefs[n].type = PREFTYPE_NONE; /* End of list marker */
uid->prefs[n].value = 0;
}
/* See whether we have the MDC feature. */
uid->flags.mdc = 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n);
if (p && n && (p[0] & 0x01))
uid->flags.mdc = 1;
/* See whether we have the AEAD feature. */
uid->flags.aead = 0;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n);
if (p && n && (p[0] & 0x02))
uid->flags.aead = 1;
/* And the keyserver modify flag. */
uid->flags.ks_modify = 1;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KS_FLAGS, &n);
if (p && n && (p[0] & 0x80))
uid->flags.ks_modify = 0;
}
static void
sig_to_revoke_info (PKT_signature * sig, struct revoke_info *rinfo)
{
rinfo->date = sig->timestamp;
rinfo->algo = sig->pubkey_algo;
rinfo->keyid[0] = sig->keyid[0];
rinfo->keyid[1] = sig->keyid[1];
}
/* Given a keyblock, parse the key block and extract various pieces of
* information and save them with the primary key packet and the user
* id packets. For instance, some information is stored in signature
* packets. We find the latest such valid packet (since the user can
* change that information) and copy its contents into the
* PKT_public_key.
*
* Note that R_REVOKED may be set to 0, 1 or 2.
*
* This function fills in the following fields in the primary key's
* keyblock:
*
* main_keyid (computed)
* revkey / numrevkeys (derived from self signed key data)
* flags.valid (whether we have at least 1 self-sig)
* flags.maybe_revoked (whether a designed revoked the key, but
* we are missing the key to check the sig)
* selfsigversion (highest version of any valid self-sig)
* pubkey_usage (derived from most recent self-sig or most
* recent user id)
* has_expired (various sources)
* expiredate (various sources)
*
* See the documentation for fixup_uidnode for how the user id packets
* are modified. In addition to that the primary user id's is_primary
* field is set to 1 and the other user id's is_primary are set to 0.
*/
static void
merge_selfsigs_main (ctrl_t ctrl, kbnode_t keyblock, int *r_revoked,
struct revoke_info *rinfo)
{
PKT_public_key *pk = NULL;
KBNODE k;
u32 kid[2];
u32 sigdate, uiddate, uiddate2;
KBNODE signode, uidnode, uidnode2;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0; /* Creation time of the key. */
u32 key_expire = 0;
int key_expire_seen = 0;
byte sigversion = 0;
*r_revoked = 0;
memset (rinfo, 0, sizeof (*rinfo));
/* Section 11.1 of RFC 4880 determines the order of packets within a
* message. There are three sections, which must occur in the
* following order: the public key, the user ids and user attributes
* and the subkeys. Within each section, each primary packet (e.g.,
* a user id packet) is followed by one or more signature packets,
* which modify that packet. */
/* According to Section 11.1 of RFC 4880, the public key must be the
first packet. Note that parse_keyblock_image ensures that the
first packet is the public key. */
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
BUG ();
pk = keyblock->pkt->pkt.public_key;
keytimestamp = pk->timestamp;
keyid_from_pk (pk, kid);
pk->main_keyid[0] = kid[0];
pk->main_keyid[1] = kid[1];
if (pk->version < 4)
{
/* Before v4 the key packet itself contains the expiration date
* and there was no way to change it, so we start with the one
* from the key packet. We do not support v3 keys anymore but
* we keep the code in case a future key versions introduces a
* hard expire time again. */
key_expire = pk->max_expiredate;
key_expire_seen = 1;
}
/* First pass:
*
* - Find the latest direct key self-signature. We assume that the
* newest one overrides all others.
*
* - Determine whether the key has been revoked.
*
* - Gather all revocation keys (unlike other data, we don't just
* take them from the latest self-signed packet).
*
* - Determine max (sig[...]->version).
*/
/* Reset this in case this key was already merged. */
xfree (pk->revkey);
pk->revkey = NULL;
pk->numrevkeys = 0;
signode = NULL;
sigdate = 0; /* Helper variable to find the latest signature. */
/* According to Section 11.1 of RFC 4880, the public key comes first
* and is immediately followed by any signature packets that modify
* it. */
for (k = keyblock;
k && k->pkt->pkttype != PKT_USER_ID
&& k->pkt->pkttype != PKT_ATTRIBUTE
&& k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
{ /* Self sig. */
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_KEY_REV (sig))
{
/* Key has been revoked - there is no way to
* override such a revocation, so we theoretically
* can stop now. We should not cope with expiration
* times for revocations here because we have to
* assume that an attacker can generate all kinds of
* signatures. However due to the fact that the key
* has been revoked it does not harm either and by
* continuing we gather some more info on that
* key. */
*r_revoked = 1;
sig_to_revoke_info (sig, rinfo);
}
else if (IS_KEY_SIG (sig))
{
/* Add the indicated revocations keys from all
* signatures not just the latest. We do this
* because you need multiple 1F sigs to properly
* handle revocation keys (PGP does it this way, and
* a revocation key could be sensitive and hence in
* a different signature). */
if (sig->revkey)
{
int i;
pk->revkey =
xrealloc (pk->revkey, sizeof (struct revocation_key) *
(pk->numrevkeys + sig->numrevkeys));
for (i = 0; i < sig->numrevkeys; i++, pk->numrevkeys++)
{
pk->revkey[pk->numrevkeys].class
= sig->revkey[i].class;
pk->revkey[pk->numrevkeys].algid
= sig->revkey[i].algid;
pk->revkey[pk->numrevkeys].fprlen
= sig->revkey[i].fprlen;
memcpy (pk->revkey[pk->numrevkeys].fpr,
sig->revkey[i].fpr, sig->revkey[i].fprlen);
memset (pk->revkey[pk->numrevkeys].fpr
+ sig->revkey[i].fprlen,
0,
sizeof (sig->revkey[i].fpr)
- sig->revkey[i].fprlen);
}
}
if (sig->timestamp >= sigdate)
{ /* This is the latest signature so far. */
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
}
}
/* Remove dupes from the revocation keys. */
if (pk->revkey)
{
int i, j, x, changed = 0;
for (i = 0; i < pk->numrevkeys; i++)
{
for (j = i + 1; j < pk->numrevkeys; j++)
{
if (memcmp (&pk->revkey[i], &pk->revkey[j],
sizeof (struct revocation_key)) == 0)
{
/* remove j */
for (x = j; x < pk->numrevkeys - 1; x++)
pk->revkey[x] = pk->revkey[x + 1];
pk->numrevkeys--;
j--;
changed = 1;
}
}
}
if (changed)
pk->revkey = xrealloc (pk->revkey,
pk->numrevkeys *
sizeof (struct revocation_key));
}
/* SIGNODE is the direct key signature packet (sigclass 0x1f) with
* the latest creation time. Extract some information from it. */
if (signode)
{
/* Some information from a direct key signature take precedence
* over the same information given in UID sigs. */
PKT_signature *sig = signode->pkt->pkt.signature;
const byte *p;
key_usage = parse_key_usage (sig);
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
{
key_expire = keytimestamp + buf32_to_u32 (p);
key_expire_seen = 1;
}
/* Mark that key as valid: One direct key signature should
* render a key as valid. */
pk->flags.valid = 1;
}
/* Pass 1.5: Look for key revocation signatures that were not made
* by the key (i.e. did a revocation key issue a revocation for
* us?). Only bother to do this if there is a revocation key in the
* first place and we're not revoked already. */
if (!*r_revoked && pk->revkey)
for (k = keyblock; k && k->pkt->pkttype != PKT_USER_ID; k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (IS_KEY_REV (sig) &&
(sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1]))
{
int rc = check_revocation_keys (ctrl, pk, sig);
if (rc == 0)
{
*r_revoked = 2;
sig_to_revoke_info (sig, rinfo);
/* Don't continue checking since we can't be any
* more revoked than this. */
break;
}
else if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
pk->flags.maybe_revoked = 1;
/* A failure here means the sig did not verify, was
* not issued by a revocation key, or a revocation
* key loop was broken. If a revocation key isn't
* findable, however, the key might be revoked and
* we don't know it. */
/* Fixme: In the future handle subkey and cert
* revocations? PGP doesn't, but it's in 2440. */
}
}
}
/* Second pass: Look at the self-signature of all user IDs. */
/* According to RFC 4880 section 11.1, user id and attribute packets
* are in the second section, after the public key packet and before
* the subkey packets. */
signode = uidnode = NULL;
sigdate = 0; /* Helper variable to find the latest signature in one UID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID || k->pkt->pkttype == PKT_ATTRIBUTE)
{ /* New user id packet. */
/* Apply the data from the most recent self-signed packet to
* the preceding user id packet. */
if (uidnode && signode)
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* Clear SIGNODE. The only relevant self-signed data for
* UIDNODE follows it. */
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else
uidnode = NULL;
signode = NULL;
sigdate = 0;
}
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] == kid[0] && sig->keyid[1] == kid[1])
{
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* signature did not verify */
else if ((IS_UID_SIG (sig) || IS_UID_REV (sig))
&& sig->timestamp >= sigdate)
{
/* Note: we allow invalidation of cert revocations
* by a newer signature. An attacker can't use this
* because a key should be revoked with a key revocation.
* The reason why we have to allow for that is that at
* one time an email address may become invalid but later
* the same email address may become valid again (hired,
* fired, hired again). */
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
if (sig->version > sigversion)
sigversion = sig->version;
}
}
}
}
if (uidnode && signode)
{
fixup_uidnode (uidnode, signode, keytimestamp);
pk->flags.valid = 1;
}
/* If the key isn't valid yet, and we have
* --allow-non-selfsigned-uid set, then force it valid. */
if (!pk->flags.valid && opt.allow_non_selfsigned_uid)
{
if (opt.verbose)
log_info (_("Invalid key %s made valid by"
" --allow-non-selfsigned-uid\n"), keystr_from_pk (pk));
pk->flags.valid = 1;
}
/* The key STILL isn't valid, so try and find an ultimately
* trusted signature. */
if (!pk->flags.valid)
{
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
uidnode = k;
else if (k->pkt->pkttype == PKT_SIGNATURE && uidnode)
{
PKT_signature *sig = k->pkt->pkt.signature;
if (sig->keyid[0] != kid[0] || sig->keyid[1] != kid[1])
{
PKT_public_key *ultimate_pk;
ultimate_pk = xmalloc_clear (sizeof (*ultimate_pk));
/* We don't want to use the full get_pubkey to avoid
* infinite recursion in certain cases. There is no
* reason to check that an ultimately trusted key is
* still valid - if it has been revoked the user
* should also remove the ultimate trust flag. */
if (get_pubkey_fast (ctrl, ultimate_pk, sig->keyid) == 0
&& check_key_signature2 (ctrl,
keyblock, k, ultimate_pk,
NULL, NULL, NULL, NULL) == 0
&& get_ownertrust (ctrl, ultimate_pk) == TRUST_ULTIMATE)
{
free_public_key (ultimate_pk);
pk->flags.valid = 1;
break;
}
free_public_key (ultimate_pk);
}
}
}
}
/* Record the highest selfsig version so we know if this is a v3 key
* through and through, or a v3 key with a v4 selfsig somewhere.
* This is useful in a few places to know if the key must be treated
* as PGP2-style or OpenPGP-style. Note that a selfsig revocation
* with a higher version number will also raise this value. This is
* okay since such a revocation must be issued by the user (i.e. it
* cannot be issued by someone else to modify the key behavior.) */
pk->selfsigversion = sigversion;
/* Now that we had a look at all user IDs we can now get some
* information from those user IDs. */
if (!key_usage)
{
/* Find the latest user ID with key flags set. */
uiddate = 0; /* Helper to find the latest user ID. */
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_usage
&& (uid->created > uiddate || (!uid->created && !uiddate)))
{
key_usage = uid->help_key_usage;
uiddate = uid->created;
}
}
}
}
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (pk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (pk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
/* Whatever happens, it's a primary key, so it can certify. */
pk->pubkey_usage = key_usage | PUBKEY_USAGE_CERT;
if (!key_expire_seen)
{
/* Find the latest valid user ID with a key expiration set.
* This may be a different one than from usage computation above
* because some user IDs may have no expiration date set. */
uiddate = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->help_key_expire
&& (uid->created > uiddate || (!uid->created && !uiddate)))
{
key_expire = uid->help_key_expire;
uiddate = uid->created;
}
}
}
}
/* Currently only the not anymore supported v3 keys have a maximum
* expiration date, but future key versions may get this feature again. */
if (key_expire == 0
|| (pk->max_expiredate && key_expire > pk->max_expiredate))
key_expire = pk->max_expiredate;
pk->has_expired = key_expire >= curtime ? 0 : key_expire;
pk->expiredate = key_expire;
/* Fixme: we should see how to get rid of the expiretime fields but
* this needs changes at other places too. */
/* And now find the real primary user ID and delete all others. */
uiddate = uiddate2 = 0;
uidnode = uidnode2 = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID && !k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (uid->flags.primary)
{
if (uid->created > uiddate)
{
uiddate = uid->created;
uidnode = k;
}
else if (uid->created == uiddate && uidnode)
{
/* The dates are equal, so we need to do a different
* (and arbitrary) comparison. This should rarely,
* if ever, happen. It's good to try and guarantee
* that two different GnuPG users with two different
* keyrings at least pick the same primary. */
if (cmp_user_ids (uid, uidnode->pkt->pkt.user_id) > 0)
uidnode = k;
}
}
else
{
if (uid->created > uiddate2)
{
uiddate2 = uid->created;
uidnode2 = k;
}
else if (uid->created == uiddate2 && uidnode2)
{
if (cmp_user_ids (uid, uidnode2->pkt->pkt.user_id) > 0)
uidnode2 = k;
}
}
}
}
if (uidnode)
{
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID &&
!k->pkt->pkt.user_id->attrib_data)
{
PKT_user_id *uid = k->pkt->pkt.user_id;
if (k != uidnode)
uid->flags.primary = 0;
}
}
}
else if (uidnode2)
{
/* None is flagged primary - use the latest user ID we have,
* and disambiguate with the arbitrary packet comparison. */
uidnode2->pkt->pkt.user_id->flags.primary = 1;
}
else
{
/* None of our uids were self-signed, so pick the one that
* sorts first to be the primary. This is the best we can do
* here since there are no self sigs to date the uids. */
uidnode = NULL;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data)
{
if (!uidnode)
{
uidnode = k;
uidnode->pkt->pkt.user_id->flags.primary = 1;
continue;
}
else
{
if (cmp_user_ids (k->pkt->pkt.user_id,
uidnode->pkt->pkt.user_id) > 0)
{
uidnode->pkt->pkt.user_id->flags.primary = 0;
uidnode = k;
uidnode->pkt->pkt.user_id->flags.primary = 1;
}
else
{
/* just to be safe: */
k->pkt->pkt.user_id->flags.primary = 0;
}
}
}
}
}
}
/* Convert a buffer to a signature. Useful for 0x19 embedded sigs.
* Caller must free the signature when they are done. */
static PKT_signature *
buf_to_sig (const byte * buf, size_t len)
{
PKT_signature *sig = xmalloc_clear (sizeof (PKT_signature));
IOBUF iobuf = iobuf_temp_with_content (buf, len);
int save_mode = set_packet_list_mode (0);
if (parse_signature (iobuf, PKT_SIGNATURE, len, sig) != 0)
{
free_seckey_enc (sig);
sig = NULL;
}
set_packet_list_mode (save_mode);
iobuf_close (iobuf);
return sig;
}
/* Use the self-signed data to fill in various fields in subkeys.
*
* KEYBLOCK is the whole keyblock. SUBNODE is the subkey to fill in.
*
* Sets the following fields on the subkey:
*
* main_keyid
* flags.valid if the subkey has a valid self-sig binding
* flags.revoked
* flags.backsig
* pubkey_usage
* has_expired
* expired_date
*
* On this subkey's most recent valid self-signed packet, the
* following field is set:
*
* flags.chosen_selfsig
*/
static void
merge_selfsigs_subkey (ctrl_t ctrl, kbnode_t keyblock, kbnode_t subnode)
{
PKT_public_key *mainpk = NULL, *subpk = NULL;
PKT_signature *sig;
KBNODE k;
u32 mainkid[2];
u32 sigdate = 0;
KBNODE signode;
u32 curtime = make_timestamp ();
unsigned int key_usage = 0;
u32 keytimestamp = 0;
u32 key_expire = 0;
const byte *p;
if (subnode->pkt->pkttype != PKT_PUBLIC_SUBKEY)
BUG ();
mainpk = keyblock->pkt->pkt.public_key;
if (mainpk->version < 4)
return;/* (actually this should never happen) */
keyid_from_pk (mainpk, mainkid);
subpk = subnode->pkt->pkt.public_key;
keytimestamp = subpk->timestamp;
subpk->flags.valid = 0;
subpk->flags.exact = 0;
subpk->main_keyid[0] = mainpk->main_keyid[0];
subpk->main_keyid[1] = mainpk->main_keyid[1];
/* Find the latest key binding self-signature. */
signode = NULL;
sigdate = 0; /* Helper to find the latest signature. */
for (k = subnode->next; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY;
k = k->next)
{
if (k->pkt->pkttype == PKT_SIGNATURE)
{
sig = k->pkt->pkt.signature;
if (sig->keyid[0] == mainkid[0] && sig->keyid[1] == mainkid[1])
{
if (check_key_signature (ctrl, keyblock, k, NULL))
; /* Signature did not verify. */
else if (IS_SUBKEY_REV (sig))
{
/* Note that this means that the date on a
* revocation sig does not matter - even if the
* binding sig is dated after the revocation sig,
* the subkey is still marked as revoked. This
* seems ok, as it is just as easy to make new
* subkeys rather than re-sign old ones as the
* problem is in the distribution. Plus, PGP (7)
* does this the same way. */
subpk->flags.revoked = 1;
sig_to_revoke_info (sig, &subpk->revoked);
/* Although we could stop now, we continue to
* figure out other information like the old expiration
* time. */
}
else if (IS_SUBKEY_SIG (sig) && sig->timestamp >= sigdate)
{
if (sig->flags.expired)
; /* Signature has expired - ignore it. */
else
{
sigdate = sig->timestamp;
signode = k;
signode->pkt->pkt.signature->flags.chosen_selfsig = 0;
}
}
}
}
}
/* No valid key binding. */
if (!signode)
return;
sig = signode->pkt->pkt.signature;
sig->flags.chosen_selfsig = 1; /* So we know which selfsig we chose later. */
key_usage = parse_key_usage (sig);
if (!key_usage)
{
/* No key flags at all: get it from the algo. */
key_usage = openpgp_pk_algo_usage (subpk->pubkey_algo);
}
else
{
/* Check that the usage matches the usage as given by the algo. */
int x = openpgp_pk_algo_usage (subpk->pubkey_algo);
if (x) /* Mask it down to the actual allowed usage. */
key_usage &= x;
}
subpk->pubkey_usage = key_usage;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (p && buf32_to_u32 (p))
key_expire = keytimestamp + buf32_to_u32 (p);
else
key_expire = 0;
subpk->has_expired = key_expire >= curtime ? 0 : key_expire;
subpk->expiredate = key_expire;
/* Algo doesn't exist. */
if (openpgp_pk_test_algo (subpk->pubkey_algo))
return;
subpk->flags.valid = 1;
/* Find the most recent 0x19 embedded signature on our self-sig. */
if (!subpk->flags.backsig)
{
int seq = 0;
size_t n;
PKT_signature *backsig = NULL;
sigdate = 0;
/* We do this while() since there may be other embedded
* signatures in the future. We only want 0x19 here. */
while ((p = enum_sig_subpkt (sig, 1, SIGSUBPKT_SIGNATURE,
&n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)
|| (p[0] == 5 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
seq = 0;
/* It is safe to have this in the unhashed area since the 0x19
* is located on the selfsig for convenience, not security. */
while ((p = enum_sig_subpkt (sig, 0, SIGSUBPKT_SIGNATURE,
&n, &seq, NULL)))
if (n > 3
&& ((p[0] == 3 && p[2] == 0x19) || (p[0] == 4 && p[1] == 0x19)
|| (p[0] == 5 && p[1] == 0x19)))
{
PKT_signature *tempsig = buf_to_sig (p, n);
if (tempsig)
{
if (tempsig->timestamp > sigdate)
{
if (backsig)
free_seckey_enc (backsig);
backsig = tempsig;
sigdate = backsig->timestamp;
}
else
free_seckey_enc (tempsig);
}
}
if (backsig)
{
/* At this point, backsig contains the most recent 0x19 sig.
* Let's see if it is good. */
/* 2==valid, 1==invalid, 0==didn't check */
if (check_backsig (mainpk, subpk, backsig) == 0)
subpk->flags.backsig = 2;
else
subpk->flags.backsig = 1;
free_seckey_enc (backsig);
}
}
}
/* Merge information from the self-signatures with the public key,
* subkeys and user ids to make using them more easy.
*
* See documentation for merge_selfsigs_main, merge_selfsigs_subkey
* and fixup_uidnode for exactly which fields are updated. */
static void
merge_selfsigs (ctrl_t ctrl, kbnode_t keyblock)
{
KBNODE k;
int revoked;
struct revoke_info rinfo;
PKT_public_key *main_pk;
prefitem_t *prefs;
unsigned int mdc_feature;
unsigned int aead_feature;
if (keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
log_error ("expected public key but found secret key "
"- must stop\n");
/* We better exit here because a public key is expected at
* other places too. FIXME: Figure this out earlier and
* don't get to here at all */
g10_exit (1);
}
BUG ();
}
merge_selfsigs_main (ctrl, keyblock, &revoked, &rinfo);
/* Now merge in the data from each of the subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
merge_selfsigs_subkey (ctrl, keyblock, k);
}
}
main_pk = keyblock->pkt->pkt.public_key;
if (revoked || main_pk->has_expired || !main_pk->flags.valid)
{
/* If the primary key is revoked, expired, or invalid we
* better set the appropriate flags on that key and all
* subkeys. */
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (!main_pk->flags.valid)
pk->flags.valid = 0;
if (revoked && !pk->flags.revoked)
{
pk->flags.revoked = revoked;
memcpy (&pk->revoked, &rinfo, sizeof (rinfo));
}
if (main_pk->has_expired)
{
pk->has_expired = main_pk->has_expired;
if (!pk->expiredate || pk->expiredate > main_pk->expiredate)
pk->expiredate = main_pk->expiredate;
}
}
}
return;
}
/* Set the preference list of all keys to those of the primary real
* user ID. Note: we use these preferences when we don't know by
* which user ID the key has been selected.
* fixme: we should keep atoms of commonly used preferences or
* use reference counting to optimize the preference lists storage.
* FIXME: it might be better to use the intersection of
* all preferences.
* Do a similar thing for the MDC feature flag. */
prefs = NULL;
mdc_feature = aead_feature = 0;
for (k = keyblock; k && k->pkt->pkttype != PKT_PUBLIC_SUBKEY; k = k->next)
{
if (k->pkt->pkttype == PKT_USER_ID
&& !k->pkt->pkt.user_id->attrib_data
&& k->pkt->pkt.user_id->flags.primary)
{
prefs = k->pkt->pkt.user_id->prefs;
mdc_feature = k->pkt->pkt.user_id->flags.mdc;
aead_feature = k->pkt->pkt.user_id->flags.aead;
break;
}
}
for (k = keyblock; k; k = k->next)
{
if (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
PKT_public_key *pk = k->pkt->pkt.public_key;
if (pk->prefs)
xfree (pk->prefs);
pk->prefs = copy_prefs (prefs);
pk->flags.mdc = mdc_feature;
pk->flags.aead = aead_feature;
}
}
}
/* See whether the key satisfies any additional requirements specified
* in CTX. If so, return the node of an appropriate key or subkey.
* Otherwise, return NULL if there was no appropriate key.
*
* Note that we do not return a reference, i.e. the result must not be
* freed using 'release_kbnode'.
*
* In case the primary key is not required, select a suitable subkey.
* We need the primary key if PUBKEY_USAGE_CERT is set in REQ_USAGE or
* we are in PGP7 mode and PUBKEY_USAGE_SIG is set in
* REQ_USAGE.
*
* If any of PUBKEY_USAGE_SIG, PUBKEY_USAGE_ENC and PUBKEY_USAGE_CERT
* are set in REQ_USAGE, we filter by the key's function. Concretely,
* if PUBKEY_USAGE_SIG and PUBKEY_USAGE_CERT are set, then we only
* return a key if it is (at least) either a signing or a
* certification key.
*
* If REQ_USAGE is set, then we reject any keys that are not good
* (i.e., valid, not revoked, not expired, etc.). This allows the
* getkey functions to be used for plain key listings.
*
* Sets the matched key's user id field (pk->user_id) to the user id
* that matched the low-level search criteria or NULL.
*
* If R_FLAGS is not NULL set certain flags for more detailed error
* reporting. Used flags are:
*
* - LOOKUP_ALL_SUBKEYS_EXPIRED :: All Subkeys are expired or have
* been revoked.
* - LOOKUP_NOT_SELECTED :: No suitable key found
*
* This function needs to handle several different cases:
*
* 1. No requested usage and no primary key requested
* Examples for this case are that we have a keyID to be used
* for decryption or verification.
* 2. No usage but primary key requested
* This is the case for all functions which work on an
* entire keyblock, e.g. for editing or listing
* 3. Usage and primary key requested
* FIXME
* 4. Usage but no primary key requested
* FIXME
*
*/
static kbnode_t
finish_lookup (kbnode_t keyblock, unsigned int req_usage, int want_exact,
int want_secret, unsigned int *r_flags)
{
kbnode_t k;
/* If WANT_EXACT is set, the key or subkey that actually matched the
low-level search criteria. */
kbnode_t foundk = NULL;
/* The user id (if any) that matched the low-level search criteria. */
PKT_user_id *foundu = NULL;
u32 latest_date;
kbnode_t latest_key;
PKT_public_key *pk;
int req_prim;
u32 curtime = make_timestamp ();
if (r_flags)
*r_flags = 0;
#define USAGE_MASK (PUBKEY_USAGE_SIG|PUBKEY_USAGE_ENC|PUBKEY_USAGE_CERT)
req_usage &= USAGE_MASK;
/* Request the primary if we're certifying another key, and also if
* signing data while --pgp7 is on since pgp 7 do
* not understand signatures made by a signing subkey. PGP 8 does. */
req_prim = ((req_usage & PUBKEY_USAGE_CERT)
|| (PGP7 && (req_usage & PUBKEY_USAGE_SIG)));
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* For an exact match mark the primary or subkey that matched the
* low-level search criteria. Use this loop also to sort our keys
* found using an ADSK fingerprint. */
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 1) && (k->pkt->pkttype == PKT_PUBLIC_KEY
|| k->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
if (want_exact)
{
if (DBG_LOOKUP)
log_debug ("finish_lookup: exact search requested and found\n");
foundk = k;
pk = k->pkt->pkt.public_key;
pk->flags.exact = 1;
break;
}
else if ((k->pkt->pkt.public_key->pubkey_usage == PUBKEY_USAGE_RENC))
{
if (DBG_LOOKUP)
log_debug ("finish_lookup: found via ADSK - not selected\n");
if (r_flags)
*r_flags |= LOOKUP_NOT_SELECTED;
return NULL; /* Not found. */
}
}
}
/* Get the user id that matched that low-level search criteria. */
for (k = keyblock; k; k = k->next)
{
if ((k->flag & 2))
{
log_assert (k->pkt->pkttype == PKT_USER_ID);
foundu = k->pkt->pkt.user_id;
break;
}
}
if (DBG_LOOKUP)
log_debug ("finish_lookup: checking key %08lX (%s)(req_usage=%x)\n",
(ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL),
foundk ? "one" : "all", req_usage);
if (!req_usage)
{
latest_key = foundk ? foundk : keyblock;
goto found;
}
latest_date = 0;
latest_key = NULL;
/* Set LATEST_KEY to the latest (the one with the most recent
* timestamp) good (valid, not revoked, not expired, etc.) subkey.
*
* Don't bother if we are only looking for a primary key or we need
* an exact match and the exact match is not a subkey. */
if (req_prim || (foundk && foundk->pkt->pkttype != PKT_PUBLIC_SUBKEY))
;
else
{
kbnode_t nextk;
int n_subkeys = 0;
int n_revoked_or_expired = 0;
int last_secret_key_avail = 0;
/* Either start a loop or check just this one subkey. */
for (k = foundk ? foundk : keyblock; k; k = nextk)
{
if (foundk)
{
/* If FOUNDK is not NULL, then only consider that exact
key, i.e., don't iterate. */
nextk = NULL;
}
else
nextk = k->next;
if (k->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = k->pkt->pkt.public_key;
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX\n",
(ulong) keyid_from_pk (pk, NULL));
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
continue;
}
if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tusage does not match: want=%x have=%x\n",
req_usage, pk->pubkey_usage);
continue;
}
n_subkeys++;
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked\n");
n_revoked_or_expired++;
continue;
}
if (pk->has_expired && !opt.ignore_expiration)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
n_revoked_or_expired++;
continue;
}
if (pk->timestamp > curtime && !opt.ignore_valid_from)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not yet valid\n");
continue;
}
if (opt.flags.require_pqc_encryption
&& (req_usage & PUBKEY_USAGE_ENC)
&& pk->pubkey_algo != PUBKEY_ALGO_KYBER)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey is not quantum-resistant\n");
continue;
}
if (want_secret)
{
int secret_key_avail = agent_probe_secret_key (NULL, pk);
if (!secret_key_avail)
{
if (DBG_LOOKUP)
log_debug ("\tno secret key\n");
continue;
}
if (secret_key_avail < last_secret_key_avail)
{
if (DBG_LOOKUP)
log_debug ("\tskipping secret key with lower avail\n");
continue;
}
if (secret_key_avail > last_secret_key_avail)
{
/* Use this key. */
last_secret_key_avail = secret_key_avail;
latest_date = 0;
}
}
if (DBG_LOOKUP)
log_debug ("\tsubkey might be fine\n");
/* In case a key has a timestamp of 0 set, we make sure
that it is used. A better change would be to compare
">=" but that might also change the selected keys and
is as such a more intrusive change. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
latest_key = k;
}
}
if (n_subkeys == n_revoked_or_expired && r_flags)
*r_flags |= LOOKUP_ALL_SUBKEYS_EXPIRED;
}
/* Check if the primary key is ok (valid, not revoke, not expire,
* matches requested usage) if:
*
* - we didn't find an appropriate subkey and we're not doing an
* exact search,
*
* - we're doing an exact match and the exact match was the
* primary key, or,
*
* - we're just considering the primary key. */
if ((!latest_key && !want_exact) || foundk == keyblock || req_prim)
{
if (DBG_LOOKUP && !foundk && !req_prim)
log_debug ("\tno suitable subkeys found - trying primary\n");
pk = keyblock->pkt->pkt.public_key;
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key not valid\n");
}
else if (!((pk->pubkey_usage & USAGE_MASK) & req_usage))
{
if (DBG_LOOKUP)
log_debug ("\tprimary key usage does not match: "
"want=%x have=%x\n", req_usage, pk->pubkey_usage);
}
else if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has been revoked\n");
}
else if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key has expired\n");
}
else if (opt.flags.require_pqc_encryption
&& (req_usage & PUBKEY_USAGE_ENC)
&& pk->pubkey_algo != PUBKEY_ALGO_KYBER)
{
if (DBG_LOOKUP)
log_debug ("\tprimary key is not quantum-resistant\n");
}
else /* Okay. */
{
if (DBG_LOOKUP)
log_debug ("\tprimary key may be used\n");
latest_key = keyblock;
}
}
if (!latest_key)
{
if (DBG_LOOKUP)
log_debug ("\tno suitable key found - giving up\n");
if (r_flags)
*r_flags |= LOOKUP_NOT_SELECTED;
return NULL; /* Not found. */
}
found:
if (DBG_LOOKUP)
log_debug ("\tusing key %08lX\n",
(ulong) keyid_from_pk (latest_key->pkt->pkt.public_key, NULL));
if (latest_key)
{
pk = latest_key->pkt->pkt.public_key;
free_user_id (pk->user_id);
pk->user_id = scopy_user_id (foundu);
}
if (latest_key != keyblock && opt.verbose)
{
char *tempkeystr =
xstrdup (keystr_from_pk (latest_key->pkt->pkt.public_key));
log_info (_("using subkey %s instead of primary key %s\n"),
tempkeystr, keystr_from_pk (keyblock->pkt->pkt.public_key));
xfree (tempkeystr);
}
cache_put_keyblock (keyblock);
return latest_key ? latest_key : keyblock; /* Found. */
}
/* Print a KEY_CONSIDERED status line. */
static void
print_status_key_considered (kbnode_t keyblock, unsigned int flags)
{
char hexfpr[2*MAX_FINGERPRINT_LEN + 1];
kbnode_t node;
char flagbuf[20];
if (!is_status_enabled ())
return;
for (node=keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
break;
if (!node)
{
log_error ("%s: keyblock w/o primary key\n", __func__);
return;
}
hexfingerprint (node->pkt->pkt.public_key, hexfpr, sizeof hexfpr);
snprintf (flagbuf, sizeof flagbuf, " %u", flags);
write_status_strings (STATUS_KEY_CONSIDERED, hexfpr, flagbuf, NULL);
}
/* A high-level function to lookup keys.
*
* This function builds on top of the low-level keydb API. It first
* searches the database using the description stored in CTX->ITEMS,
* then it filters the results using CTX and, finally, if WANT_SECRET
* is set, it ignores any keys for which no secret key is available.
*
* Unlike the low-level search functions, this function also merges
* all of the self-signed data into the keys, subkeys and user id
* packets (see the merge_selfsigs for details).
*
* On success the key's keyblock is stored at *RET_KEYBLOCK, and the
* specific subkey is stored at *RET_FOUND_KEY. Note that we do not
* return a reference in *RET_FOUND_KEY, i.e. the result must not be
* freed using 'release_kbnode', and it is only valid until
* *RET_KEYBLOCK is deallocated. Therefore, if RET_FOUND_KEY is not
* NULL, then RET_KEYBLOCK must not be NULL. */
static int
lookup (ctrl_t ctrl, getkey_ctx_t ctx, int want_secret,
kbnode_t *ret_keyblock, kbnode_t *ret_found_key)
{
int rc;
int no_suitable_key = 0;
KBNODE keyblock = NULL;
KBNODE found_key = NULL;
unsigned int infoflags;
log_assert (ret_found_key == NULL || ret_keyblock != NULL);
if (ret_keyblock)
*ret_keyblock = NULL;
for (;;)
{
rc = keydb_search (ctx->kr_handle, ctx->items, ctx->nitems, NULL);
if (rc)
break;
/* If we are iterating over the entire database, then we need to
* change from KEYDB_SEARCH_MODE_FIRST, which does an implicit
* reset, to KEYDB_SEARCH_MODE_NEXT, which gets the next record. */
if (ctx->nitems && ctx->items->mode == KEYDB_SEARCH_MODE_FIRST)
ctx->items->mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_keyblock (ctx->kr_handle, &keyblock);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto skip;
}
if (want_secret)
{
rc = agent_probe_any_secret_key (ctrl, keyblock);
if (gpg_err_code(rc) == GPG_ERR_NO_SECKEY)
goto skip; /* No secret key available. */
if (rc)
goto found; /* Unexpected error. */
}
/* Warning: node flag bits 0 and 1 should be preserved by
* merge_selfsigs. */
merge_selfsigs (ctrl, keyblock);
found_key = finish_lookup (keyblock, ctx->req_usage, ctx->exact,
want_secret, &infoflags);
print_status_key_considered (keyblock, infoflags);
if (found_key)
{
no_suitable_key = 0;
goto found;
}
else
{
no_suitable_key = 1;
}
skip:
/* Release resources and continue search. */
release_kbnode (keyblock);
keyblock = NULL;
/* The keyblock cache ignores the current "file position".
* Thus, if we request the next result and the cache matches
* (and it will since it is what we just looked for), we'll get
* the same entry back! We can avoid this infinite loop by
* disabling the cache. */
keydb_disable_caching (ctx->kr_handle);
}
found:
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
if (!rc)
{
if (ret_keyblock)
{
*ret_keyblock = keyblock; /* Return the keyblock. */
keyblock = NULL;
}
}
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND && no_suitable_key)
rc = want_secret? GPG_ERR_UNUSABLE_SECKEY : GPG_ERR_UNUSABLE_PUBKEY;
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = want_secret? GPG_ERR_NO_SECKEY : GPG_ERR_NO_PUBKEY;
release_kbnode (keyblock);
if (ret_found_key)
{
if (! rc)
*ret_found_key = found_key;
else
*ret_found_key = NULL;
}
return rc;
}
/* If a default key has been specified, return that key. If a card
* based key is also available as indicated by FPR_CARD not being
* NULL, return that key if suitable. */
gpg_error_t
get_seckey_default_or_card (ctrl_t ctrl, PKT_public_key *pk,
const byte *fpr_card, size_t fpr_len)
{
gpg_error_t err;
strlist_t namelist = NULL;
const char *def_secret_key;
def_secret_key = parse_def_secret_key (ctrl);
if (def_secret_key)
add_to_strlist (&namelist, def_secret_key);
else if (fpr_card)
{
err = get_pubkey_byfprint (ctrl, pk, NULL, fpr_card, fpr_len);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY)
{
if (opt.debug)
log_debug ("using LDAP to find public key for current card\n");
err = keyserver_import_fprint (ctrl, fpr_card, fpr_len,
opt.keyserver,
KEYSERVER_IMPORT_FLAG_LDAP);
if (!err)
err = get_pubkey_byfprint (ctrl, pk, NULL, fpr_card, fpr_len);
else if (gpg_err_code (err) == GPG_ERR_NO_DATA
|| gpg_err_code (err) == GPG_ERR_NO_KEYSERVER)
{
/* Dirmngr returns NO DATA is the selected keyserver
* does not have the requested key. It returns NO
* KEYSERVER if no LDAP keyservers are configured. */
err = gpg_error (GPG_ERR_NO_PUBKEY);
}
}
/* The key on card can be not suitable for requested usage. */
if (gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
fpr_card = NULL; /* Fallthrough as no card. */
else
return err; /* Success or other error. */
}
if (!fpr_card || (def_secret_key && *def_secret_key
&& def_secret_key[strlen (def_secret_key)-1] == '!'))
{
err = key_byname (ctrl, NULL, namelist, pk, 1, 0, NULL, NULL);
}
else
{ /* Default key is specified and card key is also available. */
kbnode_t k, keyblock = NULL;
err = key_byname (ctrl, NULL, namelist, pk, 1, 0, &keyblock, NULL);
if (err)
goto leave;
for (k = keyblock; k; k = k->next)
{
PKT_public_key *pk_candidate;
char fpr[MAX_FINGERPRINT_LEN];
if (k->pkt->pkttype != PKT_PUBLIC_KEY
&&k->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk_candidate = k->pkt->pkt.public_key;
if (!pk_candidate->flags.valid)
continue;
if (!((pk_candidate->pubkey_usage & USAGE_MASK) & pk->req_usage))
continue;
fingerprint_from_pk (pk_candidate, fpr, NULL);
if (!memcmp (fpr_card, fpr, fpr_len))
{
release_public_key_parts (pk);
copy_public_key (pk, pk_candidate);
break;
}
}
release_kbnode (keyblock);
}
leave:
free_strlist (namelist);
return err;
}
/*********************************************
*********** User ID printing helpers *******
*********************************************/
/* Return a string with a printable representation of the user_id.
* this string must be freed by xfree. If R_NOUID is not NULL it is
* set to true if a user id was not found; otherwise to false. */
static char *
get_user_id_string (ctrl_t ctrl, u32 * keyid, int mode)
{
char *name;
unsigned int namelen;
char *p;
log_assert (mode != 2);
name = cache_get_uid_bykid (keyid, &namelen);
if (!name)
{
/* Get it so that the cache will be filled. */
if (!get_pubkey (ctrl, NULL, keyid))
name = cache_get_uid_bykid (keyid, &namelen);
}
if (name)
{
if (mode)
p = xasprintf ("%08lX%08lX %.*s",
(ulong) keyid[0], (ulong) keyid[1], namelen, name);
else
p = xasprintf ("%s %.*s", keystr (keyid), namelen, name);
xfree (name);
}
else
{
if (mode)
p = xasprintf ("%08lX%08lX [?]", (ulong) keyid[0], (ulong) keyid[1]);
else
p = xasprintf ("%s [?]", keystr (keyid));
}
return p;
}
char *
get_user_id_string_native (ctrl_t ctrl, u32 * keyid)
{
char *p = get_user_id_string (ctrl, keyid, 0);
char *p2 = utf8_to_native (p, strlen (p), 0);
xfree (p);
return p2;
}
char *
get_long_user_id_string (ctrl_t ctrl, u32 * keyid)
{
return get_user_id_string (ctrl, keyid, 1);
}
/* Please try to use get_user_byfpr instead of this one. */
char *
get_user_id (ctrl_t ctrl, u32 *keyid, size_t *rn, int *r_nouid)
{
char *name;
unsigned int namelen;
if (r_nouid)
*r_nouid = 0;
name = cache_get_uid_bykid (keyid, &namelen);
if (!name)
{
/* Get it so that the cache will be filled. */
if (!get_pubkey (ctrl, NULL, keyid))
name = cache_get_uid_bykid (keyid, &namelen);
}
if (!name)
{
name = xstrdup (user_id_not_found_utf8 ());
namelen = strlen (name);
if (r_nouid)
*r_nouid = 1;
}
if (rn && name)
*rn = namelen;
return name;
}
/* Please try to use get_user_id_byfpr_native instead of this one. */
char *
get_user_id_native (ctrl_t ctrl, u32 *keyid)
{
size_t rn;
char *p = get_user_id (ctrl, keyid, &rn, NULL);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the user id for a key designated by its fingerprint, FPR,
which must be MAX_FINGERPRINT_LEN bytes in size. Note: the
returned string, which must be freed using xfree, may not be NUL
terminated. To determine the length of the string, you must use
*RN. */
static char *
get_user_id_byfpr (ctrl_t ctrl, const byte *fpr, size_t fprlen, size_t *rn)
{
char *name;
name = cache_get_uid_byfpr (fpr, fprlen, rn);
if (!name)
{
/* Get it so that the cache will be filled. */
if (!get_pubkey_byfprint (ctrl, NULL, NULL, fpr, fprlen))
name = cache_get_uid_byfpr (fpr, fprlen, rn);
}
if (!name)
{
name = xstrdup (user_id_not_found_utf8 ());
*rn = strlen (name);
}
return name;
}
/* Like get_user_id_byfpr, but convert the string to the native
encoding. The returned string needs to be freed. Unlike
get_user_id_byfpr, the returned string is NUL terminated. */
char *
get_user_id_byfpr_native (ctrl_t ctrl, const byte *fpr, size_t fprlen)
{
size_t rn;
char *p = get_user_id_byfpr (ctrl, fpr, fprlen, &rn);
char *p2 = utf8_to_native (p, rn, 0);
xfree (p);
return p2;
}
/* Return the database handle used by this context. The context still
owns the handle. */
KEYDB_HANDLE
get_ctx_handle (GETKEY_CTX ctx)
{
return ctx->kr_handle;
}
static void
free_akl (struct akl *akl)
{
if (! akl)
return;
if (akl->spec)
free_keyserver_spec (akl->spec);
xfree (akl);
}
void
release_akl (void)
{
while (opt.auto_key_locate)
{
struct akl *akl2 = opt.auto_key_locate;
opt.auto_key_locate = opt.auto_key_locate->next;
free_akl (akl2);
}
}
/* Returns true if the AKL is empty or has only the local method
* active. */
int
akl_empty_or_only_local (void)
{
struct akl *akl;
int any = 0;
for (akl = opt.auto_key_locate; akl; akl = akl->next)
if (akl->type != AKL_NODEFAULT && akl->type != AKL_LOCAL)
{
any = 1;
break;
}
return !any;
}
/* Returns false on error. */
int
parse_auto_key_locate (const char *options_arg)
{
char *tok;
char *options, *options_buf;
options = options_buf = xstrdup (options_arg);
while ((tok = optsep (&options)))
{
struct akl *akl, *check, *last = NULL;
int dupe = 0;
if (tok[0] == '\0')
continue;
akl = xmalloc_clear (sizeof (*akl));
if (ascii_strcasecmp (tok, "clear") == 0)
{
xfree (akl);
free_akl (opt.auto_key_locate);
opt.auto_key_locate = NULL;
continue;
}
else if (ascii_strcasecmp (tok, "nodefault") == 0)
akl->type = AKL_NODEFAULT;
else if (ascii_strcasecmp (tok, "local") == 0)
akl->type = AKL_LOCAL;
else if (ascii_strcasecmp (tok, "ldap") == 0)
akl->type = AKL_LDAP;
else if (ascii_strcasecmp (tok, "keyserver") == 0)
akl->type = AKL_KEYSERVER;
else if (ascii_strcasecmp (tok, "cert") == 0)
akl->type = AKL_CERT;
else if (ascii_strcasecmp (tok, "pka") == 0)
akl->type = AKL_PKA;
else if (ascii_strcasecmp (tok, "dane") == 0)
akl->type = AKL_DANE;
else if (ascii_strcasecmp (tok, "wkd") == 0)
akl->type = AKL_WKD;
else if (ascii_strcasecmp (tok, "ntds") == 0)
akl->type = AKL_NTDS;
else if ((akl->spec = parse_keyserver_uri (tok, 1)))
akl->type = AKL_SPEC;
else
{
free_akl (akl);
xfree (options_buf);
return 0;
}
/* We must maintain the order the user gave us */
for (check = opt.auto_key_locate; check;
last = check, check = check->next)
{
/* Check for duplicates */
if (check->type == akl->type
&& (akl->type != AKL_SPEC
|| (akl->type == AKL_SPEC
&& strcmp (check->spec->uri, akl->spec->uri) == 0)))
{
dupe = 1;
free_akl (akl);
break;
}
}
if (!dupe)
{
if (last)
last->next = akl;
else
opt.auto_key_locate = akl;
}
}
xfree (options_buf);
return 1;
}
/* The list of key origins. */
static struct {
const char *name;
int origin;
} key_origin_list[] =
{
{ "self", KEYORG_SELF },
{ "file", KEYORG_FILE },
{ "url", KEYORG_URL },
{ "wkd", KEYORG_WKD },
{ "dane", KEYORG_DANE },
{ "ks-pref", KEYORG_KS_PREF },
{ "ks", KEYORG_KS },
{ "unknown", KEYORG_UNKNOWN }
};
/* Parse the argument for --key-origin. Return false on error. */
int
parse_key_origin (char *string)
{
int i;
char *comma;
comma = strchr (string, ',');
if (comma)
*comma = 0;
if (!ascii_strcasecmp (string, "help"))
{
log_info (_("valid values for option '%s':\n"), "--key-origin");
for (i=0; i < DIM (key_origin_list); i++)
log_info (" %s\n", key_origin_list[i].name);
g10_exit (1);
}
for (i=0; i < DIM (key_origin_list); i++)
if (!ascii_strcasecmp (string, key_origin_list[i].name))
{
opt.key_origin = key_origin_list[i].origin;
xfree (opt.key_origin_url);
opt.key_origin_url = NULL;
if (comma && comma[1])
{
opt.key_origin_url = xstrdup (comma+1);
trim_spaces (opt.key_origin_url);
}
return 1;
}
if (comma)
*comma = ',';
return 0;
}
/* Return a string or "?" for the key ORIGIN. */
const char *
key_origin_string (int origin)
{
int i;
for (i=0; i < DIM (key_origin_list); i++)
if (key_origin_list[i].origin == origin)
return key_origin_list[i].name;
return "?";
}
/* Returns true if a secret key is available for the public key with
key id KEYID; returns false if not. This function ignores legacy
keys. Note: this is just a fast check and does not tell us whether
the secret key is valid; this check merely indicates whether there
is some secret key with the specified key id. */
int
have_secret_key_with_kid (ctrl_t ctrl, u32 *keyid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
kbnode_t keyblock;
kbnode_t node;
int result = 0;
kdbhd = keydb_new (ctrl);
if (!kdbhd)
return 0;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = keyid[0];
desc.u.kid[1] = keyid[1];
while (!result)
{
err = keydb_search (kdbhd, &desc, 1, NULL);
if (err)
break;
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
break;
}
for (node = keyblock; node; node = node->next)
{
/* Bit 0 of the flags is set if the search found the key
using that key or subkey. Note: a search will only ever
match a single key or subkey. */
if ((node->flag & 1))
{
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
if (agent_probe_secret_key (NULL, node->pkt->pkt.public_key))
result = 1; /* Secret key available. */
else
result = 0;
break;
}
}
release_kbnode (keyblock);
}
keydb_release (kdbhd);
return result;
}
diff --git a/g10/gpg.c b/g10/gpg.c
index 82745fa18..c2165dafc 100644
--- a/g10/gpg.c
+++ b/g10/gpg.c
@@ -1,5913 +1,5913 @@
/* gpg.c - The GnuPG OpenPGP tool
* Copyright (C) 1998-2020 Free Software Foundation, Inc.
* Copyright (C) 1997-2019 Werner Koch
* Copyright (C) 2015-2022 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_STAT
#include <sys/stat.h> /* for stat() */
#endif
#include <fcntl.h>
#ifdef HAVE_W32_SYSTEM
# ifdef HAVE_WINSOCK2_H
# include <winsock2.h>
# endif
# include <windows.h>
#endif
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpg.h"
#include <assuan.h>
#include "../common/iobuf.h"
#include "../common/util.h"
#include "packet.h"
#include "../common/membuf.h"
#include "main.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "keyserver-internal.h"
#include "exec.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "call-dirmngr.h"
#include "tofu.h"
#include "objcache.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
#include "../common/zb32.h"
#include "../common/shareddefs.h"
#include "../common/compliance.h"
#include "../common/comopt.h"
#include "../kbx/keybox.h"
#if defined(HAVE_DOSISH_SYSTEM) || defined(__CYGWIN__)
#define MY_O_BINARY O_BINARY
#ifndef S_IRGRP
# define S_IRGRP 0
# define S_IWGRP 0
#endif
#else
#define MY_O_BINARY 0
#endif
enum cmd_and_opt_values
{
aNull = 0,
oArmor = 'a',
aDetachedSign = 'b',
aSym = 'c',
aDecrypt = 'd',
aEncr = 'e',
oRecipientFile = 'f',
oHiddenRecipientFile = 'F',
oInteractive = 'i',
aListKeys = 'k',
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oRecipient = 'r',
oHiddenRecipient = 'R',
aSign = 's',
oTextmodeShort= 't',
oLocalUser = 'u',
oVerbose = 'v',
oCompress = 'z',
oSetNotation = 'N',
aListSecretKeys = 'K',
oBatch = 500,
oMaxOutput,
oInputSizeHint,
oChunkSize,
oSigNotation,
oCertNotation,
oShowNotation,
oNoShowNotation,
oKnownNotation,
aEncrFiles,
aEncrSym,
aDecryptFiles,
aClearsign,
aStore,
aQuickKeygen,
aFullKeygen,
aKeygen,
aSignEncr,
aSignEncrSym,
aSignSym,
aSignKey,
aLSignKey,
aQuickSignKey,
aQuickLSignKey,
aQuickRevSig,
aQuickAddUid,
aQuickAddKey,
aQuickAddADSK,
aQuickRevUid,
aQuickSetExpire,
aQuickSetPrimaryUid,
aQuickUpdatePref,
aQuickSetOwnertrust,
aListConfig,
aListGcryptConfig,
aGPGConfList,
aGPGConfTest,
aListPackets,
aEditKey,
aDeleteKeys,
aDeleteSecretKeys,
aDeleteSecretAndPublicKeys,
aImport,
aFastImport,
aVerify,
aVerifyFiles,
aListSigs,
aSendKeys,
aRecvKeys,
aLocateKeys,
aLocateExtKeys,
aSearchKeys,
aRefreshKeys,
aFetchKeys,
aShowKeys,
aExport,
aExportSecret,
aExportSecretSub,
aExportSshKey,
aExportSecretSshKey,
aCheckKeys,
aGenRevoke,
aDesigRevoke,
aPrimegen,
aPrintMD,
aPrintMDs,
aCheckTrustDB,
aUpdateTrustDB,
aFixTrustDB,
aListTrustDB,
aListTrustPath,
aExportOwnerTrust,
aImportOwnerTrust,
aDeArmor,
aEnArmor,
aGenRandom,
aRebuildKeydbCaches,
aCardStatus,
aCardEdit,
aChangePIN,
aPasswd,
aServer,
aTOFUPolicy,
oMimemode,
oTextmode,
oNoTextmode,
oExpert,
oNoExpert,
oDefSigExpire,
oAskSigExpire,
oNoAskSigExpire,
oDefCertExpire,
oAskCertExpire,
oNoAskCertExpire,
oDefCertLevel,
oMinCertLevel,
oAskCertLevel,
oNoAskCertLevel,
oFingerprint,
oWithV5Fingerprint,
oWithFingerprint,
oWithSubkeyFingerprint,
oWithoutSubkeyFingerprint,
oWithICAOSpelling,
oWithKeygrip,
oWithKeyScreening,
oWithSecret,
oWithWKDHash,
oWithColons,
oWithKeyData,
oWithKeyOrigin,
oWithTofuInfo,
oWithSigList,
oWithSigCheck,
oAnswerYes,
oAnswerNo,
oKeyring,
oPrimaryKeyring,
oSecretKeyring,
oShowKeyring,
oDefaultKey,
oDefRecipient,
oDefRecipientSelf,
oNoDefRecipient,
oTrySecretKey,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugIOLBF,
oDebugSetIobufSize,
oDebugAllowLargeChunks,
oDebugIgnoreExpiration,
oStatusFD,
oStatusFile,
oAttributeFD,
oAttributeFile,
oEmitVersion,
oNoEmitVersion,
oCompletesNeeded,
oMarginalsNeeded,
oMaxCertDepth,
oLoadExtension,
oCompliance,
oGnuPG,
oRFC2440,
oRFC4880,
oOpenPGP,
oPGP7,
oPGP8,
oDE_VS,
oMinRSALength,
oRFC2440Text,
oNoRFC2440Text,
oCipherAlgo,
oDigestAlgo,
oCertDigestAlgo,
oNoCompress,
oCompressAlgo,
oCompressLevel,
oBZ2CompressLevel,
oBZ2DecompressLowmem,
oPassphrase,
oPassphraseFD,
oPassphraseFile,
oPassphraseRepeat,
oPinentryMode,
oCommandFD,
oCommandFile,
oQuickRandom,
oNoVerbose,
oTrustDBName,
oNoSecmemWarn,
oRequireSecmem,
oNoRequireSecmem,
oNoPermissionWarn,
oNoArmor,
oNoDefKeyring,
oNoKeyring,
oNoGreeting,
oNoTTY,
oNoOptions,
oNoBatch,
oHomedir,
oSkipVerify,
oSkipHiddenRecipients,
oNoSkipHiddenRecipients,
oAlwaysTrust,
oTrustModel,
oForceOwnertrust,
oNoAutoTrustNewKey,
oSetFilename,
oForYourEyesOnly,
oNoForYourEyesOnly,
oSetPolicyURL,
oSigPolicyURL,
oCertPolicyURL,
oShowPolicyURL,
oNoShowPolicyURL,
oSigKeyserverURL,
oUseEmbeddedFilename,
oNoUseEmbeddedFilename,
oComment,
oDefaultComment,
oNoComments,
oThrowKeyids,
oNoThrowKeyids,
oShowPhotos,
oNoShowPhotos,
oPhotoViewer,
oForceAEAD,
oS2KMode,
oS2KDigest,
oS2KCipher,
oS2KCount,
oDisplayCharset,
oNotDashEscaped,
oEscapeFrom,
oNoEscapeFrom,
oLockOnce,
oLockMultiple,
oLockNever,
oKeyServer,
oKeyServerOptions,
oImportOptions,
oImportFilter,
oExportOptions,
oExportFilter,
oListOptions,
oListFilter,
oVerifyOptions,
oTempDir,
oExecPath,
oEncryptTo,
oHiddenEncryptTo,
oNoEncryptTo,
oEncryptToDefaultKey,
oLoggerFD,
oLoggerFile,
oLogTime,
oUtf8Strings,
oNoUtf8Strings,
oDisableCipherAlgo,
oDisablePubkeyAlgo,
oAllowNonSelfsignedUID,
oNoAllowNonSelfsignedUID,
oAllowFreeformUID,
oNoAllowFreeformUID,
oAllowSecretKeyImport,
oAllowOldCipherAlgos,
oEnableSpecialFilenames,
oDisableFdTranslation,
oNoLiteral,
oSetFilesize,
oHonorHttpProxy,
oFastListMode,
oListOnly,
oIgnoreTimeConflict,
oIgnoreValidFrom,
oIgnoreCrcError,
oIgnoreMDCError,
oShowSessionKey,
oOverrideSessionKey,
oOverrideSessionKeyFD,
oNoRandomSeedFile,
oAutoKeyRetrieve,
oNoAutoKeyRetrieve,
oAutoKeyImport,
oNoAutoKeyImport,
oUseAgent,
oNoUseAgent,
oGpgAgentInfo,
oUseKeyboxd,
oMergeOnly,
oTryAllSecrets,
oTrustedKey,
oNoExpensiveTrustChecks,
oFixedListMode,
oLegacyListMode,
oNoSigCache,
oAutoCheckTrustDB,
oNoAutoCheckTrustDB,
oPreservePermissions,
oDefaultPreferenceList,
oDefaultKeyserverURL,
oPersonalCipherPreferences,
oPersonalDigestPreferences,
oPersonalCompressPreferences,
oAgentProgram,
oKeyboxdProgram,
oDirmngrProgram,
oDisableDirmngr,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oGroup,
oUnGroup,
oNoGroups,
oStrict,
oNoStrict,
oMangleDosFilenames,
oNoMangleDosFilenames,
oEnableProgressFilter,
oMultifile,
oKeyidFormat,
oExitOnStatusWriteError,
oLimitCardInsertTries,
oReaderPort,
octapiDriver,
opcscDriver,
oDisableCCID,
oRequireCrossCert,
oNoRequireCrossCert,
oAutoKeyLocate,
oNoAutoKeyLocate,
oEnableLargeRSA,
oDisableLargeRSA,
oEnableDSA2,
oDisableDSA2,
oAllowWeakDigestAlgos,
oAllowWeakKeySignatures,
oFakedSystemTime,
oNoAutostart,
oPrintDANERecords,
oTOFUDefaultPolicy,
oTOFUDBFormat,
oDefaultNewKeyAlgo,
oWeakDigest,
oUnwrap,
oOnlySignTextIDs,
oDisableSignerUID,
oSender,
oKeyOrigin,
oRequestOrigin,
oNoSymkeyCache,
oUseOnlyOpenPGPCard,
oFullTimestrings,
oIncludeKeyBlock,
oNoIncludeKeyBlock,
oChUid,
oForceSignKey,
oForbidGenKey,
oRequireCompliance,
oCompatibilityFlags,
oAddDesigRevoker,
oAssertSigner,
oAssertPubkeyAlgo,
oKbxBufferSize,
oRequirePQCEncryption,
oNoop
};
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aSign, "sign", N_("make a signature")),
ARGPARSE_c (aClearsign, "clear-sign", N_("make a clear text signature")),
ARGPARSE_c (aClearsign, "clearsign", "@"),
ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")),
ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")),
ARGPARSE_c (aEncrFiles, "encrypt-files", "@"),
ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),
ARGPARSE_c (aStore, "store", "@"),
ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")),
ARGPARSE_c (aDecryptFiles, "decrypt-files", "@"),
ARGPARSE_c (aVerify, "verify" , N_("verify a signature")),
ARGPARSE_c (aVerifyFiles, "verify-files" , "@" ),
ARGPARSE_c (aListKeys, "list-keys", N_("list keys")),
ARGPARSE_c (aListKeys, "list-public-keys", "@" ),
ARGPARSE_c (aListSigs, "list-signatures", N_("list keys and signatures")),
ARGPARSE_c (aListSigs, "list-sigs", "@"),
ARGPARSE_c (aCheckKeys, "check-signatures",
N_("list and check key signatures")),
ARGPARSE_c (aCheckKeys, "check-sigs", "@"),
ARGPARSE_c (oFingerprint, "fingerprint", N_("list keys and fingerprints")),
ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
ARGPARSE_c (aKeygen, "generate-key",
N_("generate a new key pair")),
ARGPARSE_c (aKeygen, "gen-key", "@"),
ARGPARSE_c (aQuickKeygen, "quick-generate-key" ,
N_("quickly generate a new key pair")),
ARGPARSE_c (aQuickKeygen, "quick-gen-key", "@"),
ARGPARSE_c (aQuickAddUid, "quick-add-uid",
N_("quickly add a new user-id")),
ARGPARSE_c (aQuickAddUid, "quick-adduid", "@"),
ARGPARSE_c (aQuickAddKey, "quick-add-key", "@"),
ARGPARSE_c (aQuickAddKey, "quick-addkey", "@"),
ARGPARSE_c (aQuickAddADSK, "quick-add-adsk", "@"),
ARGPARSE_c (aQuickRevUid, "quick-revoke-uid",
N_("quickly revoke a user-id")),
ARGPARSE_c (aQuickRevUid, "quick-revuid", "@"),
ARGPARSE_c (aQuickSetExpire, "quick-set-expire",
N_("quickly set a new expiration date")),
ARGPARSE_c (aQuickSetPrimaryUid, "quick-set-primary-uid", "@"),
ARGPARSE_c (aQuickUpdatePref, "quick-update-pref", "@"),
ARGPARSE_c (aQuickSetOwnertrust, "quick-set-ownertrust", "@"),
ARGPARSE_c (aFullKeygen, "full-generate-key" ,
N_("full featured key pair generation")),
ARGPARSE_c (aFullKeygen, "full-gen-key", "@"),
ARGPARSE_c (aGenRevoke, "generate-revocation",
N_("generate a revocation certificate")),
ARGPARSE_c (aGenRevoke, "gen-revoke", "@"),
ARGPARSE_c (aDeleteKeys,"delete-keys",
N_("remove keys from the public keyring")),
ARGPARSE_c (aDeleteSecretKeys, "delete-secret-keys",
N_("remove keys from the secret keyring")),
ARGPARSE_c (aQuickSignKey, "quick-sign-key" ,
N_("quickly sign a key")),
ARGPARSE_c (aQuickLSignKey, "quick-lsign-key",
N_("quickly sign a key locally")),
ARGPARSE_c (aQuickRevSig, "quick-revoke-sig" ,
N_("quickly revoke a key signature")),
ARGPARSE_c (aSignKey, "sign-key" ,N_("sign a key")),
ARGPARSE_c (aLSignKey, "lsign-key" ,N_("sign a key locally")),
ARGPARSE_c (aEditKey, "edit-key" ,N_("sign or edit a key")),
ARGPARSE_c (aEditKey, "key-edit" ,"@"),
ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")),
ARGPARSE_c (aPasswd, "passwd", "@"),
ARGPARSE_c (aDesigRevoke, "generate-designated-revocation", "@"),
ARGPARSE_c (aDesigRevoke, "desig-revoke","@" ),
ARGPARSE_c (aExport, "export" , N_("export keys") ),
ARGPARSE_c (aSendKeys, "send-keys" , N_("export keys to a keyserver") ),
ARGPARSE_c (aRecvKeys, "receive-keys" , N_("import keys from a keyserver") ),
ARGPARSE_c (aRecvKeys, "recv-keys" , "@"),
ARGPARSE_c (aSearchKeys, "search-keys" ,
N_("search for keys on a keyserver") ),
ARGPARSE_c (aRefreshKeys, "refresh-keys",
N_("update all keys from a keyserver")),
ARGPARSE_c (aLocateKeys, "locate-keys", "@"),
ARGPARSE_c (aLocateExtKeys, "locate-external-keys", "@"),
ARGPARSE_c (aFetchKeys, "fetch-keys" , "@" ),
ARGPARSE_c (aShowKeys, "show-keys" , "@" ),
ARGPARSE_c (aExportSecret, "export-secret-keys" , "@" ),
ARGPARSE_c (aExportSecretSub, "export-secret-subkeys" , "@" ),
ARGPARSE_c (aExportSshKey, "export-ssh-key", "@" ),
ARGPARSE_c (aExportSecretSshKey, "export-secret-ssh-key", "@" ),
ARGPARSE_c (aImport, "import", N_("import/merge keys")),
ARGPARSE_c (aFastImport, "fast-import", "@"),
#ifdef ENABLE_CARD_SUPPORT
ARGPARSE_c (aCardStatus, "card-status", N_("print the card status")),
ARGPARSE_c (aCardEdit, "edit-card", N_("change data on a card")),
ARGPARSE_c (aCardEdit, "card-edit", "@"),
ARGPARSE_c (aChangePIN, "change-pin", N_("change a card's PIN")),
#endif
ARGPARSE_c (aListConfig, "list-config", "@"),
ARGPARSE_c (aListGcryptConfig, "list-gcrypt-config", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@" ),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@" ),
ARGPARSE_c (aListPackets, "list-packets","@"),
#ifndef NO_TRUST_MODELS
ARGPARSE_c (aExportOwnerTrust, "export-ownertrust", "@"),
ARGPARSE_c (aImportOwnerTrust, "import-ownertrust", "@"),
ARGPARSE_c (aUpdateTrustDB,"update-trustdb",
N_("update the trust database")),
ARGPARSE_c (aCheckTrustDB, "check-trustdb", "@"),
ARGPARSE_c (aFixTrustDB, "fix-trustdb", "@"),
ARGPARSE_c (aListTrustDB, "list-trustdb", "@"),
#endif
ARGPARSE_c (aDeArmor, "dearmor", "@"),
ARGPARSE_c (aDeArmor, "dearmour", "@"),
ARGPARSE_c (aEnArmor, "enarmor", "@"),
ARGPARSE_c (aEnArmor, "enarmour", "@"),
ARGPARSE_c (aPrintMD, "print-md", N_("print message digests")),
ARGPARSE_c (aPrintMDs, "print-mds", "@"), /* old */
ARGPARSE_c (aPrimegen, "gen-prime", "@" ),
ARGPARSE_c (aGenRandom,"gen-random", "@" ),
ARGPARSE_c (aServer, "server", N_("run in server mode")),
ARGPARSE_c (aTOFUPolicy, "tofu-policy",
N_("|VALUE|set the TOFU policy for a key")),
/* Not yet used:
ARGPARSE_c (aListTrustPath, "list-trust-path", "@"), */
ARGPARSE_c (aDeleteSecretAndPublicKeys,
"delete-secret-and-public-keys", "@"),
ARGPARSE_c (aRebuildKeydbCaches, "rebuild-keydb-caches", "@"),
ARGPARSE_c (aListKeys, "list-key", "@"), /* alias */
ARGPARSE_c (aListSigs, "list-sig", "@"), /* alias */
ARGPARSE_c (aCheckKeys, "check-sig", "@"), /* alias */
ARGPARSE_c (aShowKeys, "show-key", "@"), /* alias */
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoTTY, "no-tty", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugIOLBF, "debug-iolbf", "@"),
ARGPARSE_s_u (oDebugSetIobufSize, "debug-set-iobuf-size", "@"),
ARGPARSE_s_u (oDebugAllowLargeChunks, "debug-allow-large-chunks", "@"),
ARGPARSE_s_s (oDisplayCharset, "display-charset", "@"),
ARGPARSE_s_s (oDisplayCharset, "charset", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_s (oLoggerFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_s_s (oLoggerFile, "logger-file", "@"), /* 1.4 compatibility. */
ARGPARSE_s_n (oLogTime, "log-time", "@"),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_s (oDefaultKey, "default-key",
N_("|NAME|use NAME as default secret key")),
ARGPARSE_s_s (oEncryptTo, "encrypt-to",
N_("|NAME|encrypt to user ID NAME as well")),
ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"),
ARGPARSE_s_s (oHiddenEncryptTo, "hidden-encrypt-to", "@"),
ARGPARSE_s_n (oEncryptToDefaultKey, "encrypt-to-default-key", "@"),
ARGPARSE_s_s (oDefRecipient, "default-recipient", "@"),
ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", "@"),
ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"),
ARGPARSE_s_s (oGroup, "group",
N_("|SPEC|set up email aliases")),
ARGPARSE_s_s (oUnGroup, "ungroup", "@"),
ARGPARSE_s_n (oNoGroups, "no-groups", "@"),
ARGPARSE_s_s (oCompliance, "compliance", "@"),
ARGPARSE_s_n (oGnuPG, "gnupg", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp2", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp6", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp7", "@"),
ARGPARSE_s_n (oGnuPG, "no-pgp8", "@"),
ARGPARSE_s_n (oRFC2440, "rfc2440", "@"),
ARGPARSE_s_n (oRFC4880, "rfc4880", "@"),
ARGPARSE_s_n (oOpenPGP, "openpgp", N_("use strict OpenPGP behavior")),
ARGPARSE_s_n (oPGP7, "pgp6", "@"),
ARGPARSE_s_n (oPGP7, "pgp7", "@"),
ARGPARSE_s_n (oPGP8, "pgp8", "@"),
ARGPARSE_s_s (oDefaultNewKeyAlgo, "default-new-key-algo", "@"),
ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"),
#ifndef NO_TRUST_MODELS
ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"),
#endif
ARGPARSE_s_s (oTrustModel, "trust-model", "@"),
ARGPARSE_s_s (oPhotoViewer, "photo-viewer", "@"),
ARGPARSE_s_s (oKnownNotation, "known-notation", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_n (oExitOnStatusWriteError, "exit-on-status-write-error", "@"),
ARGPARSE_s_i (oLimitCardInsertTries, "limit-card-insert-tries", "@"),
ARGPARSE_s_n (oEnableProgressFilter, "enable-progress-filter", "@"),
ARGPARSE_s_s (oTempDir, "temp-directory", "@"),
ARGPARSE_s_s (oExecPath, "exec-path", "@"),
ARGPARSE_s_n (oExpert, "expert", "@"),
ARGPARSE_s_n (oNoExpert, "no-expert", "@"),
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_s_n (oRequireSecmem, "require-secmem", "@"),
ARGPARSE_s_n (oNoRequireSecmem, "no-require-secmem", "@"),
ARGPARSE_s_n (oNoPermissionWarn, "no-permission-warning", "@"),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oInteractive, "interactive", N_("prompt before overwriting")),
ARGPARSE_s_s (oDefSigExpire, "default-sig-expire", "@"),
ARGPARSE_s_n (oAskSigExpire, "ask-sig-expire", "@"),
ARGPARSE_s_n (oNoAskSigExpire, "no-ask-sig-expire", "@"),
ARGPARSE_s_s (oDefCertExpire, "default-cert-expire", "@"),
ARGPARSE_s_n (oAskCertExpire, "ask-cert-expire", "@"),
ARGPARSE_s_n (oNoAskCertExpire, "no-ask-cert-expire", "@"),
ARGPARSE_s_i (oDefCertLevel, "default-cert-level", "@"),
ARGPARSE_s_i (oMinCertLevel, "min-cert-level", "@"),
ARGPARSE_s_n (oAskCertLevel, "ask-cert-level", "@"),
ARGPARSE_s_n (oNoAskCertLevel, "no-ask-cert-level", "@"),
ARGPARSE_s_n (oOnlySignTextIDs, "only-sign-text-ids", "@"),
ARGPARSE_s_n (oEnableLargeRSA, "enable-large-rsa", "@"),
ARGPARSE_s_n (oDisableLargeRSA, "disable-large-rsa", "@"),
ARGPARSE_s_n (oEnableDSA2, "enable-dsa2", "@"),
ARGPARSE_s_n (oDisableDSA2, "disable-dsa2", "@"),
ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-preferences","@"),
ARGPARSE_s_s (oPersonalDigestPreferences, "personal-digest-preferences","@"),
ARGPARSE_s_s (oPersonalCompressPreferences,
"personal-compress-preferences", "@"),
ARGPARSE_s_s (oDefaultPreferenceList, "default-preference-list", "@"),
ARGPARSE_s_s (oDefaultKeyserverURL, "default-keyserver-url", "@"),
ARGPARSE_s_n (oNoExpensiveTrustChecks, "no-expensive-trust-checks", "@"),
ARGPARSE_s_n (oAllowNonSelfsignedUID, "allow-non-selfsigned-uid", "@"),
ARGPARSE_s_n (oNoAllowNonSelfsignedUID, "no-allow-non-selfsigned-uid", "@"),
ARGPARSE_s_n (oAllowFreeformUID, "allow-freeform-uid", "@"),
ARGPARSE_s_n (oNoAllowFreeformUID, "no-allow-freeform-uid", "@"),
ARGPARSE_s_n (oPreservePermissions, "preserve-permissions", "@"),
ARGPARSE_s_i (oDefCertLevel, "default-cert-check-level", "@"), /* old */
ARGPARSE_s_s (oTOFUDefaultPolicy, "tofu-default-policy", "@"),
ARGPARSE_s_n (oLockOnce, "lock-once", "@"),
ARGPARSE_s_n (oLockMultiple, "lock-multiple", "@"),
ARGPARSE_s_n (oLockNever, "lock-never", "@"),
ARGPARSE_s_n (oNoCompress, "no-compress", "@"),
ARGPARSE_s_s (oCompressAlgo,"compress-algo", "@"),
ARGPARSE_s_s (oCompressAlgo, "compression-algo", "@"), /* Alias */
ARGPARSE_s_n (oBZ2DecompressLowmem, "bzip2-decompress-lowmem", "@"),
ARGPARSE_s_i (oCompletesNeeded, "completes-needed", "@"),
ARGPARSE_s_i (oMarginalsNeeded, "marginals-needed", "@"),
ARGPARSE_s_i (oMaxCertDepth, "max-cert-depth", "@" ),
#ifndef NO_TRUST_MODELS
ARGPARSE_s_s (oTrustDBName, "trustdb-name", "@"),
ARGPARSE_s_n (oAutoCheckTrustDB, "auto-check-trustdb", "@"),
ARGPARSE_s_n (oNoAutoCheckTrustDB, "no-auto-check-trustdb", "@"),
ARGPARSE_s_s (oForceOwnertrust, "force-ownertrust", "@"),
ARGPARSE_s_n (oNoAutoTrustNewKey, "no-auto-trust-new-key", "@"),
#endif
ARGPARSE_s_s (oAddDesigRevoker, "add-desig-revoker", "@"),
ARGPARSE_s_s (oAssertSigner, "assert-signer", "@"),
ARGPARSE_s_s (oAssertPubkeyAlgo,"assert-pubkey-algo", "@"),
ARGPARSE_header ("Input", N_("Options controlling the input")),
ARGPARSE_s_n (oMultifile, "multifile", "@"),
ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"),
ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"),
ARGPARSE_s_n (oNoUtf8Strings, "no-utf8-strings", "@"),
ARGPARSE_p_u (oSetFilesize, "set-filesize", "@"),
ARGPARSE_s_n (oNoLiteral, "no-literal", "@"),
ARGPARSE_s_s (oSetNotation, "set-notation", "@"),
ARGPARSE_s_s (oSigNotation, "sig-notation", "@"),
ARGPARSE_s_s (oCertNotation, "cert-notation", "@"),
ARGPARSE_s_s (oSetPolicyURL, "set-policy-url", "@"),
ARGPARSE_s_s (oSigPolicyURL, "sig-policy-url", "@"),
ARGPARSE_s_s (oCertPolicyURL, "cert-policy-url", "@"),
ARGPARSE_s_s (oSigKeyserverURL, "sig-keyserver-url", "@"),
ARGPARSE_header ("Output", N_("Options controlling the output")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_n (oArmor, "armour", "@"),
ARGPARSE_s_n (oNoArmor, "no-armor", "@"),
ARGPARSE_s_n (oNoArmor, "no-armour", "@"),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_p_u (oMaxOutput, "max-output", "@"),
ARGPARSE_s_s (oComment, "comment", "@"),
ARGPARSE_s_n (oDefaultComment, "default-comment", "@"),
ARGPARSE_s_n (oNoComments, "no-comments", "@"),
ARGPARSE_s_n (oEmitVersion, "emit-version", "@"),
ARGPARSE_s_n (oNoEmitVersion, "no-emit-version", "@"),
ARGPARSE_s_n (oNoEmitVersion, "no-version", "@"), /* alias */
ARGPARSE_s_n (oNotDashEscaped, "not-dash-escaped", "@"),
ARGPARSE_s_n (oEscapeFrom, "escape-from-lines", "@"),
ARGPARSE_s_n (oNoEscapeFrom, "no-escape-from-lines", "@"),
ARGPARSE_s_n (oMimemode, "mimemode", "@"),
ARGPARSE_s_n (oTextmodeShort, NULL, "@"),
ARGPARSE_s_n (oTextmode, "textmode", "@"),
ARGPARSE_s_n (oNoTextmode, "no-textmode", "@"),
ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
ARGPARSE_s_n (oForYourEyesOnly, "for-your-eyes-only", "@"),
ARGPARSE_s_n (oNoForYourEyesOnly, "no-for-your-eyes-only", "@"),
ARGPARSE_s_n (oShowNotation, "show-notation", "@"),
ARGPARSE_s_n (oNoShowNotation, "no-show-notation", "@"),
ARGPARSE_s_n (oShowSessionKey, "show-session-key", "@"),
ARGPARSE_s_n (oUseEmbeddedFilename, "use-embedded-filename", "@"),
ARGPARSE_s_n (oNoUseEmbeddedFilename, "no-use-embedded-filename", "@"),
ARGPARSE_s_n (oUnwrap, "unwrap", "@"),
ARGPARSE_s_n (oMangleDosFilenames, "mangle-dos-filenames", "@"),
ARGPARSE_s_n (oNoMangleDosFilenames, "no-mangle-dos-filenames", "@"),
ARGPARSE_s_i (oChunkSize, "chunk-size", "@"),
ARGPARSE_s_n (oNoSymkeyCache, "no-symkey-cache", "@"),
ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"),
ARGPARSE_s_n (oListOnly, "list-only", "@"),
ARGPARSE_s_i (oCompress, NULL,
N_("|N|set compress level to N (0 disables)")),
ARGPARSE_s_i (oCompressLevel, "compress-level", "@"),
ARGPARSE_s_i (oBZ2CompressLevel, "bzip2-compress-level", "@"),
ARGPARSE_s_n (oDisableSignerUID, "disable-signer-uid", "@"),
ARGPARSE_header ("ImportExport",
N_("Options controlling key import and export")),
ARGPARSE_s_s (oAutoKeyLocate, "auto-key-locate",
N_("|MECHANISMS|use MECHANISMS to locate keys by mail address")),
ARGPARSE_s_n (oNoAutoKeyLocate, "no-auto-key-locate", "@"),
ARGPARSE_s_n (oAutoKeyImport, "auto-key-import",
N_("import missing key from a signature")),
ARGPARSE_s_n (oNoAutoKeyImport, "no-auto-key-import", "@"),
ARGPARSE_s_n (oAutoKeyRetrieve, "auto-key-retrieve", "@"),
ARGPARSE_s_n (oNoAutoKeyRetrieve, "no-auto-key-retrieve", "@"),
ARGPARSE_s_n (oIncludeKeyBlock, "include-key-block",
N_("include the public key in signatures")),
ARGPARSE_s_n (oNoIncludeKeyBlock, "no-include-key-block", "@"),
ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr",
N_("disable all access to the dirmngr")),
ARGPARSE_s_s (oKeyServer, "keyserver", "@"), /* Deprecated. */
ARGPARSE_s_s (oKeyServerOptions, "keyserver-options", "@"),
ARGPARSE_s_s (oKeyOrigin, "key-origin", "@"),
ARGPARSE_s_s (oImportOptions, "import-options", "@"),
ARGPARSE_s_s (oImportFilter, "import-filter", "@"),
ARGPARSE_s_s (oExportOptions, "export-options", "@"),
ARGPARSE_s_s (oExportFilter, "export-filter", "@"),
ARGPARSE_s_n (oMergeOnly, "merge-only", "@" ),
ARGPARSE_s_n (oAllowSecretKeyImport, "allow-secret-key-import", "@"),
ARGPARSE_header ("Keylist", N_("Options controlling key listings")),
ARGPARSE_s_s (oListOptions, "list-options", "@"),
ARGPARSE_s_s (oListFilter, "list-filter", "@"),
ARGPARSE_s_n (oFullTimestrings, "full-timestrings", "@"),
ARGPARSE_s_n (oShowPhotos, "show-photos", "@"),
ARGPARSE_s_n (oNoShowPhotos, "no-show-photos", "@"),
ARGPARSE_s_n (oShowPolicyURL, "show-policy-url", "@"),
ARGPARSE_s_n (oNoShowPolicyURL, "no-show-policy-url", "@"),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oWithTofuInfo,"with-tofu-info", "@"),
ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"),
ARGPARSE_s_n (oWithSigList,"with-sig-list", "@"),
ARGPARSE_s_n (oWithSigCheck,"with-sig-check", "@"),
ARGPARSE_s_n (oWithV5Fingerprint, "with-v5-fingerprint", "@"),
ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"),
ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprint", "@"),
ARGPARSE_s_n (oWithSubkeyFingerprint, "with-subkey-fingerprints", "@"),
ARGPARSE_s_n (oWithoutSubkeyFingerprint, "without-subkey-fingerprint", "@"),
ARGPARSE_s_n (oWithICAOSpelling, "with-icao-spelling", "@"),
ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"),
ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"),
ARGPARSE_s_n (oWithSecret, "with-secret", "@"),
ARGPARSE_s_n (oWithWKDHash, "with-wkd-hash", "@"),
ARGPARSE_s_n (oWithKeyOrigin, "with-key-origin", "@"),
ARGPARSE_s_n (oFastListMode, "fast-list-mode", "@"),
ARGPARSE_s_n (oFixedListMode, "fixed-list-mode", "@"),
ARGPARSE_s_n (oLegacyListMode, "legacy-list-mode", "@"),
ARGPARSE_s_n (oPrintDANERecords, "print-dane-records", "@"),
ARGPARSE_s_s (oKeyidFormat, "keyid-format", "@"),
ARGPARSE_s_n (oShowKeyring, "show-keyring", "@"),
ARGPARSE_header (NULL, N_("Options to specify keys")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oHiddenRecipient, "hidden-recipient", "@"),
ARGPARSE_s_s (oRecipientFile, "recipient-file", "@"),
ARGPARSE_s_s (oHiddenRecipientFile, "hidden-recipient-file", "@"),
ARGPARSE_s_s (oRecipient, "remote-user", "@"), /* (old option name) */
ARGPARSE_s_n (oThrowKeyids, "throw-keyids", "@"),
ARGPARSE_s_n (oNoThrowKeyids, "no-throw-keyids", "@"),
ARGPARSE_s_s (oLocalUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oTrustedKey, "trusted-key", "@"),
ARGPARSE_s_s (oSender, "sender", "@"),
ARGPARSE_s_s (oTrySecretKey, "try-secret-key", "@"),
ARGPARSE_s_n (oTryAllSecrets, "try-all-secrets", "@"),
ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"),
ARGPARSE_s_n (oNoKeyring, "no-keyring", "@"),
ARGPARSE_s_s (oKeyring, "keyring", "@"),
ARGPARSE_s_s (oPrimaryKeyring, "primary-keyring", "@"),
ARGPARSE_s_s (oSecretKeyring, "secret-keyring", "@"),
ARGPARSE_s_n (oSkipHiddenRecipients, "skip-hidden-recipients", "@"),
ARGPARSE_s_n (oNoSkipHiddenRecipients, "no-skip-hidden-recipients", "@"),
ARGPARSE_s_s (oOverrideSessionKey, "override-session-key", "@"),
ARGPARSE_s_i (oOverrideSessionKeyFD, "override-session-key-fd", "@"),
ARGPARSE_header ("Security", N_("Options controlling the security")),
ARGPARSE_s_i (oS2KMode, "s2k-mode", "@"),
ARGPARSE_s_s (oS2KDigest, "s2k-digest-algo", "@"),
ARGPARSE_s_s (oS2KCipher, "s2k-cipher-algo", "@"),
ARGPARSE_s_i (oS2KCount, "s2k-count", "@"),
ARGPARSE_s_n (oForceAEAD, "force-ocb", "@"),
ARGPARSE_s_n (oForceAEAD, "force-aead", "@"), /*(old name)*/
ARGPARSE_s_n (oRequireCrossCert, "require-backsigs", "@"),
ARGPARSE_s_n (oRequireCrossCert, "require-cross-certification", "@"),
ARGPARSE_s_n (oNoRequireCrossCert, "no-require-backsigs", "@"),
ARGPARSE_s_n (oNoRequireCrossCert, "no-require-cross-certification", "@"),
/* Options to override new security defaults. */
ARGPARSE_s_n (oAllowWeakKeySignatures, "allow-weak-key-signatures", "@"),
ARGPARSE_s_n (oAllowWeakDigestAlgos, "allow-weak-digest-algos", "@"),
ARGPARSE_s_n (oAllowOldCipherAlgos, "allow-old-cipher-algos", "@"),
ARGPARSE_s_s (oWeakDigest, "weak-digest","@"),
ARGPARSE_s_s (oVerifyOptions, "verify-options", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
ARGPARSE_s_n (oNoSigCache, "no-sig-cache", "@"),
ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"),
ARGPARSE_s_n (oIgnoreValidFrom, "ignore-valid-from", "@"),
ARGPARSE_s_n (oIgnoreCrcError, "ignore-crc-error", "@"),
ARGPARSE_s_n (oIgnoreMDCError, "ignore-mdc-error", "@"),
ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"),
ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"),
ARGPARSE_s_s (oCipherAlgo, "cipher-algo", "@"),
ARGPARSE_s_s (oDigestAlgo, "digest-algo", "@"),
ARGPARSE_s_s (oCertDigestAlgo, "cert-digest-algo", "@"),
ARGPARSE_s_n (oRequirePQCEncryption, "require-pqc-encryption", "@"),
ARGPARSE_header (NULL, N_("Options for unattended use")),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_n (oNoBatch, "no-batch", "@"),
ARGPARSE_s_n (oAnswerYes, "yes", "@"),
ARGPARSE_s_n (oAnswerNo, "no", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", "@"),
ARGPARSE_s_s (oStatusFile, "status-file", "@"),
ARGPARSE_s_i (oAttributeFD, "attribute-fd", "@"),
ARGPARSE_s_s (oAttributeFile, "attribute-file", "@"),
ARGPARSE_s_i (oCommandFD, "command-fd", "@"),
ARGPARSE_s_s (oCommandFile, "command-file", "@"),
ARGPARSE_o_s (oPassphrase, "passphrase", "@"),
ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"),
ARGPARSE_s_s (oPassphraseFile, "passphrase-file", "@"),
ARGPARSE_s_i (oPassphraseRepeat,"passphrase-repeat", "@"),
ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"),
ARGPARSE_s_n (oForceSignKey, "force-sign-key", "@"),
ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"),
ARGPARSE_s_n (oDisableFdTranslation, "disable-fd-translation", "@"),
ARGPARSE_header (NULL, N_("Other options")),
ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oChUid, "chuid", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_n (oUseKeyboxd, "use-keyboxd", "@"),
ARGPARSE_s_n (oForbidGenKey, "forbid-gen-key", "@"),
ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"),
ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"),
/* Options which can be used in special circumstances. They are not
* published and we hope they are never required. */
ARGPARSE_s_n (oUseOnlyOpenPGPCard, "use-only-openpgp-card", "@"),
/* Esoteric compatibility options. */
ARGPARSE_s_n (oRFC2440Text, "rfc2440-text", "@"),
ARGPARSE_s_n (oNoRFC2440Text, "no-rfc2440-text", "@"),
ARGPARSE_p_u (oKbxBufferSize, "kbx-buffer-size", "@"),
ARGPARSE_s_n (oQuickRandom, "debug-quick-random", "@"),
ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"),
ARGPARSE_header (NULL, ""), /* Stop the header group. */
/* Aliases. I constantly mistype these, and assume other people do
as well. */
ARGPARSE_s_s (oPersonalCipherPreferences, "personal-cipher-prefs", "@"),
ARGPARSE_s_s (oPersonalCompressPreferences, "personal-compress-prefs", "@"),
/* These two are aliases to help users of the PGP command line
product use gpg with minimal pain. Many commands are common
already as they seem to have borrowed commands from us. Now I'm
returning the favor. */
ARGPARSE_s_s (oLocalUser, "sign-with", "@"),
ARGPARSE_s_s (oRecipient, "user", "@"),
/* Dummy options with warnings. */
ARGPARSE_s_n (oUseAgent, "use-agent", "@"),
ARGPARSE_s_n (oNoUseAgent, "no-use-agent", "@"),
ARGPARSE_s_s (oGpgAgentInfo, "gpg-agent-info", "@"),
ARGPARSE_s_s (oReaderPort, "reader-port", "@"),
ARGPARSE_s_s (octapiDriver, "ctapi-driver", "@"),
ARGPARSE_s_s (opcscDriver, "pcsc-driver", "@"),
ARGPARSE_s_n (oDisableCCID, "disable-ccid", "@"),
ARGPARSE_s_n (oHonorHttpProxy, "honor-http-proxy", "@"),
ARGPARSE_s_s (oTOFUDBFormat, "tofu-db-format", "@"),
/* Dummy options. */
ARGPARSE_ignore (oStrict, "strict"),
ARGPARSE_ignore (oNoStrict, "no-strict"),
ARGPARSE_ignore (oLoadExtension, "load-extension"), /* from 1.4. */
ARGPARSE_s_n (oNoop, "sk-comments", "@"),
ARGPARSE_s_n (oNoop, "no-sk-comments", "@"),
ARGPARSE_s_n (oNoop, "compress-keys", "@"),
ARGPARSE_s_n (oNoop, "compress-sigs", "@"),
ARGPARSE_s_n (oNoop, "force-v3-sigs", "@"),
ARGPARSE_s_n (oNoop, "no-force-v3-sigs", "@"),
ARGPARSE_s_n (oNoop, "force-v4-certs", "@"),
ARGPARSE_s_n (oNoop, "no-force-v4-certs", "@"),
ARGPARSE_s_n (oNoop, "no-mdc-warning", "@"),
ARGPARSE_s_n (oNoop, "force-mdc", "@"),
ARGPARSE_s_n (oNoop, "no-force-mdc", "@"),
ARGPARSE_s_n (oNoop, "disable-mdc", "@"),
ARGPARSE_s_n (oNoop, "no-disable-mdc", "@"),
ARGPARSE_s_n (oNoop, "allow-multisig-verification", "@"),
ARGPARSE_s_n (oNoop, "allow-multiple-messages", "@"),
ARGPARSE_s_n (oNoop, "no-allow-multiple-messages", "@"),
ARGPARSE_s_s (oNoop, "aead-algo", "@"),
ARGPARSE_s_s (oNoop, "personal-aead-preferences","@"),
ARGPARSE_s_n (oNoop, "rfc4880bis", "@"),
ARGPARSE_s_n (oNoop, "override-compliance-check", "@"),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
ARGPARSE_group (303, N_("@\nExamples:\n\n"
" -se -r Bob [file] sign and encrypt for user Bob\n"
" --clear-sign [file] make a clear text signature\n"
" --detach-sign [file] make a detached signature\n"
" --list-keys [names] show keys\n"
" --fingerprint [names] show fingerprints\n")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_PACKET_VALUE , "packet" },
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_FILTER_VALUE , "filter" },
{ DBG_IOBUF_VALUE , "iobuf" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_TRUST_VALUE , "trust" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* The list of compatibility flags. */
static struct compatibility_flags_s compatibility_flags [] =
{
{ COMPAT_PARALLELIZED, "parallelized" },
{ COMPAT_T7014_OLD, "t7014-old" },
{ 0, NULL }
};
#ifdef ENABLE_SELINUX_HACKS
#define ALWAYS_ADD_KEYRINGS 1
#else
#define ALWAYS_ADD_KEYRINGS 0
#endif
/* The list of the default AKL methods. */
#define DEFAULT_AKL_LIST "local,wkd"
/* Can be set to true to force gpg to return with EXIT_FAILURE. */
int g10_errors_seen = 0;
/* If opt.assert_signer_list is used and this variable is not true
* gpg will be forced to return EXIT_FAILURE. */
int assert_signer_true = 0;
/* If opt.assert_pubkey_algo is used and this variable is not true
* gpg will be forced to return EXIT_FAILURE. */
int assert_pubkey_algo_false = 0;
static int utf8_strings =
#ifdef HAVE_W32_SYSTEM
1
#else
0
#endif
;
static int maybe_setuid = 1;
static unsigned int opt_set_iobuf_size;
static unsigned int opt_set_iobuf_size_used;
static int opt_log_time;
/* Collection of options used only in this module. */
static struct {
unsigned int forbid_gen_key;
} mopt;
static char *build_list( const char *text, char letter,
const char *(*mapf)(int), int (*chkf)(int) );
static void set_cmd( enum cmd_and_opt_values *ret_cmd,
enum cmd_and_opt_values new_cmd );
static void print_mds( const char *fname, int algo );
static void add_notation_data( const char *string, int which );
static void add_policy_url( const char *string, int which );
static void add_keyserver_url( const char *string, int which );
static void emergency_cleanup (void);
static void read_sessionkey_from_fd (int fd);
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static int
build_list_pk_test_algo (int algo)
{
/* Show only one "RSA" string. If RSA_E or RSA_S is available RSA
is also available. */
if (algo == PUBKEY_ALGO_RSA_E
|| algo == PUBKEY_ALGO_RSA_S)
return GPG_ERR_DIGEST_ALGO;
return openpgp_pk_test_algo (algo);
}
static const char *
build_list_pk_algo_name (int algo)
{
return openpgp_pk_algo_name (algo);
}
static int
build_list_cipher_test_algo (int algo)
{
return openpgp_cipher_test_algo (algo);
}
static const char *
build_list_cipher_algo_name (int algo)
{
return openpgp_cipher_algo_name (algo);
}
static int
build_list_md_test_algo (int algo)
{
/* By default we do not accept MD5 based signatures. To avoid
confusion we do not announce support for it either. */
if (algo == DIGEST_ALGO_MD5)
return GPG_ERR_DIGEST_ALGO;
return openpgp_md_test_algo (algo);
}
static const char *
build_list_md_algo_name (int algo)
{
return openpgp_md_algo_name (algo);
}
static const char *
my_strusage( int level )
{
static char *digests, *pubkeys, *ciphers, *zips, *ver_gcry;
const char *p;
switch( level ) {
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@GPG@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
#ifdef IS_DEVELOPMENT_VERSION
case 25:
p="NOTE: THIS IS A DEVELOPMENT VERSION!";
break;
case 26:
p="It is only intended for test purposes and should NOT be";
break;
case 27:
p="used in a production environment or with production keys!";
break;
#endif
case 1:
case 40: p =
_("Usage: @GPG@ [options] [files] (-h for help)");
break;
case 41: p =
_("Syntax: @GPG@ [options] [files]\n"
"Sign, check, encrypt or decrypt\n"
"Default operation depends on the input data\n");
break;
case 31: p = "\nHome: "; break;
#ifndef __riscos__
case 32: p = gnupg_homedir (); break;
#else /* __riscos__ */
case 32: p = make_filename(gnupg_homedir (), NULL); break;
#endif /* __riscos__ */
case 33: p = _("\nSupported algorithms:\n"); break;
case 34:
if (!pubkeys)
pubkeys = build_list (_("Pubkey: "), 1,
build_list_pk_algo_name,
build_list_pk_test_algo );
p = pubkeys;
break;
case 35:
if( !ciphers )
ciphers = build_list(_("Cipher: "), 'S',
build_list_cipher_algo_name,
build_list_cipher_test_algo );
p = ciphers;
break;
case 37:
if( !digests )
digests = build_list(_("Hash: "), 'H',
build_list_md_algo_name,
build_list_md_test_algo );
p = digests;
break;
case 38:
if( !zips )
zips = build_list(_("Compression: "),'Z',
compress_algo_to_string,
check_compress_algo);
p = zips;
break;
case 95:
p = "1"; /* <-- Enable globbing under Windows (see init.c) */
break;
default: p = NULL;
}
return p;
}
static char *
build_list (const char *text, char letter,
const char * (*mapf)(int), int (*chkf)(int))
{
membuf_t mb;
int indent;
int i, j, len;
int limit;
const char *s;
char *string;
if (maybe_setuid)
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
indent = utf8_charcount (text, -1);
len = 0;
init_membuf (&mb, 512);
limit = (letter == 'A')? 4 : 110;
for (i=0; i <= limit; i++ )
{
if (!chkf (i) && (s = mapf (i)))
{
if (mb.len - len > 60)
{
put_membuf_str (&mb, ",\n");
len = mb.len;
for (j=0; j < indent; j++)
put_membuf_str (&mb, " ");
}
else if (mb.len)
put_membuf_str (&mb, ", ");
else
put_membuf_str (&mb, text);
put_membuf_str (&mb, s);
if (opt.verbose && letter)
{
char num[20];
if (letter == 1)
snprintf (num, sizeof num, " (%d)", i);
else
snprintf (num, sizeof num, " (%c%d)", letter, i);
put_membuf_str (&mb, num);
}
}
}
if (mb.len)
put_membuf_str (&mb, "\n");
put_membuf (&mb, "", 1);
string = get_membuf (&mb, NULL);
return xrealloc (string, strlen (string)+1);
}
static void
wrong_args( const char *text)
{
es_fprintf (es_stderr, _("usage: %s [options] %s\n"), GPG_NAME, text);
log_inc_errorcount ();
g10_exit(2);
}
static char *
make_username( const char *string )
{
char *p;
if( utf8_strings )
p = xstrdup(string);
else
p = native_to_utf8( string );
return p;
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_MEMSTAT_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_MEMSTAT_VALUE|DBG_TRUST_VALUE|DBG_EXTPROG_VALUE
|DBG_CACHE_VALUE|DBG_LOOKUP|DBG_FILTER_VALUE|DBG_PACKET_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
g10_exit (2);
}
if ((opt.debug & DBG_MEMORY_VALUE))
memory_debug_mode = 1;
if ((opt.debug & DBG_MEMSTAT_VALUE))
memory_stat_debug_mode = 1;
if (DBG_MPI)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (DBG_CRYPTO)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
if ((opt.debug & DBG_IOBUF_VALUE))
iobuf_debug_mode = 1;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
/* Make sure that we are --verbose in debug mode. */
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt_set_iobuf_size || opt_set_iobuf_size_used)
log_debug ("iobuf buffer size is %uk\n",
iobuf_set_buffer_size (opt_set_iobuf_size));
}
/* We set the screen dimensions for UI purposes. Do not allow screens
smaller than 80x24 for the sake of simplicity. */
static void
set_screen_dimensions(void)
{
#ifndef HAVE_W32_SYSTEM
char *str;
str=getenv("COLUMNS");
if(str)
opt.screen_columns=atoi(str);
str=getenv("LINES");
if(str)
opt.screen_lines=atoi(str);
#endif
if(opt.screen_columns<80 || opt.screen_columns>255)
opt.screen_columns=80;
if(opt.screen_lines<24 || opt.screen_lines>255)
opt.screen_lines=24;
}
/* Helper to open a file FNAME either for reading or writing to be
used with --status-file etc functions. Not generally useful but it
avoids the riscos specific functions and well some Windows people
might like it too. Prints an error message and returns -1 on
error. On success the file descriptor is returned. */
static int
open_info_file (const char *fname, int for_write, int binary)
{
#ifdef __riscos__
return riscos_fdopenfile (fname, for_write);
#elif defined (ENABLE_SELINUX_HACKS)
/* We can't allow these even when testing for a secured filename
because files to be secured might not yet been secured. This is
similar to the option file but in that case it is unlikely that
sensitive information may be retrieved by means of error
messages. */
(void)fname;
(void)for_write;
(void)binary;
return -1;
#else
int fd;
if (binary)
binary = MY_O_BINARY;
/* if (is_secured_filename (fname)) */
/* { */
/* fd = -1; */
/* gpg_err_set_errno (EPERM); */
/* } */
/* else */
/* { */
do
{
if (for_write)
fd = gnupg_open (fname, O_CREAT | O_TRUNC | O_WRONLY | binary,
S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP);
else
fd = gnupg_open (fname, O_RDONLY | binary, 0);
}
while (fd == -1 && errno == EINTR);
/* } */
if ( fd == -1)
log_error ( for_write? _("can't create '%s': %s\n")
: _("can't open '%s': %s\n"), fname, strerror(errno));
return fd;
#endif
}
static void
set_cmd( enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd )
{
enum cmd_and_opt_values cmd = *ret_cmd;
if( !cmd || cmd == new_cmd )
cmd = new_cmd;
else if( cmd == aSign && new_cmd == aEncr )
cmd = aSignEncr;
else if( cmd == aEncr && new_cmd == aSign )
cmd = aSignEncr;
else if( cmd == aSign && new_cmd == aSym )
cmd = aSignSym;
else if( cmd == aSym && new_cmd == aSign )
cmd = aSignSym;
else if( cmd == aSym && new_cmd == aEncr )
cmd = aEncrSym;
else if( cmd == aEncr && new_cmd == aSym )
cmd = aEncrSym;
else if (cmd == aSignEncr && new_cmd == aSym)
cmd = aSignEncrSym;
else if (cmd == aSignSym && new_cmd == aEncr)
cmd = aSignEncrSym;
else if (cmd == aEncrSym && new_cmd == aSign)
cmd = aSignEncrSym;
else if( ( cmd == aSign && new_cmd == aClearsign )
|| ( cmd == aClearsign && new_cmd == aSign ) )
cmd = aClearsign;
else {
log_error(_("conflicting commands\n"));
g10_exit(2);
}
*ret_cmd = cmd;
}
static void
add_group(char *string)
{
char *name,*value;
struct groupitem *item;
/* Break off the group name */
name=strsep(&string,"=");
if(string==NULL)
{
log_error(_("no = sign found in group definition '%s'\n"),name);
return;
}
trim_trailing_ws(name,strlen(name));
/* Does this group already exist? */
for(item=opt.grouplist;item;item=item->next)
if(strcasecmp(item->name,name)==0)
break;
if(!item)
{
item=xmalloc(sizeof(struct groupitem));
item->name=name;
item->next=opt.grouplist;
item->values=NULL;
opt.grouplist=item;
}
/* Break apart the values */
while ((value= strsep(&string," \t")))
{
if (*value)
add_to_strlist2(&item->values,value,utf8_strings);
}
}
static void
rm_group(char *name)
{
struct groupitem *item,*last=NULL;
trim_trailing_ws(name,strlen(name));
for(item=opt.grouplist;item;last=item,item=item->next)
{
if(strcasecmp(item->name,name)==0)
{
if(last)
last->next=item->next;
else
opt.grouplist=item->next;
free_strlist(item->values);
xfree(item);
break;
}
}
}
/* We need to check three things.
0) The homedir. It must be x00, a directory, and owned by the
user.
1) The options/gpg.conf file. Okay unless it or its containing
directory is group or other writable or not owned by us. Disable
exec in this case.
2) Extensions. Same as #1.
Returns true if the item is unsafe. */
static int
check_permissions (const char *path, int item)
{
#if defined(HAVE_STAT) && !defined(HAVE_DOSISH_SYSTEM)
static int homedir_cache=-1;
char *tmppath,*dir;
struct stat statbuf,dirbuf;
int homedir=0,ret=0,checkonly=0;
int perm=0,own=0,enc_dir_perm=0,enc_dir_own=0;
if(opt.no_perm_warn)
return 0;
log_assert(item==0 || item==1 || item==2);
/* extensions may attach a path */
if(item==2 && path[0]!=DIRSEP_C)
{
if(strchr(path,DIRSEP_C))
tmppath=make_filename(path,NULL);
else
tmppath=make_filename(gnupg_libdir (),path,NULL);
}
else
tmppath=xstrdup(path);
/* If the item is located in the homedir, but isn't the homedir,
don't continue if we already checked the homedir itself. This is
to avoid user confusion with an extra options file warning which
could be rectified if the homedir itself had proper
permissions. */
if(item!=0 && homedir_cache>-1
&& !ascii_strncasecmp (gnupg_homedir (), tmppath,
strlen (gnupg_homedir ())))
{
ret=homedir_cache;
goto end;
}
/* It's okay if the file or directory doesn't exist */
if (gnupg_stat (tmppath,&statbuf))
{
ret=0;
goto end;
}
/* Now check the enclosing directory. Theoretically, we could walk
this test up to the root directory /, but for the sake of sanity,
I'm stopping at one level down. */
dir=make_dirname(tmppath);
if (gnupg_stat (dir,&dirbuf) || !S_ISDIR (dirbuf.st_mode))
{
/* Weird error */
xfree(dir);
ret=1;
goto end;
}
xfree(dir);
/* Assume failure */
ret=1;
if(item==0)
{
/* The homedir must be x00, a directory, and owned by the user. */
if(S_ISDIR(statbuf.st_mode))
{
if(statbuf.st_uid==getuid())
{
if((statbuf.st_mode & (S_IRWXG|S_IRWXO))==0)
ret=0;
else
perm=1;
}
else
own=1;
homedir_cache=ret;
}
}
else if(item==1 || item==2)
{
/* The options or extension file. Okay unless it or its
containing directory is group or other writable or not owned
by us or root. */
if(S_ISREG(statbuf.st_mode))
{
if(statbuf.st_uid==getuid() || statbuf.st_uid==0)
{
if((statbuf.st_mode & (S_IWGRP|S_IWOTH))==0)
{
/* it's not writable, so make sure the enclosing
directory is also not writable */
if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0)
{
if((dirbuf.st_mode & (S_IWGRP|S_IWOTH))==0)
ret=0;
else
enc_dir_perm=1;
}
else
enc_dir_own=1;
}
else
{
/* it's writable, so the enclosing directory had
better not let people get to it. */
if(dirbuf.st_uid==getuid() || dirbuf.st_uid==0)
{
if((dirbuf.st_mode & (S_IRWXG|S_IRWXO))==0)
ret=0;
else
perm=enc_dir_perm=1; /* unclear which one to fix! */
}
else
enc_dir_own=1;
}
}
else
own=1;
}
}
else
BUG();
if(!checkonly)
{
if(own)
{
if(item==0)
log_info(_("WARNING: unsafe ownership on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe ownership on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe ownership on"
" extension '%s'\n"),tmppath);
}
if(perm)
{
if(item==0)
log_info(_("WARNING: unsafe permissions on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe permissions on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe permissions on"
" extension '%s'\n"),tmppath);
}
if(enc_dir_own)
{
if(item==0)
log_info(_("WARNING: unsafe enclosing directory ownership on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe enclosing directory ownership on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe enclosing directory ownership on"
" extension '%s'\n"),tmppath);
}
if(enc_dir_perm)
{
if(item==0)
log_info(_("WARNING: unsafe enclosing directory permissions on"
" homedir '%s'\n"),tmppath);
else if(item==1)
log_info(_("WARNING: unsafe enclosing directory permissions on"
" configuration file '%s'\n"),tmppath);
else
log_info(_("WARNING: unsafe enclosing directory permissions on"
" extension '%s'\n"),tmppath);
}
}
end:
xfree(tmppath);
if(homedir)
homedir_cache=ret;
return ret;
#else /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/
(void)path;
(void)item;
return 0;
#endif /*!(HAVE_STAT && !HAVE_DOSISH_SYSTEM)*/
}
/* Print the OpenPGP defined algo numbers. */
static void
print_algo_numbers(int (*checker)(int))
{
int i,first=1;
for(i=0;i<=110;i++)
{
if(!checker(i))
{
if(first)
first=0;
else
es_printf (";");
es_printf ("%d",i);
}
}
}
static void
print_algo_names(int (*checker)(int),const char *(*mapper)(int))
{
int i,first=1;
for(i=0;i<=110;i++)
{
if(!checker(i))
{
if(first)
first=0;
else
es_printf (";");
es_printf ("%s",mapper(i));
}
}
}
/* In the future, we can do all sorts of interesting configuration
output here. For now, just give "group" as the Enigmail folks need
it, and pubkey, cipher, hash, and compress as they may be useful
for frontends. */
static void
list_config(char *items)
{
int show_all = !items;
char *name = NULL;
const char *s;
struct groupitem *giter;
int first, iter;
if(!opt.with_colons)
return;
while(show_all || (name=strsep(&items," ")))
{
int any=0;
if(show_all || ascii_strcasecmp(name,"group")==0)
{
for (giter = opt.grouplist; giter; giter = giter->next)
{
strlist_t sl;
es_fprintf (es_stdout, "cfg:group:");
es_write_sanitized (es_stdout, giter->name, strlen(giter->name),
":", NULL);
es_putc (':', es_stdout);
for(sl=giter->values; sl; sl=sl->next)
{
es_write_sanitized (es_stdout, sl->d, strlen (sl->d),
":;", NULL);
if(sl->next)
es_printf(";");
}
es_printf("\n");
}
any=1;
}
if(show_all || ascii_strcasecmp(name,"version")==0)
{
es_printf("cfg:version:");
es_write_sanitized (es_stdout, VERSION, strlen(VERSION), ":", NULL);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"pubkey")==0)
{
es_printf ("cfg:pubkey:");
print_algo_numbers (build_list_pk_test_algo);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"pubkeyname")==0)
{
es_printf ("cfg:pubkeyname:");
print_algo_names (build_list_pk_test_algo,
build_list_pk_algo_name);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"cipher")==0)
{
es_printf ("cfg:cipher:");
print_algo_numbers (build_list_cipher_test_algo);
es_printf ("\n");
any=1;
}
if (show_all || !ascii_strcasecmp (name,"ciphername"))
{
es_printf ("cfg:ciphername:");
print_algo_names (build_list_cipher_test_algo,
build_list_cipher_algo_name);
es_printf ("\n");
any = 1;
}
if(show_all
|| ascii_strcasecmp(name,"digest")==0
|| ascii_strcasecmp(name,"hash")==0)
{
es_printf ("cfg:digest:");
print_algo_numbers (build_list_md_test_algo);
es_printf ("\n");
any=1;
}
if (show_all
|| !ascii_strcasecmp(name,"digestname")
|| !ascii_strcasecmp(name,"hashname"))
{
es_printf ("cfg:digestname:");
print_algo_names (build_list_md_test_algo,
build_list_md_algo_name);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp(name,"compress")==0)
{
es_printf ("cfg:compress:");
print_algo_numbers(check_compress_algo);
es_printf ("\n");
any=1;
}
if(show_all || ascii_strcasecmp (name, "compressname") == 0)
{
es_printf ("cfg:compressname:");
print_algo_names (check_compress_algo,
compress_algo_to_string);
es_printf ("\n");
any=1;
}
if (show_all || !ascii_strcasecmp(name,"ccid-reader-id"))
{
/* We ignore this for GnuPG 1.4 backward compatibility. */
any=1;
}
if (show_all || !ascii_strcasecmp (name,"curve"))
{
es_printf ("cfg:curve:");
for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first=0)
es_printf ("%s%s", first?"":";", s);
es_printf ("\n");
any=1;
}
/* Curve OIDs are rarely useful and thus only printed if requested. */
if (name && !ascii_strcasecmp (name,"curveoid"))
{
es_printf ("cfg:curveoid:");
for (iter=0, first=1; (s = openpgp_enum_curves (&iter)); first = 0)
{
s = openpgp_curve_to_oid (s, NULL, NULL);
es_printf ("%s%s", first?"":";", s? s:"[?]");
}
es_printf ("\n");
any=1;
}
if(show_all)
break;
if(!any)
log_error(_("unknown configuration item '%s'\n"),name);
}
}
/* List default values for use by gpgconf. */
static void
gpgconf_list (void)
{
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("compliance:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT, "gnupg");
/* The next one is an info only item and should match the macros at
the top of keygen.c */
es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
get_default_pubkey_algo ());
/* This info only mode tells whether the we are running in de-vs
* compliance mode. This does not test all parameters but the basic
* conditions like a proper RNG and Libgcrypt. AS of now we always
* return 0 because this version of gnupg has not yet received an
- * appoval. */
+ * approval. */
es_printf ("compliance_de_vs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT,
0 /*gnupg_rng_is_compliant (CO_DE_VS)*/);
es_printf ("use_keyboxd:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, opt.use_keyboxd);
}
static int
parse_subpacket_list(char *list)
{
char *tok;
byte subpackets[128],i;
int count=0;
if(!list)
{
/* No arguments means all subpackets */
memset(subpackets+1,1,sizeof(subpackets)-1);
count=127;
}
else
{
memset(subpackets,0,sizeof(subpackets));
/* Merge with earlier copy */
if(opt.show_subpackets)
{
byte *in;
for(in=opt.show_subpackets;*in;in++)
{
if(*in>127 || *in<1)
BUG();
if(!subpackets[*in])
count++;
subpackets[*in]=1;
}
}
while((tok=strsep(&list," ,")))
{
if(!*tok)
continue;
i=atoi(tok);
if(i>127 || i<1)
return 0;
if(!subpackets[i])
count++;
subpackets[i]=1;
}
}
xfree(opt.show_subpackets);
opt.show_subpackets=xmalloc(count+1);
opt.show_subpackets[count--]=0;
for(i=1;i<128 && count>=0;i++)
if(subpackets[i])
opt.show_subpackets[count--]=i;
return 1;
}
static int
parse_list_options(char *str)
{
char *subpackets=""; /* something that isn't NULL */
struct parse_options lopts[]=
{
{"show-sig-subpackets",LIST_SHOW_SIG_SUBPACKETS,NULL,
NULL},
{"show-photos",LIST_SHOW_PHOTOS,NULL,
N_("display photo IDs during key listings")},
{"show-usage",LIST_SHOW_USAGE,NULL,
N_("show key usage information during key listings")},
{"show-policy-urls",LIST_SHOW_POLICY_URLS,NULL,
N_("show policy URLs during signature listings")},
{"show-notations",LIST_SHOW_NOTATIONS,NULL,
N_("show all notations during signature listings")},
{"show-std-notations",LIST_SHOW_STD_NOTATIONS,NULL,
N_("show IETF standard notations during signature listings")},
{"show-standard-notations",LIST_SHOW_STD_NOTATIONS,NULL,
NULL},
{"show-user-notations",LIST_SHOW_USER_NOTATIONS,NULL,
N_("show user-supplied notations during signature listings")},
{"show-x509-notations",LIST_SHOW_X509_NOTATIONS,NULL, NULL },
{"store-x509-notations",LIST_STORE_X509_NOTATIONS,NULL, NULL },
{"show-keyserver-urls",LIST_SHOW_KEYSERVER_URLS,NULL,
N_("show preferred keyserver URLs during signature listings")},
{"show-uid-validity",LIST_SHOW_UID_VALIDITY,NULL,
N_("show user ID validity during key listings")},
{"show-unusable-uids",LIST_SHOW_UNUSABLE_UIDS,NULL,
N_("show revoked and expired user IDs in key listings")},
{"show-unusable-subkeys",LIST_SHOW_UNUSABLE_SUBKEYS,NULL,
N_("show revoked and expired subkeys in key listings")},
{"show-unusable-sigs",LIST_SHOW_UNUSABLE_SIGS,NULL,
N_("show signatures with invalid algorithms during signature listings")},
{"show-keyring",LIST_SHOW_KEYRING,NULL,
N_("show the keyring name in key listings")},
{"show-sig-expire",LIST_SHOW_SIG_EXPIRE,NULL,
N_("show expiration dates during signature listings")},
{"show-pref", LIST_SHOW_PREF, NULL,
N_("show preferences")},
{"show-pref-verbose", LIST_SHOW_PREF_VERBOSE, NULL,
N_("show preferences")},
{"show-ownertrust", LIST_SHOW_OWNERTRUST, NULL,
N_("show ownertrust")},
{"show-only-fpr-mbox",LIST_SHOW_ONLY_FPR_MBOX, NULL,
NULL},
{"sort-sigs", LIST_SORT_SIGS, NULL,
NULL},
{NULL,0,NULL,NULL}
};
int i;
/* C99 allows for non-constant initializers, but we'd like to
compile everywhere, so fill in the show-sig-subpackets argument
here. Note that if the parse_options array changes, we'll have
to change the subscript here. We use a loop here in case the
list above is reordered. */
for (i=0; lopts[i].name; i++)
if (lopts[i].bit == LIST_SHOW_SIG_SUBPACKETS)
{
lopts[i].value = &subpackets;
break;
}
if(parse_options(str,&opt.list_options,lopts,1))
{
if(opt.list_options&LIST_SHOW_SIG_SUBPACKETS)
{
/* Unset so users can pass multiple lists in. */
opt.list_options&=~LIST_SHOW_SIG_SUBPACKETS;
if(!parse_subpacket_list(subpackets))
return 0;
}
else if(subpackets==NULL && opt.show_subpackets)
{
/* User did 'no-show-subpackets' */
xfree(opt.show_subpackets);
opt.show_subpackets=NULL;
}
return 1;
}
else
return 0;
}
/* Collapses argc/argv into a single string that must be freed */
static char *
collapse_args(int argc,char *argv[])
{
char *str=NULL;
int i,first=1,len=0;
for(i=0;i<argc;i++)
{
len+=strlen(argv[i])+2;
str=xrealloc(str,len);
if(first)
{
str[0]='\0';
first=0;
}
else
strcat(str," ");
strcat(str,argv[i]);
}
return str;
}
#ifndef NO_TRUST_MODELS
static void
parse_trust_model(const char *model)
{
if(ascii_strcasecmp(model,"pgp")==0)
opt.trust_model=TM_PGP;
else if(ascii_strcasecmp(model,"classic")==0)
opt.trust_model=TM_CLASSIC;
else if(ascii_strcasecmp(model,"always")==0)
opt.trust_model=TM_ALWAYS;
else if(ascii_strcasecmp(model,"direct")==0)
opt.trust_model=TM_DIRECT;
#ifdef USE_TOFU
else if(ascii_strcasecmp(model,"tofu")==0)
opt.trust_model=TM_TOFU;
else if(ascii_strcasecmp(model,"tofu+pgp")==0)
opt.trust_model=TM_TOFU_PGP;
#endif /*USE_TOFU*/
else if(ascii_strcasecmp(model,"auto")==0)
opt.trust_model=TM_AUTO;
else
log_error("unknown trust model '%s'\n",model);
}
#endif /*NO_TRUST_MODELS*/
static int
parse_tofu_policy (const char *policystr)
{
#ifdef USE_TOFU
struct { const char *keyword; int policy; } list[] = {
{ "auto", TOFU_POLICY_AUTO },
{ "good", TOFU_POLICY_GOOD },
{ "unknown", TOFU_POLICY_UNKNOWN },
{ "bad", TOFU_POLICY_BAD },
{ "ask", TOFU_POLICY_ASK }
};
int i;
if (!ascii_strcasecmp (policystr, "help"))
{
log_info (_("valid values for option '%s':\n"), "--tofu-policy");
for (i=0; i < DIM (list); i++)
log_info (" %s\n", list[i].keyword);
g10_exit (1);
}
for (i=0; i < DIM (list); i++)
if (!ascii_strcasecmp (policystr, list[i].keyword))
return list[i].policy;
#endif /*USE_TOFU*/
log_error (_("unknown TOFU policy '%s'\n"), policystr);
if (!opt.quiet)
log_info (_("(use \"help\" to list choices)\n"));
g10_exit (1);
}
static struct gnupg_compliance_option compliance_options[] =
{
{ "gnupg", oGnuPG },
{ "openpgp", oOpenPGP },
{ "rfc4880bis", oGnuPG },
{ "rfc4880", oRFC4880 },
{ "rfc2440", oRFC2440 },
{ "pgp6", oPGP7 },
{ "pgp7", oPGP7 },
{ "pgp8", oPGP8 },
{ "de-vs", oDE_VS }
};
/* Helper to set compliance related options. This is a separate
* function so that it can also be used by the --compliance option
* parser. */
static void
set_compliance_option (enum cmd_and_opt_values option)
{
switch (option)
{
case oOpenPGP:
case oRFC4880:
/* This is effectively the same as RFC2440, but with
"--enable-dsa2 --no-rfc2440-text --escape-from-lines
--require-cross-certification". */
opt.compliance = CO_RFC4880;
opt.flags.dsa2 = 1;
opt.flags.require_cross_cert = 1;
opt.rfc2440_text = 0;
opt.allow_non_selfsigned_uid = 1;
opt.allow_freeform_uid = 1;
opt.escape_from = 1;
opt.not_dash_escaped = 0;
opt.def_cipher_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1;
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
opt.flags.allow_old_cipher_algos = 1;
break;
case oRFC2440:
opt.compliance = CO_RFC2440;
opt.flags.dsa2 = 0;
opt.rfc2440_text = 1;
opt.allow_non_selfsigned_uid = 1;
opt.allow_freeform_uid = 1;
opt.escape_from = 0;
opt.not_dash_escaped = 0;
opt.def_cipher_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1;
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_digest_algo = DIGEST_ALGO_SHA1;
opt.s2k_cipher_algo = CIPHER_ALGO_3DES;
opt.flags.allow_old_cipher_algos = 1;
break;
case oPGP7: opt.compliance = CO_PGP7; break;
case oPGP8: opt.compliance = CO_PGP8; break;
case oGnuPG:
opt.compliance = CO_GNUPG;
break;
case oDE_VS:
set_compliance_option (oOpenPGP);
opt.compliance = CO_DE_VS;
/* We divert here from the backward compatible rfc4880 algos. */
opt.s2k_digest_algo = DIGEST_ALGO_SHA256;
opt.s2k_cipher_algo = CIPHER_ALGO_AES256;
break;
default:
BUG ();
}
}
static void
gen_key_forbidden (void)
{
write_status_failure ("gen-key", gpg_error (GPG_ERR_NOT_ENABLED));
log_error (_("This command is not allowed while in %s mode.\n"),
"forbid-gen-key");
}
/* This function called to initialized a new control object. It is
assumed that this object has been zeroed out before calling this
function. */
static void
gpg_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
}
/* This function is called to deinitialize a control object. It is
not deallocated. */
static void
gpg_deinit_default_ctrl (ctrl_t ctrl)
{
#ifdef USE_TOFU
tofu_closedbs (ctrl);
#endif
gpg_dirmngr_deinit_session_data (ctrl);
keydb_release (ctrl->cached_getkey_kdb);
gpg_keyboxd_deinit_session_data (ctrl);
xfree (ctrl->secret_keygrips);
ctrl->secret_keygrips = NULL;
}
int
main (int argc, char **argv)
{
gpgrt_argparse_t pargs;
IOBUF a;
int rc=0;
int orig_argc;
char **orig_argv;
const char *fname;
char *username;
int may_coredump;
strlist_t sl;
strlist_t remusr = NULL;
strlist_t locusr = NULL;
strlist_t nrings = NULL;
armor_filter_context_t *afx = NULL;
int detached_sig = 0;
char *last_configname = NULL;
const char *configname = NULL; /* NULL or points to last_configname.
* NULL also indicates that we are
* processing options from the cmdline. */
int debug_argparser = 0;
int default_keyring = 1;
int greeting = 0;
int nogreeting = 0;
char *logfile = NULL;
int use_random_seed = 1;
enum cmd_and_opt_values cmd = 0;
const char *debug_level = NULL;
#ifndef NO_TRUST_MODELS
const char *trustdb_name = NULL;
#endif /*!NO_TRUST_MODELS*/
char *def_cipher_string = NULL;
char *def_digest_string = NULL;
char *compress_algo_string = NULL;
char *cert_digest_string = NULL;
char *s2k_cipher_string = NULL;
char *s2k_digest_string = NULL;
char *pers_cipher_list = NULL;
char *pers_digest_list = NULL;
char *pers_compress_list = NULL;
int eyes_only=0;
int multifile=0;
int pwfd = -1;
int ovrseskeyfd = -1;
int fpr_maybe_cmd = 0; /* --fingerprint maybe a command. */
int any_explicit_recipient = 0;
int default_akl = 1;
int require_secmem = 0;
int got_secmem = 0;
struct assuan_malloc_hooks malloc_hooks;
ctrl_t ctrl;
static int print_dane_records;
static int allow_large_chunks;
static const char *homedirvalue;
static const char *changeuser;
#ifdef __riscos__
opt.lock_once = 1;
#endif /* __riscos__ */
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to
secmem_init() somewhere after the option parsing. */
early_system_init ();
gnupg_reopen_std (GPG_NAME);
trap_unaligned ();
gnupg_rl_initialize ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix (GPG_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
/* Use our own logging handler for Libcgrypt. */
setup_libgcrypt_logging ();
/* Put random number into secure memory */
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps();
gnupg_init_signals (0, emergency_cleanup);
dotlock_create (NULL, 0); /* Register lock file cleanup. */
/* Tell the compliance module who we are. */
gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPG);
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
opt.command_fd = -1; /* no command fd */
opt.compress_level = -1; /* defaults to standard compress level */
opt.bz2_compress_level = -1; /* defaults to standard compress level */
/* note: if you change these lines, look at oOpenPGP */
opt.def_cipher_algo = 0;
opt.def_digest_algo = 0;
opt.cert_digest_algo = 0;
opt.compress_algo = -1; /* defaults to DEFAULT_COMPRESS_ALGO */
opt.s2k_mode = 3; /* iterated+salted */
opt.s2k_count = 0; /* Auto-calibrate when needed. */
opt.s2k_cipher_algo = DEFAULT_CIPHER_ALGO;
opt.completes_needed = 1;
opt.marginals_needed = 3;
opt.max_cert_depth = 5;
opt.escape_from = 1;
opt.flags.require_cross_cert = 1;
opt.import_options = (IMPORT_REPAIR_KEYS
| IMPORT_COLLAPSE_UIDS
| IMPORT_COLLAPSE_SUBKEYS);
opt.export_options = EXPORT_ATTRIBUTES;
opt.keyserver_options.import_options = (IMPORT_REPAIR_KEYS
| IMPORT_REPAIR_PKS_SUBKEY_BUG
| IMPORT_SELF_SIGS_ONLY
| IMPORT_COLLAPSE_UIDS
| IMPORT_COLLAPSE_SUBKEYS
| IMPORT_CLEAN);
opt.keyserver_options.export_options = EXPORT_ATTRIBUTES;
opt.keyserver_options.options = 0;
opt.verify_options = (LIST_SHOW_UID_VALIDITY
| VERIFY_SHOW_POLICY_URLS
| VERIFY_SHOW_STD_NOTATIONS
| VERIFY_SHOW_KEYSERVER_URLS);
opt.list_options = (LIST_SHOW_UID_VALIDITY
| LIST_SORT_SIGS
| LIST_SHOW_USAGE);
#ifdef NO_TRUST_MODELS
opt.trust_model = TM_ALWAYS;
#else
opt.trust_model = TM_AUTO;
#endif
opt.tofu_default_policy = TOFU_POLICY_AUTO;
opt.mangle_dos_filenames = 0;
opt.min_cert_level = 2;
set_screen_dimensions ();
opt.keyid_format = KF_NONE;
opt.def_sig_expire = "0";
opt.def_cert_expire = "0";
opt.passphrase_repeat = 1;
opt.emit_version = 0;
opt.weak_digests = NULL;
opt.with_subkey_fingerprint = 1;
opt.compliance = CO_GNUPG;
/* Check special options given on the command line. */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oDebugIOLBF:
es_setvbuf (es_stdout, NULL, _IOLBF, 0);
break;
case oNoOptions:
/* Set here here because the homedir would otherwise be
* created before main option parsing starts. */
opt.no_homedir_creation = 1;
break;
case oHomedir:
homedirvalue = pargs.r.ret_str;
break;
case oChUid:
changeuser = pargs.r.ret_str;
break;
case oNoPermissionWarn:
opt.no_perm_warn = 1;
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
#ifdef HAVE_DOSISH_SYSTEM
/* FIXME: Do we still need this? No: gnupg_homedir calls
* make_filename which changes the slashed anyway. IsDBCSLeadByte still
* needed? See bug #561. */
if ( strchr (gnupg_homedir (), '\\') ) {
char *d, *buf = xmalloc (strlen (gnupg_homedir ())+1);
const char *s;
for (d=buf, s = gnupg_homedir (); *s; s++)
{
*d++ = *s == '\\'? '/': *s;
#ifdef HAVE_W32_SYSTEM
if (s[1] && IsDBCSLeadByte (*s))
*d++ = *++s;
#endif
}
*d = 0;
gnupg_set_homedir (buf);
}
#endif
/* Initialize the secure memory. */
if (!gcry_control (GCRYCTL_INIT_SECMEM, SECMEM_BUFFER_SIZE, 0))
got_secmem = 1;
#if defined(HAVE_GETUID) && defined(HAVE_GETEUID)
/* There should be no way to get to this spot while still carrying
setuid privs. Just in case, bomb out if we are. */
if ( getuid () != geteuid () )
BUG ();
#endif
maybe_setuid = 0;
/* Okay, we are now working under our real uid */
/* malloc hooks go here ... */
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Change UID and then set the homedir. */
if (changeuser && gnupg_chuid (changeuser, 0))
log_inc_errorcount (); /* Force later termination. */
gnupg_set_homedir (homedirvalue);
/* Set default options which require that malloc stuff is ready. */
additional_weak_digest ("MD5");
parse_auto_key_locate (DEFAULT_AKL_LIST);
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
/* We are re-using the struct, thus the reset flag. We OR the
- * flags so that the internal intialized flag won't be cleared. */
+ * flags so that the internal initialized flag won't be cleared. */
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER
| ARGPARSE_FLAG_USERVERS);
/* By this point we have a homedir, and cannot change it. */
check_permissions (gnupg_homedir (), 0);
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
while (gpgrt_argparser (&pargs, opts, GPG_NAME EXTSEP_S "conf" ))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
if (is_secured_filename (configname))
{
pargs.r_opt = ARGPARSE_PERMISSION_ERROR;
pargs.err = ARGPARSE_PRINT_ERROR;
}
else if (strncmp (configname, gnupg_sysconfdir (),
strlen (gnupg_sysconfdir ())))
{
/* This is not the global config file and thus we
* need to check the permissions: If the file is
* unsafe, then disable any external programs for
* keyserver calls or photo IDs. Since the
* external program to call is set in the options
* file, a unsafe options file can lead to an
* arbitrary program being run. */
if (check_permissions (configname, 1))
opt.exec_disable=1;
}
}
else
configname = NULL;
break;
/* case oOptions: */
/* case oNoOptions: */
/* We will never see these options here because
* gpgrt_argparse handles them for us. */
/* break */
case aListConfig:
case aListGcryptConfig:
case aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
/* Do not register a keyring for these commands. */
default_keyring = -1;
break;
case aCheckKeys:
case aListPackets:
case aImport:
case aFastImport:
case aSendKeys:
case aRecvKeys:
case aSearchKeys:
case aRefreshKeys:
case aFetchKeys:
case aExport:
#ifdef ENABLE_CARD_SUPPORT
case aCardStatus:
case aCardEdit:
case aChangePIN:
#endif /* ENABLE_CARD_SUPPORT*/
case aListKeys:
case aLocateKeys:
case aLocateExtKeys:
case aListSigs:
case aExportSecret:
case aExportSecretSub:
case aExportSshKey:
case aExportSecretSshKey:
case aSym:
case aClearsign:
case aGenRevoke:
case aDesigRevoke:
case aPrimegen:
case aGenRandom:
case aPrintMD:
case aPrintMDs:
case aListTrustDB:
case aCheckTrustDB:
case aUpdateTrustDB:
case aFixTrustDB:
case aListTrustPath:
case aDeArmor:
case aEnArmor:
case aSign:
case aQuickSignKey:
case aQuickLSignKey:
case aQuickRevSig:
case aSignKey:
case aLSignKey:
case aStore:
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
case aQuickAddADSK:
case aQuickRevUid:
case aQuickSetExpire:
case aQuickSetPrimaryUid:
case aQuickUpdatePref:
case aQuickSetOwnertrust:
case aExportOwnerTrust:
case aImportOwnerTrust:
case aRebuildKeydbCaches:
set_cmd (&cmd, pargs.r_opt);
break;
case aKeygen:
case aFullKeygen:
case aEditKey:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
case aDeleteKeys:
case aPasswd:
set_cmd (&cmd, pargs.r_opt);
greeting=1;
break;
case aShowKeys:
set_cmd (&cmd, pargs.r_opt);
opt.import_options |= IMPORT_SHOW;
opt.import_options |= IMPORT_DRY_RUN;
opt.import_options &= ~IMPORT_REPAIR_KEYS;
opt.list_options |= LIST_SHOW_UNUSABLE_UIDS;
opt.list_options |= LIST_SHOW_UNUSABLE_SUBKEYS;
opt.list_options |= LIST_SHOW_NOTATIONS;
opt.list_options |= LIST_SHOW_POLICY_URLS;
break;
case aDetachedSign: detached_sig = 1; set_cmd( &cmd, aSign ); break;
case aDecryptFiles: multifile=1; /* fall through */
case aDecrypt: set_cmd( &cmd, aDecrypt); break;
case aEncrFiles: multifile=1; /* fall through */
case aEncr: set_cmd( &cmd, aEncr); break;
case aVerifyFiles: multifile=1; /* fall through */
case aVerify: set_cmd( &cmd, aVerify); break;
case aServer:
set_cmd (&cmd, pargs.r_opt);
opt.batch = 1;
break;
case aTOFUPolicy:
set_cmd (&cmd, pargs.r_opt);
break;
case oArmor: opt.armor = 1; opt.no_armor=0; break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oMaxOutput: opt.max_output = pargs.r.ret_ulong; break;
case oInputSizeHint:
opt.input_size_hint = string_to_u64 (pargs.r.ret_str);
break;
case oChunkSize:
opt.chunk_size = pargs.r.ret_int;
break;
case oQuiet: opt.quiet = 1; break;
case oNoTTY: tty_no_terminal(1); break;
case oDryRun: opt.dry_run = 1; break;
case oInteractive: opt.interactive = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
opt.list_options|=LIST_SHOW_UNUSABLE_UIDS;
opt.list_options|=LIST_SHOW_UNUSABLE_SUBKEYS;
break;
case oBatch:
opt.batch = 1;
nogreeting = 1;
break;
case oUseAgent: /* Dummy. */
break;
case oNoUseAgent:
obsolete_option (configname, pargs.lineno, "no-use-agent");
break;
case oGpgAgentInfo:
obsolete_option (configname, pargs.lineno, "gpg-agent-info");
break;
case oUseKeyboxd:
opt.use_keyboxd = 1;
break;
case oReaderPort:
obsolete_scdaemon_option (configname, pargs.lineno, "reader-port");
break;
case octapiDriver:
obsolete_scdaemon_option (configname, pargs.lineno, "ctapi-driver");
break;
case opcscDriver:
obsolete_scdaemon_option (configname, pargs.lineno, "pcsc-driver");
break;
case oDisableCCID:
obsolete_scdaemon_option (configname, pargs.lineno, "disable-ccid");
break;
case oHonorHttpProxy:
obsolete_option (configname, pargs.lineno, "honor-http-proxy");
break;
case oAnswerYes: opt.answer_yes = 1; break;
case oAnswerNo: opt.answer_no = 1; break;
case oForceSignKey: opt.flags.force_sign_key = 1; break;
case oKeyring: append_to_strlist( &nrings, pargs.r.ret_str); break;
case oPrimaryKeyring:
sl = append_to_strlist (&nrings, pargs.r.ret_str);
sl->flags = KEYDB_RESOURCE_FLAG_PRIMARY;
break;
case oShowKeyring:
deprecated_warning(configname,pargs.lineno,"--show-keyring",
"--list-options ","show-keyring");
opt.list_options|=LIST_SHOW_KEYRING;
break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugIOLBF: break; /* Already set in pre-parse step. */
case oDebugSetIobufSize:
opt_set_iobuf_size = pargs.r.ret_ulong;
opt_set_iobuf_size_used = 1;
break;
case oDebugAllowLargeChunks:
allow_large_chunks = 1;
break;
case oDebugIgnoreExpiration:
opt.ignore_expiration = 1;
break;
case oCompatibilityFlags:
if (parse_compatibility_flags (pargs.r.ret_str, &opt.compat_flags,
compatibility_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oStatusFD:
set_status_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) );
break;
case oStatusFile:
set_status_fd ( open_info_file (pargs.r.ret_str, 1, 0) );
break;
case oAttributeFD:
set_attrib_fd ( translate_sys2libc_fd_int (pargs.r.ret_int, 1) );
break;
case oAttributeFile:
set_attrib_fd ( open_info_file (pargs.r.ret_str, 1, 1) );
break;
case oLoggerFD:
log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oLoggerFile:
logfile = pargs.r.ret_str;
break;
case oLogTime:
opt_log_time = 1;
break;
case oWithV5Fingerprint:
opt.with_v5_fingerprint = 1;
break;
case oWithFingerprint:
opt.with_fingerprint = 1;
opt.fingerprint++;
break;
case oWithSubkeyFingerprint:
opt.with_subkey_fingerprint = 1;
break;
case oWithoutSubkeyFingerprint:
opt.with_subkey_fingerprint = 0;
break;
case oWithICAOSpelling:
opt.with_icao_spelling = 1;
break;
case oFingerprint:
opt.fingerprint++;
fpr_maybe_cmd = 1;
break;
case oWithKeygrip:
opt.with_keygrip = 1;
break;
case oWithKeyScreening:
opt.with_key_screening = 1;
break;
case oWithSecret:
opt.with_secret = 1;
break;
case oWithWKDHash:
opt.with_wkd_hash = 1;
break;
case oWithKeyOrigin:
opt.with_key_origin = 1;
break;
case oSecretKeyring:
obsolete_option (configname, pargs.lineno, "secret-keyring");
break;
case oNoArmor: opt.no_armor=1; opt.armor=0; break;
case oNoDefKeyring:
if (default_keyring > 0)
default_keyring = 0;
break;
case oNoKeyring:
default_keyring = -1;
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
opt.list_sigs=0;
break;
case oQuickRandom:
gcry_control (GCRYCTL_ENABLE_QUICK_RANDOM, 0);
break;
case oEmitVersion: opt.emit_version++; break;
case oNoEmitVersion: opt.emit_version=0; break;
case oCompletesNeeded: opt.completes_needed = pargs.r.ret_int; break;
case oMarginalsNeeded: opt.marginals_needed = pargs.r.ret_int; break;
case oMaxCertDepth: opt.max_cert_depth = pargs.r.ret_int; break;
#ifndef NO_TRUST_MODELS
case oTrustDBName: trustdb_name = pargs.r.ret_str; break;
#endif /*!NO_TRUST_MODELS*/
case oDefaultKey:
sl = add_to_strlist (&opt.def_secret_key, pargs.r.ret_str);
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configname)
sl->flags |= PK_LIST_CONFIG;
break;
case oDefRecipient:
if( *pargs.r.ret_str )
{
xfree (opt.def_recipient);
opt.def_recipient = make_username(pargs.r.ret_str);
}
break;
case oDefRecipientSelf:
xfree(opt.def_recipient); opt.def_recipient = NULL;
opt.def_recipient_self = 1;
break;
case oNoDefRecipient:
xfree(opt.def_recipient); opt.def_recipient = NULL;
opt.def_recipient_self = 0;
break;
case oHomedir: break;
case oChUid: break; /* Command line only (see above). */
case oNoBatch: opt.batch = 0; break;
case oWithTofuInfo: opt.with_tofu_info = 1; break;
case oWithKeyData: opt.with_key_data=1; /*FALLTHRU*/
case oWithColons: opt.with_colons=':'; break;
case oWithSigCheck: opt.check_sigs = 1; /*FALLTHRU*/
case oWithSigList: opt.list_sigs = 1; break;
case oSkipVerify: opt.skip_verify=1; break;
case oSkipHiddenRecipients: opt.skip_hidden_recipients = 1; break;
case oNoSkipHiddenRecipients: opt.skip_hidden_recipients = 0; break;
case aListSecretKeys: set_cmd( &cmd, aListSecretKeys); break;
#ifndef NO_TRUST_MODELS
/* There are many programs (like mutt) that call gpg with
--always-trust so keep this option around for a long
time. */
case oAlwaysTrust: opt.trust_model=TM_ALWAYS; break;
case oTrustModel:
parse_trust_model(pargs.r.ret_str);
break;
#endif /*!NO_TRUST_MODELS*/
case oTOFUDefaultPolicy:
opt.tofu_default_policy = parse_tofu_policy (pargs.r.ret_str);
break;
case oTOFUDBFormat:
obsolete_option (configname, pargs.lineno, "tofu-db-format");
break;
case oForceOwnertrust:
log_info(_("Note: %s is not for normal use!\n"),
"--force-ownertrust");
opt.force_ownertrust=string_to_trust_value(pargs.r.ret_str);
if(opt.force_ownertrust==-1)
{
log_error("invalid ownertrust '%s'\n",pargs.r.ret_str);
opt.force_ownertrust=0;
}
break;
case oNoAutoTrustNewKey: opt.flags.no_auto_trust_new_key = 1; break;
case oCompliance:
{
int compliance = gnupg_parse_compliance_option
(pargs.r.ret_str,
compliance_options, DIM (compliance_options),
opt.quiet);
if (compliance < 0)
g10_exit (1);
set_compliance_option (compliance);
}
break;
case oOpenPGP:
case oRFC2440:
case oRFC4880:
case oPGP7:
case oPGP8:
case oGnuPG:
set_compliance_option (pargs.r_opt);
break;
case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break;
case oRequirePQCEncryption:
opt.flags.require_pqc_encryption = 1;
break;
case oRFC2440Text: opt.rfc2440_text=1; break;
case oNoRFC2440Text: opt.rfc2440_text=0; break;
case oSetFilename:
if(utf8_strings)
opt.set_filename = pargs.r.ret_str;
else
opt.set_filename = native_to_utf8(pargs.r.ret_str);
break;
case oForYourEyesOnly: eyes_only = 1; break;
case oNoForYourEyesOnly: eyes_only = 0; break;
case oSetPolicyURL:
add_policy_url(pargs.r.ret_str,0);
add_policy_url(pargs.r.ret_str,1);
break;
case oSigPolicyURL: add_policy_url(pargs.r.ret_str,0); break;
case oCertPolicyURL: add_policy_url(pargs.r.ret_str,1); break;
case oShowPolicyURL:
deprecated_warning(configname,pargs.lineno,"--show-policy-url",
"--list-options ","show-policy-urls");
deprecated_warning(configname,pargs.lineno,"--show-policy-url",
"--verify-options ","show-policy-urls");
opt.list_options|=LIST_SHOW_POLICY_URLS;
opt.verify_options|=VERIFY_SHOW_POLICY_URLS;
break;
case oNoShowPolicyURL:
deprecated_warning(configname,pargs.lineno,"--no-show-policy-url",
"--list-options ","no-show-policy-urls");
deprecated_warning(configname,pargs.lineno,"--no-show-policy-url",
"--verify-options ","no-show-policy-urls");
opt.list_options&=~LIST_SHOW_POLICY_URLS;
opt.verify_options&=~VERIFY_SHOW_POLICY_URLS;
break;
case oSigKeyserverURL: add_keyserver_url(pargs.r.ret_str,0); break;
case oUseEmbeddedFilename:
opt.flags.use_embedded_filename=1;
break;
case oNoUseEmbeddedFilename:
opt.flags.use_embedded_filename=0;
break;
case oComment:
if(pargs.r.ret_str[0])
append_to_strlist(&opt.comments,pargs.r.ret_str);
break;
case oDefaultComment:
deprecated_warning(configname,pargs.lineno,
"--default-comment","--no-comments","");
/* fall through */
case oNoComments:
free_strlist(opt.comments);
opt.comments=NULL;
break;
case oThrowKeyids: opt.throw_keyids = 1; break;
case oNoThrowKeyids: opt.throw_keyids = 0; break;
case oShowPhotos:
deprecated_warning(configname,pargs.lineno,"--show-photos",
"--list-options ","show-photos");
deprecated_warning(configname,pargs.lineno,"--show-photos",
"--verify-options ","show-photos");
opt.list_options|=LIST_SHOW_PHOTOS;
opt.verify_options|=VERIFY_SHOW_PHOTOS;
break;
case oNoShowPhotos:
deprecated_warning(configname,pargs.lineno,"--no-show-photos",
"--list-options ","no-show-photos");
deprecated_warning(configname,pargs.lineno,"--no-show-photos",
"--verify-options ","no-show-photos");
opt.list_options&=~LIST_SHOW_PHOTOS;
opt.verify_options&=~VERIFY_SHOW_PHOTOS;
break;
case oPhotoViewer: opt.photo_viewer = pargs.r.ret_str; break;
case oForceAEAD: opt.force_aead = 1; break;
case oDisableSignerUID: opt.flags.disable_signer_uid = 1; break;
case oIncludeKeyBlock: opt.flags.include_key_block = 1; break;
case oNoIncludeKeyBlock: opt.flags.include_key_block = 0; break;
case oS2KMode: opt.s2k_mode = pargs.r.ret_int; break;
case oS2KDigest: s2k_digest_string = xstrdup(pargs.r.ret_str); break;
case oS2KCipher: s2k_cipher_string = xstrdup(pargs.r.ret_str); break;
case oS2KCount:
if (pargs.r.ret_int)
opt.s2k_count = encode_s2k_iterations (pargs.r.ret_int);
else
opt.s2k_count = 0; /* Auto-calibrate when needed. */
break;
case oRecipient:
case oHiddenRecipient:
case oRecipientFile:
case oHiddenRecipientFile:
/* Store the recipient. Note that we also store the
* option as private data in the flags. This is achieved
* by shifting the option value to the left so to keep
* enough space for the flags. */
sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings );
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configname)
sl->flags |= PK_LIST_CONFIG;
if (pargs.r_opt == oHiddenRecipient
|| pargs.r_opt == oHiddenRecipientFile)
sl->flags |= PK_LIST_HIDDEN;
if (pargs.r_opt == oRecipientFile
|| pargs.r_opt == oHiddenRecipientFile)
sl->flags |= PK_LIST_FROM_FILE;
any_explicit_recipient = 1;
break;
case oEncryptTo:
case oHiddenEncryptTo:
/* Store an additional recipient. */
sl = add_to_strlist2( &remusr, pargs.r.ret_str, utf8_strings );
sl->flags = ((pargs.r_opt << PK_LIST_SHIFT) | PK_LIST_ENCRYPT_TO);
if (configname)
sl->flags |= PK_LIST_CONFIG;
if (pargs.r_opt == oHiddenEncryptTo)
sl->flags |= PK_LIST_HIDDEN;
break;
case oNoEncryptTo:
opt.no_encrypt_to = 1;
break;
case oEncryptToDefaultKey:
opt.encrypt_to_default_key = configname ? 2 : 1;
break;
case oTrySecretKey:
add_to_strlist2 (&opt.secret_keys_to_try,
pargs.r.ret_str, utf8_strings);
break;
case oMimemode: opt.mimemode = opt.textmode = 1; break;
case oTextmodeShort: opt.textmode = 2; break;
case oTextmode: opt.textmode=1; break;
case oNoTextmode: opt.textmode=opt.mimemode=0; break;
case oExpert: opt.expert = 1; break;
case oNoExpert: opt.expert = 0; break;
case oDefSigExpire:
if(*pargs.r.ret_str!='\0')
{
if(parse_expire_string(pargs.r.ret_str)==(u32)-1)
log_error(_("'%s' is not a valid signature expiration\n"),
pargs.r.ret_str);
else
opt.def_sig_expire=pargs.r.ret_str;
}
break;
case oAskSigExpire: opt.ask_sig_expire = 1; break;
case oNoAskSigExpire: opt.ask_sig_expire = 0; break;
case oDefCertExpire:
if(*pargs.r.ret_str!='\0')
{
if(parse_expire_string(pargs.r.ret_str)==(u32)-1)
log_error(_("'%s' is not a valid signature expiration\n"),
pargs.r.ret_str);
else
opt.def_cert_expire=pargs.r.ret_str;
}
break;
case oAskCertExpire: opt.ask_cert_expire = 1; break;
case oNoAskCertExpire: opt.ask_cert_expire = 0; break;
case oDefCertLevel: opt.def_cert_level=pargs.r.ret_int; break;
case oMinCertLevel: opt.min_cert_level=pargs.r.ret_int; break;
case oAskCertLevel: opt.ask_cert_level = 1; break;
case oNoAskCertLevel: opt.ask_cert_level = 0; break;
case oLocalUser: /* store the local users */
sl = add_to_strlist2( &locusr, pargs.r.ret_str, utf8_strings );
sl->flags = (pargs.r_opt << PK_LIST_SHIFT);
if (configname)
sl->flags |= PK_LIST_CONFIG;
break;
case oSender:
{
char *mbox = mailbox_from_userid (pargs.r.ret_str, 0);
if (!mbox)
log_error (_("\"%s\" is not a proper mail address\n"),
pargs.r.ret_str);
else
{
add_to_strlist (&opt.sender_list, mbox);
xfree (mbox);
}
}
break;
case oCompress:
/* this is the -z command line option */
opt.compress_level = opt.bz2_compress_level = pargs.r.ret_int;
opt.explicit_compress_option = 1;
break;
case oNoCompress:
/* --no-compress is the same as -z0 */
opt.compress_level = opt.bz2_compress_level = 0;
opt.explicit_compress_option = 1;
break;
case oCompressLevel: opt.compress_level = pargs.r.ret_int; break;
case oBZ2CompressLevel: opt.bz2_compress_level = pargs.r.ret_int; break;
case oBZ2DecompressLowmem: opt.bz2_decompress_lowmem=1; break;
case oPassphrase:
set_passphrase_from_string (pargs.r_type ? pargs.r.ret_str : "");
break;
case oPassphraseFD:
pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oPassphraseFile:
pwfd = open_info_file (pargs.r.ret_str, 0, 1);
break;
case oPassphraseRepeat:
opt.passphrase_repeat = pargs.r.ret_int;
break;
case oPinentryMode:
opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str);
if (opt.pinentry_mode == -1)
log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str);
break;
case oRequestOrigin:
opt.request_origin = parse_request_origin (pargs.r.ret_str);
if (opt.request_origin == -1)
log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str);
break;
case oCommandFD:
opt.command_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
if (! gnupg_fd_valid (opt.command_fd))
log_error ("command-fd is invalid: %s\n", strerror (errno));
break;
case oCommandFile:
opt.command_fd = open_info_file (pargs.r.ret_str, 0, 1);
break;
case oCipherAlgo:
def_cipher_string = xstrdup(pargs.r.ret_str);
break;
case oDigestAlgo:
def_digest_string = xstrdup(pargs.r.ret_str);
break;
case oCompressAlgo:
/* If it is all digits, stick a Z in front of it for
later. This is for backwards compatibility with
versions that took the compress algorithm number. */
{
char *pt=pargs.r.ret_str;
while(*pt)
{
if (!isascii (*pt) || !isdigit (*pt))
break;
pt++;
}
if(*pt=='\0')
{
compress_algo_string=xmalloc(strlen(pargs.r.ret_str)+2);
strcpy(compress_algo_string,"Z");
strcat(compress_algo_string,pargs.r.ret_str);
}
else
compress_algo_string = xstrdup(pargs.r.ret_str);
}
break;
case oCertDigestAlgo:
cert_digest_string = xstrdup(pargs.r.ret_str);
break;
case oNoSecmemWarn:
gcry_control (GCRYCTL_DISABLE_SECMEM_WARN);
break;
case oRequireSecmem: require_secmem=1; break;
case oNoRequireSecmem: require_secmem=0; break;
case oNoPermissionWarn: opt.no_perm_warn=1; break;
case oDisplayCharset:
if( set_native_charset( pargs.r.ret_str ) )
log_error(_("'%s' is not a valid character set\n"),
pargs.r.ret_str);
break;
case oNotDashEscaped: opt.not_dash_escaped = 1; break;
case oEscapeFrom: opt.escape_from = 1; break;
case oNoEscapeFrom: opt.escape_from = 0; break;
case oLockOnce: opt.lock_once = 1; break;
case oLockNever:
dotlock_disable ();
break;
case oLockMultiple:
#ifndef __riscos__
opt.lock_once = 0;
#else /* __riscos__ */
riscos_not_implemented("lock-multiple");
#endif /* __riscos__ */
break;
case oKeyServer:
{
keyserver_spec_t keyserver;
keyserver = parse_keyserver_uri (pargs.r.ret_str, 0);
if (!keyserver)
log_error (_("could not parse keyserver URL\n"));
else
{
/* We only support a single keyserver. Later ones
override earlier ones. (Since we parse the
config file first and then the command line
arguments, the command line takes
precedence.) */
if (opt.keyserver)
free_keyserver_spec (opt.keyserver);
opt.keyserver = keyserver;
}
}
break;
case oKeyServerOptions:
if(!parse_keyserver_options(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid keyserver options\n"),
configname,pargs.lineno);
else
log_error(_("invalid keyserver options\n"));
}
break;
case oImportOptions:
if(!parse_import_options(pargs.r.ret_str,&opt.import_options,1))
{
if(configname)
log_error(_("%s:%d: invalid import options\n"),
configname,pargs.lineno);
else
log_error(_("invalid import options\n"));
}
break;
case oImportFilter:
rc = parse_and_set_import_filter (pargs.r.ret_str);
if (rc)
log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
break;
case oExportOptions:
if(!parse_export_options(pargs.r.ret_str,&opt.export_options,1))
{
if(configname)
log_error(_("%s:%d: invalid export options\n"),
configname,pargs.lineno);
else
log_error(_("invalid export options\n"));
}
break;
case oExportFilter:
rc = parse_and_set_export_filter (pargs.r.ret_str);
if (rc)
log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
break;
case oListFilter:
rc = parse_and_set_list_filter (pargs.r.ret_str);
if (rc)
log_error (_("invalid filter option: %s\n"), gpg_strerror (rc));
break;
case oListOptions:
if(!parse_list_options(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid list options\n"),
configname,pargs.lineno);
else
log_error(_("invalid list options\n"));
}
break;
case oVerifyOptions:
{
struct parse_options vopts[]=
{
{"show-photos",VERIFY_SHOW_PHOTOS,NULL,
N_("display photo IDs during signature verification")},
{"show-policy-urls",VERIFY_SHOW_POLICY_URLS,NULL,
N_("show policy URLs during signature verification")},
{"show-notations",VERIFY_SHOW_NOTATIONS,NULL,
N_("show all notations during signature verification")},
{"show-std-notations",VERIFY_SHOW_STD_NOTATIONS,NULL,
N_("show IETF standard notations during signature verification")},
{"show-standard-notations",VERIFY_SHOW_STD_NOTATIONS,NULL,
NULL},
{"show-user-notations",VERIFY_SHOW_USER_NOTATIONS,NULL,
N_("show user-supplied notations during signature verification")},
{"show-keyserver-urls",VERIFY_SHOW_KEYSERVER_URLS,NULL,
N_("show preferred keyserver URLs during signature verification")},
{"show-uid-validity",VERIFY_SHOW_UID_VALIDITY,NULL,
N_("show user ID validity during signature verification")},
{"show-unusable-uids",VERIFY_SHOW_UNUSABLE_UIDS,NULL,
N_("show revoked and expired user IDs in signature verification")},
{"show-primary-uid-only",VERIFY_SHOW_PRIMARY_UID_ONLY,NULL,
N_("show only the primary user ID in signature verification")},
{NULL,0,NULL,NULL}
};
if(!parse_options(pargs.r.ret_str,&opt.verify_options,vopts,1))
{
if(configname)
log_error(_("%s:%d: invalid verify options\n"),
configname,pargs.lineno);
else
log_error(_("invalid verify options\n"));
}
}
break;
case oTempDir: opt.temp_dir=pargs.r.ret_str; break;
case oExecPath:
if(set_exec_path(pargs.r.ret_str))
log_error(_("unable to set exec-path to %s\n"),pargs.r.ret_str);
else
opt.exec_path_set=1;
break;
case oSetNotation:
add_notation_data( pargs.r.ret_str, 0 );
add_notation_data( pargs.r.ret_str, 1 );
break;
case oSigNotation: add_notation_data( pargs.r.ret_str, 0 ); break;
case oCertNotation: add_notation_data( pargs.r.ret_str, 1 ); break;
case oKnownNotation: register_known_notation (pargs.r.ret_str); break;
case oShowNotation:
deprecated_warning(configname,pargs.lineno,"--show-notation",
"--list-options ","show-notations");
deprecated_warning(configname,pargs.lineno,"--show-notation",
"--verify-options ","show-notations");
opt.list_options|=LIST_SHOW_NOTATIONS;
opt.verify_options|=VERIFY_SHOW_NOTATIONS;
break;
case oNoShowNotation:
deprecated_warning(configname,pargs.lineno,"--no-show-notation",
"--list-options ","no-show-notations");
deprecated_warning(configname,pargs.lineno,"--no-show-notation",
"--verify-options ","no-show-notations");
opt.list_options&=~LIST_SHOW_NOTATIONS;
opt.verify_options&=~VERIFY_SHOW_NOTATIONS;
break;
case oUtf8Strings: utf8_strings = 1; break;
case oNoUtf8Strings:
#ifndef HAVE_W32_SYSTEM
utf8_strings = 0;
#endif
break;
case oDisableCipherAlgo:
{
int algo = string_to_cipher_algo (pargs.r.ret_str);
gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo);
}
break;
case oDisablePubkeyAlgo:
{
int algo = gcry_pk_map_name (pargs.r.ret_str);
gcry_pk_ctl (GCRYCTL_DISABLE_ALGO, &algo, sizeof algo);
}
break;
case oNoSigCache: opt.no_sig_cache = 1; break;
case oAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid = 1; break;
case oNoAllowNonSelfsignedUID: opt.allow_non_selfsigned_uid=0; break;
case oAllowFreeformUID: opt.allow_freeform_uid = 1; break;
case oNoAllowFreeformUID: opt.allow_freeform_uid = 0; break;
case oNoLiteral: opt.no_literal = 1; break;
case oSetFilesize:
/* There are restricts on the value (e.g. < 2^32); you
* need to check the entire code to understand this. */
opt.set_filesize = pargs.r.ret_ulong;
break;
case oFastListMode: opt.fast_list_mode = 1; break;
case oFixedListMode: /* Dummy */ break;
case oLegacyListMode: opt.legacy_list_mode = 1; break;
case oPrintDANERecords: print_dane_records = 1; break;
case oListOnly: opt.list_only=1; break;
case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
case oIgnoreValidFrom: opt.ignore_valid_from = 1; break;
case oIgnoreCrcError: opt.ignore_crc_error = 1; break;
case oIgnoreMDCError: opt.ignore_mdc_error = 1; break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oAutoKeyImport: opt.flags.auto_key_import = 1; break;
case oNoAutoKeyImport: opt.flags.auto_key_import = 0; break;
case oAutoKeyRetrieve:
opt.keyserver_options.options |= KEYSERVER_AUTO_KEY_RETRIEVE;
break;
case oNoAutoKeyRetrieve:
opt.keyserver_options.options &= ~KEYSERVER_AUTO_KEY_RETRIEVE;
break;
case oShowSessionKey: opt.show_session_key = 1; break;
case oOverrideSessionKey:
opt.override_session_key = pargs.r.ret_str;
break;
case oOverrideSessionKeyFD:
ovrseskeyfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oMergeOnly:
deprecated_warning(configname,pargs.lineno,"--merge-only",
"--import-options ","merge-only");
opt.import_options|=IMPORT_MERGE_ONLY;
break;
case oAllowSecretKeyImport: /* obsolete */ break;
case oTryAllSecrets: opt.try_all_secrets = 1; break;
case oTrustedKey: register_trusted_key( pargs.r.ret_str ); break;
case oEnableSpecialFilenames:
enable_special_filenames ();
break;
case oDisableFdTranslation:
disable_translate_sys2libc_fd ();
break;
case oNoExpensiveTrustChecks: opt.no_expensive_trust_checks=1; break;
case oAutoCheckTrustDB: opt.no_auto_check_trustdb=0; break;
case oNoAutoCheckTrustDB: opt.no_auto_check_trustdb=1; break;
case oPreservePermissions: opt.preserve_permissions=1; break;
case oDefaultPreferenceList:
opt.def_preference_list = pargs.r.ret_str;
break;
case oDefaultKeyserverURL:
{
keyserver_spec_t keyserver;
keyserver = parse_keyserver_uri (pargs.r.ret_str,1 );
if (!keyserver)
log_error (_("could not parse keyserver URL\n"));
else
free_keyserver_spec (keyserver);
opt.def_keyserver_url = pargs.r.ret_str;
}
break;
case oPersonalCipherPreferences:
pers_cipher_list=pargs.r.ret_str;
break;
case oPersonalDigestPreferences:
pers_digest_list=pargs.r.ret_str;
break;
case oPersonalCompressPreferences:
pers_compress_list=pargs.r.ret_str;
break;
case oAgentProgram:
xfree (opt.agent_program);
opt.agent_program = make_filename (pargs.r.ret_str, NULL);
break;
case oKeyboxdProgram:
xfree (opt.keyboxd_program);
opt.keyboxd_program = make_filename (pargs.r.ret_str, NULL);
break;
case oDirmngrProgram:
xfree (opt.dirmngr_program);
opt.dirmngr_program = make_filename (pargs.r.ret_str, NULL);
break;
case oDisableDirmngr: opt.disable_dirmngr = 1; break;
case oWeakDigest:
additional_weak_digest(pargs.r.ret_str);
break;
case oUnwrap:
opt.unwrap_encryption = 1;
break;
case oOnlySignTextIDs:
opt.only_sign_text_ids = 1;
break;
case oDisplay:
set_opt_session_env ("DISPLAY", pargs.r.ret_str);
break;
case oTTYname:
set_opt_session_env ("GPG_TTY", pargs.r.ret_str);
break;
case oTTYtype:
set_opt_session_env ("TERM", pargs.r.ret_str);
break;
case oXauthority:
set_opt_session_env ("XAUTHORITY", pargs.r.ret_str);
break;
case oLCctype: opt.lc_ctype = pargs.r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs.r.ret_str; break;
case oGroup: add_group(pargs.r.ret_str); break;
case oUnGroup: rm_group(pargs.r.ret_str); break;
case oNoGroups:
while(opt.grouplist)
{
struct groupitem *iter=opt.grouplist;
free_strlist(iter->values);
opt.grouplist=opt.grouplist->next;
xfree(iter);
}
break;
case oMangleDosFilenames: opt.mangle_dos_filenames = 1; break;
case oNoMangleDosFilenames: opt.mangle_dos_filenames = 0; break;
case oEnableProgressFilter: opt.enable_progress_filter = 1; break;
case oMultifile: multifile=1; break;
case oKeyidFormat:
if(ascii_strcasecmp(pargs.r.ret_str,"short")==0)
opt.keyid_format=KF_SHORT;
else if(ascii_strcasecmp(pargs.r.ret_str,"long")==0)
opt.keyid_format=KF_LONG;
else if(ascii_strcasecmp(pargs.r.ret_str,"0xshort")==0)
opt.keyid_format=KF_0xSHORT;
else if(ascii_strcasecmp(pargs.r.ret_str,"0xlong")==0)
opt.keyid_format=KF_0xLONG;
else if(ascii_strcasecmp(pargs.r.ret_str,"none")==0)
opt.keyid_format = KF_NONE;
else
log_error("unknown keyid-format '%s'\n",pargs.r.ret_str);
break;
case oExitOnStatusWriteError:
opt.exit_on_status_write_error = 1;
break;
case oLimitCardInsertTries:
opt.limit_card_insert_tries = pargs.r.ret_int;
break;
case oRequireCrossCert: opt.flags.require_cross_cert=1; break;
case oNoRequireCrossCert: opt.flags.require_cross_cert=0; break;
case oAutoKeyLocate:
if (default_akl)
{
/* This is the first time --auto-key-locate is seen.
* We need to reset the default akl. */
default_akl = 0;
release_akl();
}
if(!parse_auto_key_locate(pargs.r.ret_str))
{
if(configname)
log_error(_("%s:%d: invalid auto-key-locate list\n"),
configname,pargs.lineno);
else
log_error(_("invalid auto-key-locate list\n"));
}
break;
case oNoAutoKeyLocate:
release_akl();
break;
case oKeyOrigin:
if(!parse_key_origin (pargs.r.ret_str))
log_error (_("invalid argument for option \"%.50s\"\n"),
"--key-origin");
break;
case oEnableLargeRSA:
#if SECMEM_BUFFER_SIZE >= 65536
opt.flags.large_rsa=1;
#else
if (configname)
log_info("%s:%d: WARNING: gpg not built with large secure "
"memory buffer. Ignoring enable-large-rsa\n",
configname,pargs.lineno);
else
log_info("WARNING: gpg not built with large secure "
"memory buffer. Ignoring --enable-large-rsa\n");
#endif /* SECMEM_BUFFER_SIZE >= 65536 */
break;
case oDisableLargeRSA: opt.flags.large_rsa=0;
break;
case oEnableDSA2: opt.flags.dsa2=1; break;
case oDisableDSA2: opt.flags.dsa2=0; break;
case oAllowWeakDigestAlgos:
opt.flags.allow_weak_digest_algos = 1;
break;
case oAllowWeakKeySignatures:
opt.flags.allow_weak_key_signatures = 1;
break;
case oAllowOldCipherAlgos:
opt.flags.allow_old_cipher_algos = 1;
break;
case oFakedSystemTime:
{
size_t len = strlen (pargs.r.ret_str);
int freeze = 0;
time_t faked_time;
if (len > 0 && pargs.r.ret_str[len-1] == '!')
{
freeze = 1;
pargs.r.ret_str[len-1] = '\0';
}
faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, freeze);
}
break;
case oNoAutostart: opt.autostart = 0; break;
case oNoSymkeyCache: opt.no_symkey_cache = 1; break;
case oDefaultNewKeyAlgo:
opt.def_new_key_algo = pargs.r.ret_str;
break;
case oUseOnlyOpenPGPCard:
opt.flags.use_only_openpgp_card = 1;
break;
case oFullTimestrings:
opt.flags.full_timestrings = 1;
break;
case oForbidGenKey:
mopt.forbid_gen_key = 1;
break;
case oRequireCompliance:
opt.flags.require_compliance = 1;
break;
case oAddDesigRevoker:
if (!strcmp (pargs.r.ret_str, "clear"))
FREE_STRLIST (opt.desig_revokers);
else
append_to_strlist (&opt.desig_revokers, pargs.r.ret_str);
break;
case oAssertSigner:
add_to_strlist (&opt.assert_signer_list, pargs.r.ret_str);
break;
case oAssertPubkeyAlgo:
if (!opt.assert_pubkey_algos)
opt.assert_pubkey_algos = xstrdup (pargs.r.ret_str);
else
{
char *tmp = opt.assert_pubkey_algos;
opt.assert_pubkey_algos = xstrconcat (tmp, ",",
pargs.r.ret_str, NULL);
xfree (tmp);
}
break;
case oKbxBufferSize:
keybox_set_buffersize (pargs.r.ret_ulong, 0);
break;
case oNoop: break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
{
pargs.err = ARGPARSE_PRINT_ERROR;
/* The argparse function calls a plain exit and thus
* we need to print a status here. */
write_status_failure ("option-parser",
gpg_error(GPG_ERR_GENERAL));
}
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (log_get_errorcount (0))
{
write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL));
g10_exit(2);
}
/* Set depended fingerprint options. */
if (opt.with_v5_fingerprint && !opt.with_fingerprint)
{
opt.with_fingerprint = 1;
if (!opt.fingerprint)
opt.fingerprint = 1;
}
/* Process common component options. */
if (parse_comopt (GNUPG_MODULE_NAME_GPG, debug_argparser))
{
write_status_failure ("option-parser", gpg_error(GPG_ERR_GENERAL));
g10_exit(2);
}
if (opt.use_keyboxd)
log_info ("Note: Please move option \"%s\" to \"common.conf\"\n",
"use-keyboxd");
opt.use_keyboxd = comopt.use_keyboxd; /* Override. */
if (opt.keyboxd_program)
log_info ("Note: Please move option \"%s\" to \"common.conf\"\n",
"keyboxd-program");
if (!opt.keyboxd_program && comopt.keyboxd_program)
{
opt.keyboxd_program = comopt.keyboxd_program;
comopt.keyboxd_program = NULL;
}
if (comopt.no_autostart)
opt.autostart = 0;
/* The command --gpgconf-list is pretty simple and may be called
directly after the option parsing. */
if (cmd == aGPGConfList)
{
gpgconf_list ();
g10_exit (0);
}
xfree (last_configname);
if (print_dane_records)
log_error ("invalid option \"%s\"; use \"%s\" instead\n",
"--print-dane-records",
"--export-options export-dane");
if (log_get_errorcount (0))
{
write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL));
g10_exit(2);
}
if( nogreeting )
greeting = 0;
if( greeting )
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14));
es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
if (!opt.batch)
{
const char *s;
if((s=gpgrt_strusage(25)))
log_info("%s\n",s);
if((s=gpgrt_strusage(26)))
log_info("%s\n",s);
if((s=gpgrt_strusage(27)))
log_info("%s\n",s);
}
#endif
/* Init threading which is used by some helper functions. */
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID ));
}
else if (opt_log_time)
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY
|GPGRT_LOG_WITH_TIME));
if (opt.verbose > 2)
log_info ("using character set '%s'\n", get_native_charset ());
if( may_coredump && !opt.quiet )
log_info(_("WARNING: program may create a core file!\n"));
if (eyes_only) {
if (opt.set_filename)
log_info(_("WARNING: %s overrides %s\n"),
"--for-your-eyes-only","--set-filename");
opt.set_filename="_CONSOLE";
}
if (opt.no_literal) {
log_info(_("Note: %s is not for normal use!\n"), "--no-literal");
if (opt.textmode)
log_error(_("%s not allowed with %s!\n"),
"--textmode", "--no-literal" );
if (opt.set_filename)
log_error(_("%s makes no sense with %s!\n"),
eyes_only?"--for-your-eyes-only":"--set-filename",
"--no-literal" );
}
if (opt.set_filesize)
log_info(_("Note: %s is not for normal use!\n"), "--set-filesize");
if( opt.batch )
tty_batchmode( 1 );
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
if(require_secmem && !got_secmem)
{
log_info(_("will not run with insecure memory due to %s\n"),
"--require-secmem");
write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL));
g10_exit(2);
}
set_debug (debug_level);
if (opt.verbose) /* Print the compatibility flags. */
parse_compatibility_flags (NULL, &opt.compat_flags, compatibility_flags);
gnupg_set_compliance_extra_info (opt.min_rsa_length);
if (DBG_CLOCK)
log_clock ("start");
/* Do these after the switch(), so they can override settings. */
if (PGP7)
{
/* That does not anymore work because we have no more support
for v3 signatures. */
opt.escape_from=1;
opt.ask_sig_expire=0;
}
else if(PGP8)
{
opt.escape_from=1;
}
if( def_cipher_string ) {
opt.def_cipher_algo = string_to_cipher_algo (def_cipher_string);
xfree(def_cipher_string); def_cipher_string = NULL;
if ( openpgp_cipher_test_algo (opt.def_cipher_algo) )
log_error(_("selected cipher algorithm is invalid\n"));
}
if( def_digest_string ) {
opt.def_digest_algo = string_to_digest_algo (def_digest_string);
xfree(def_digest_string); def_digest_string = NULL;
if ( openpgp_md_test_algo (opt.def_digest_algo) )
log_error(_("selected digest algorithm is invalid\n"));
}
if( compress_algo_string ) {
opt.compress_algo = string_to_compress_algo(compress_algo_string);
xfree(compress_algo_string); compress_algo_string = NULL;
if( check_compress_algo(opt.compress_algo) )
log_error(_("selected compression algorithm is invalid\n"));
}
if( cert_digest_string ) {
opt.cert_digest_algo = string_to_digest_algo (cert_digest_string);
xfree(cert_digest_string); cert_digest_string = NULL;
if (openpgp_md_test_algo(opt.cert_digest_algo))
log_error(_("selected certification digest algorithm is invalid\n"));
}
if( s2k_cipher_string ) {
opt.s2k_cipher_algo = string_to_cipher_algo (s2k_cipher_string);
xfree(s2k_cipher_string); s2k_cipher_string = NULL;
if (openpgp_cipher_test_algo (opt.s2k_cipher_algo))
log_error(_("selected cipher algorithm is invalid\n"));
}
if( s2k_digest_string ) {
opt.s2k_digest_algo = string_to_digest_algo (s2k_digest_string);
xfree(s2k_digest_string); s2k_digest_string = NULL;
if (openpgp_md_test_algo(opt.s2k_digest_algo))
log_error(_("selected digest algorithm is invalid\n"));
}
if( opt.completes_needed < 1 )
log_error(_("completes-needed must be greater than 0\n"));
if( opt.marginals_needed < 2 )
log_error(_("marginals-needed must be greater than 1\n"));
if( opt.max_cert_depth < 1 || opt.max_cert_depth > 255 )
log_error(_("max-cert-depth must be in the range from 1 to 255\n"));
if(opt.def_cert_level<0 || opt.def_cert_level>3)
log_error(_("invalid default-cert-level; must be 0, 1, 2, or 3\n"));
if( opt.min_cert_level < 1 || opt.min_cert_level > 3 )
log_error(_("invalid min-cert-level; must be 1, 2, or 3\n"));
switch( opt.s2k_mode ) {
case 0:
if (!opt.quiet)
log_info(_("Note: simple S2K mode (0) is strongly discouraged\n"));
break;
case 1: case 3: break;
default:
log_error(_("invalid S2K mode; must be 0, 1 or 3\n"));
}
/* This isn't actually needed, but does serve to error out if the
string is invalid. */
if(opt.def_preference_list &&
keygen_set_std_prefs(opt.def_preference_list,0))
log_error(_("invalid default preferences\n"));
if(pers_cipher_list &&
keygen_set_std_prefs(pers_cipher_list,PREFTYPE_SYM))
log_error(_("invalid personal cipher preferences\n"));
if(pers_digest_list &&
keygen_set_std_prefs(pers_digest_list,PREFTYPE_HASH))
log_error(_("invalid personal digest preferences\n"));
if(pers_compress_list &&
keygen_set_std_prefs(pers_compress_list,PREFTYPE_ZIP))
log_error(_("invalid personal compress preferences\n"));
/* Check chunk size. Please fix also the man page if you change
* the default. The limits are given by the specs. */
if (!opt.chunk_size)
opt.chunk_size = 22; /* Default to the suggested max of 4 MiB. */
else if (opt.chunk_size < 6)
{
opt.chunk_size = 6;
log_info (_("chunk size invalid - using %d\n"), opt.chunk_size);
}
else if (opt.chunk_size > (allow_large_chunks? 62 : 22))
{
opt.chunk_size = (allow_large_chunks? 62 : 22);
log_info (_("chunk size invalid - using %d\n"), opt.chunk_size);
}
/* We don't support all possible commands with multifile yet */
if(multifile)
{
char *cmdname;
switch(cmd)
{
case aSign:
cmdname="--sign";
break;
case aSignEncr:
cmdname="--sign --encrypt";
break;
case aClearsign:
cmdname="--clear-sign";
break;
case aDetachedSign:
cmdname="--detach-sign";
break;
case aSym:
cmdname="--symmetric";
break;
case aEncrSym:
cmdname="--symmetric --encrypt";
break;
case aStore:
cmdname="--store";
break;
default:
cmdname=NULL;
break;
}
if(cmdname)
log_error(_("%s does not yet work with %s\n"),cmdname,"--multifile");
}
if( log_get_errorcount(0) )
{
write_status_failure ("option-postprocessing",
gpg_error(GPG_ERR_GENERAL));
g10_exit (2);
}
if(opt.compress_level==0)
opt.compress_algo=COMPRESS_ALGO_NONE;
/* Check our chosen algorithms against the list of legal
algorithms. */
if(!GNUPG)
{
const char *badalg=NULL;
preftype_t badtype=PREFTYPE_NONE;
if(opt.def_cipher_algo
&& !algo_available(PREFTYPE_SYM,opt.def_cipher_algo,NULL))
{
badalg = openpgp_cipher_algo_name (opt.def_cipher_algo);
badtype = PREFTYPE_SYM;
}
else if(opt.def_digest_algo
&& !algo_available(PREFTYPE_HASH,opt.def_digest_algo,NULL))
{
badalg = gcry_md_algo_name (opt.def_digest_algo);
badtype = PREFTYPE_HASH;
}
else if(opt.cert_digest_algo
&& !algo_available(PREFTYPE_HASH,opt.cert_digest_algo,NULL))
{
badalg = gcry_md_algo_name (opt.cert_digest_algo);
badtype = PREFTYPE_HASH;
}
else if(opt.compress_algo!=-1
&& !algo_available(PREFTYPE_ZIP,opt.compress_algo,NULL))
{
badalg = compress_algo_to_string(opt.compress_algo);
badtype = PREFTYPE_ZIP;
}
if(badalg)
{
switch(badtype)
{
case PREFTYPE_SYM:
log_info (_("cipher algorithm '%s'"
" may not be used in %s mode\n"),
badalg,
gnupg_compliance_option_string (opt.compliance));
break;
case PREFTYPE_HASH:
log_info (_("digest algorithm '%s'"
" may not be used in %s mode\n"),
badalg,
gnupg_compliance_option_string (opt.compliance));
break;
case PREFTYPE_ZIP:
log_info (_("compression algorithm '%s'"
" may not be used in %s mode\n"),
badalg,
gnupg_compliance_option_string (opt.compliance));
break;
default:
BUG();
}
compliance_failure();
}
}
/* Check our chosen algorithms against the list of allowed
* algorithms in the current compliance mode, and fail hard if it
* is not. This is us being nice to the user informing her early
* that the chosen algorithms are not available. We also check
* and enforce this right before the actual operation. */
if (opt.def_cipher_algo
&& ! gnupg_cipher_is_allowed (opt.compliance,
cmd == aEncr
|| cmd == aSignEncr
|| cmd == aEncrSym
|| cmd == aSym
|| cmd == aSignSym
|| cmd == aSignEncrSym,
opt.def_cipher_algo,
GCRY_CIPHER_MODE_NONE))
log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
openpgp_cipher_algo_name (opt.def_cipher_algo),
gnupg_compliance_option_string (opt.compliance));
if (opt.def_digest_algo
&& ! gnupg_digest_is_allowed (opt.compliance,
cmd == aSign
|| cmd == aSignEncr
|| cmd == aSignEncrSym
|| cmd == aSignSym
|| cmd == aClearsign,
opt.def_digest_algo))
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (opt.def_digest_algo),
gnupg_compliance_option_string (opt.compliance));
/* Fail hard. */
if (log_get_errorcount (0))
{
write_status_failure ("option-checking", gpg_error(GPG_ERR_GENERAL));
g10_exit (2);
}
/* Set the random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL );
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
if (!gnupg_access (p, F_OK))
register_secured_file (p);
xfree(p);
}
/* If there is no command but the --fingerprint is given, default
to the --list-keys command. */
if (!cmd && fpr_maybe_cmd)
{
set_cmd (&cmd, aListKeys);
}
if( opt.verbose > 1 )
set_packet_list_mode(1);
/* Add the keyrings, but not for some special commands. We always
* need to add the keyrings if we are running under SELinux, this
* is so that the rings are added to the list of secured files.
* We do not add any keyring if --no-keyring or --use-keyboxd has
* been used. Note that keydb_add_resource may create a new
* homedir and also tries to write a common.conf to enable the use
* of the keyboxd - in this case a special error code is returned
* and use_keyboxd is then also set. */
if (!opt.use_keyboxd
&& default_keyring >= 0
&& (ALWAYS_ADD_KEYRINGS
|| (cmd != aDeArmor && cmd != aEnArmor && cmd != aGPGConfTest)))
{
gpg_error_t tmperr = 0;
if (!nrings || default_keyring > 0) /* Add default ring. */
tmperr = keydb_add_resource ("pubring" EXTSEP_S GPGEXT_GPG,
KEYDB_RESOURCE_FLAG_DEFAULT);
if (gpg_err_code (tmperr) == GPG_ERR_TRUE && opt.use_keyboxd)
; /* The keyboxd has been enabled. */
else
{
for (sl = nrings; sl; sl = sl->next )
keydb_add_resource (sl->d, sl->flags);
}
}
FREE_STRLIST(nrings);
/* In loopback mode, never ask for the password multiple times. */
if (opt.pinentry_mode == PINENTRY_MODE_LOOPBACK)
{
opt.passphrase_repeat = 0;
}
/* If no pinentry is expected shunt
* gnupg_allow_set_foregound_window to avoid useless error
* messages on Windows. */
if (opt.pinentry_mode != PINENTRY_MODE_ASK)
{
gnupg_inhibit_set_foregound_window (1);
}
if (cmd == aGPGConfTest)
g10_exit(0);
if (pwfd != -1) /* Read the passphrase now. */
read_passphrase_from_fd (pwfd);
if (ovrseskeyfd != -1 ) /* Read the sessionkey now. */
read_sessionkey_from_fd (ovrseskeyfd);
fname = argc? *argv : NULL;
if(fname && utf8_strings)
opt.flags.utf8_filename=1;
ctrl = xcalloc (1, sizeof *ctrl);
gpg_init_default_ctrl (ctrl);
#ifndef NO_TRUST_MODELS
switch (cmd)
{
case aPrimegen:
case aPrintMD:
case aPrintMDs:
case aGenRandom:
case aDeArmor:
case aEnArmor:
case aListConfig:
case aListGcryptConfig:
break;
case aFixTrustDB:
case aExportOwnerTrust:
rc = setup_trustdb (0, trustdb_name);
break;
case aListTrustDB:
rc = setup_trustdb (argc? 1:0, trustdb_name);
break;
case aKeygen:
case aFullKeygen:
case aQuickKeygen:
rc = setup_trustdb (1, trustdb_name);
break;
default:
/* If we are using TM_ALWAYS, we do not need to create the
trustdb. */
rc = setup_trustdb (opt.trust_model != TM_ALWAYS, trustdb_name);
break;
}
if (rc)
log_error (_("failed to initialize the TrustDB: %s\n"),
gpg_strerror (rc));
#endif /*!NO_TRUST_MODELS*/
switch (cmd)
{
case aStore:
case aSym:
case aSign:
case aSignSym:
case aClearsign:
if (!opt.quiet && any_explicit_recipient)
log_info (_("WARNING: recipients (-r) given "
"without using public key encryption\n"));
break;
default:
break;
}
/* Check for certain command whether we need to migrate a
secring.gpg to the gpg-agent. */
switch (cmd)
{
case aListSecretKeys:
case aSign:
case aSignEncr:
case aSignEncrSym:
case aSignSym:
case aClearsign:
case aDecrypt:
case aSignKey:
case aLSignKey:
case aEditKey:
case aPasswd:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
case aQuickKeygen:
case aQuickAddUid:
case aQuickAddKey:
case aQuickAddADSK:
case aQuickRevUid:
case aQuickSetPrimaryUid:
case aQuickUpdatePref:
case aQuickSetOwnertrust:
case aFullKeygen:
case aKeygen:
case aImport:
case aExportSecret:
case aExportSecretSub:
case aGenRevoke:
case aDesigRevoke:
case aCardEdit:
case aChangePIN:
migrate_secring (ctrl);
break;
case aListKeys:
if (opt.with_secret)
migrate_secring (ctrl);
break;
default:
break;
}
/* The command dispatcher. */
switch( cmd )
{
case aServer:
gpg_server (ctrl);
break;
case aStore: /* only store the file */
if( argc > 1 )
wrong_args("--store [filename]");
if( (rc = encrypt_store(fname)) )
{
write_status_failure ("store", rc);
log_error ("storing '%s' failed: %s\n",
print_fname_stdin(fname),gpg_strerror (rc) );
}
break;
case aSym: /* encrypt the given file only with the symmetric cipher */
if( argc > 1 )
wrong_args("--symmetric [filename]");
if( (rc = encrypt_symmetric(fname)) )
{
write_status_failure ("symencrypt", rc);
log_error (_("symmetric encryption of '%s' failed: %s\n"),
print_fname_stdin(fname),gpg_strerror (rc) );
}
break;
case aEncr: /* encrypt the given file */
if(multifile)
encrypt_crypt_files (ctrl, argc, argv, remusr);
else
{
if( argc > 1 )
wrong_args("--encrypt [filename]");
if ((rc = encrypt_crypt (ctrl, GNUPG_INVALID_FD, fname, remusr,
0, NULL, GNUPG_INVALID_FD)))
{
write_status_failure ("encrypt", rc);
log_error("%s: encryption failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
}
break;
case aEncrSym:
/* This works with PGP 8 in the sense that it acts just like a
symmetric message. It doesn't work at all with 2 or 6. It
might work with 7, but alas, I don't have a copy to test
with right now. */
if( argc > 1 )
wrong_args("--symmetric --encrypt [filename]");
else if(opt.s2k_mode==0)
log_error(_("you cannot use --symmetric --encrypt"
" with --s2k-mode 0\n"));
else if (PGP7)
log_error(_("you cannot use --symmetric --encrypt"
" in %s mode\n"),
gnupg_compliance_option_string (opt.compliance));
else
{
if ((rc = encrypt_crypt (ctrl, GNUPG_INVALID_FD, fname, remusr,
1, NULL, GNUPG_INVALID_FD)))
{
write_status_failure ("encrypt", rc);
log_error ("%s: encryption failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
}
break;
case aSign: /* sign the given file */
sl = NULL;
if( detached_sig ) { /* sign all files */
for( ; argc; argc--, argv++ )
add_to_strlist( &sl, *argv );
}
else {
if( argc > 1 )
wrong_args("--sign [filename]");
if( argc ) {
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
}
if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 0, NULL, NULL)))
{
write_status_failure ("sign", rc);
log_error ("signing failed: %s\n", gpg_strerror (rc) );
}
free_strlist(sl);
break;
case aSignEncr: /* sign and encrypt the given file */
if( argc > 1 )
wrong_args("--sign --encrypt [filename]");
if( argc ) {
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
else
sl = NULL;
if ((rc = sign_file (ctrl, sl, detached_sig, locusr, 1, remusr, NULL)))
{
write_status_failure ("sign-encrypt", rc);
log_error("%s: sign+encrypt failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
free_strlist(sl);
break;
case aSignEncrSym: /* sign and encrypt the given file */
if( argc > 1 )
wrong_args("--symmetric --sign --encrypt [filename]");
else if(opt.s2k_mode==0)
log_error(_("you cannot use --symmetric --sign --encrypt"
" with --s2k-mode 0\n"));
else if (PGP7)
log_error(_("you cannot use --symmetric --sign --encrypt"
" in %s mode\n"),
gnupg_compliance_option_string (opt.compliance));
else
{
if( argc )
{
sl = xmalloc_clear( sizeof *sl + strlen(fname));
strcpy(sl->d, fname);
}
else
sl = NULL;
if ((rc = sign_file (ctrl, sl, detached_sig, locusr,
2, remusr, NULL)))
{
write_status_failure ("sign-encrypt", rc);
log_error("%s: symmetric+sign+encrypt failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
free_strlist(sl);
}
break;
case aSignSym: /* sign and conventionally encrypt the given file */
if (argc > 1)
wrong_args("--sign --symmetric [filename]");
rc = sign_symencrypt_file (ctrl, fname, locusr);
if (rc)
{
write_status_failure ("sign-symencrypt", rc);
log_error("%s: sign+symmetric failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
break;
case aClearsign: /* make a clearsig */
if( argc > 1 )
wrong_args("--clear-sign [filename]");
if( (rc = clearsign_file (ctrl, fname, locusr, NULL)) )
{
write_status_failure ("sign", rc);
log_error("%s: clear-sign failed: %s\n",
print_fname_stdin(fname), gpg_strerror (rc) );
}
break;
case aVerify:
if (multifile)
{
if ((rc = verify_files (ctrl, argc, argv)))
log_error("verify files failed: %s\n", gpg_strerror (rc) );
}
else
{
if ((rc = verify_signatures (ctrl, argc, argv)))
log_error("verify signatures failed: %s\n", gpg_strerror (rc) );
}
if (rc)
write_status_failure ("verify", rc);
break;
case aDecrypt:
if (multifile)
decrypt_messages (ctrl, argc, argv);
else
{
if( argc > 1 )
wrong_args("--decrypt [filename]");
if( (rc = decrypt_message (ctrl, fname) ))
{
write_status_failure ("decrypt", rc);
log_error("decrypt_message failed: %s\n", gpg_strerror (rc) );
}
}
break;
case aQuickSignKey:
case aQuickLSignKey:
{
const char *fpr;
if (argc < 1)
wrong_args ("--quick-[l]sign-key fingerprint [userids]");
fpr = *argv++; argc--;
sl = NULL;
for( ; argc; argc--, argv++)
append_to_strlist2 (&sl, *argv, utf8_strings);
keyedit_quick_sign (ctrl, fpr, sl, locusr, (cmd == aQuickLSignKey));
free_strlist (sl);
}
break;
case aQuickRevSig:
{
const char *userid, *siguserid;
if (argc < 2)
wrong_args ("--quick-revoke-sig USER-ID SIG-USER-ID [userids]");
userid = *argv++; argc--;
siguserid = *argv++; argc--;
sl = NULL;
for( ; argc; argc--, argv++)
append_to_strlist2 (&sl, *argv, utf8_strings);
keyedit_quick_revsig (ctrl, userid, siguserid, sl);
free_strlist (sl);
}
break;
case aSignKey:
if( argc != 1 )
wrong_args("--sign-key user-id");
/* fall through */
case aLSignKey:
if( argc != 1 )
wrong_args("--lsign-key user-id");
/* fall through */
sl=NULL;
if(cmd==aSignKey)
append_to_strlist(&sl,"sign");
else if(cmd==aLSignKey)
append_to_strlist(&sl,"lsign");
else
BUG();
append_to_strlist( &sl, "save" );
username = make_username( fname );
keyedit_menu (ctrl, username, locusr, sl, 0, 0 );
xfree(username);
free_strlist(sl);
break;
case aEditKey: /* Edit a key signature */
if( !argc )
wrong_args("--edit-key user-id [commands]");
username = make_username( fname );
if( argc > 1 ) {
sl = NULL;
for( argc--, argv++ ; argc; argc--, argv++ )
append_to_strlist( &sl, *argv );
keyedit_menu (ctrl, username, locusr, sl, 0, 1 );
free_strlist(sl);
}
else
keyedit_menu (ctrl, username, locusr, NULL, 0, 1 );
xfree(username);
break;
case aPasswd:
if (argc != 1)
wrong_args("--change-passphrase <user-id>");
else
{
username = make_username (fname);
keyedit_passwd (ctrl, username);
xfree (username);
}
break;
case aDeleteKeys:
case aDeleteSecretKeys:
case aDeleteSecretAndPublicKeys:
sl = NULL;
/* Print a note if the user did not specify any key. */
if (!argc && !opt.quiet)
log_info (_("Note: %s\n"), gpg_strerror (GPG_ERR_NO_KEY));
/* I'm adding these in reverse order as add_to_strlist2
reverses them again, and it's easier to understand in the
proper order :) */
for( ; argc; argc-- )
add_to_strlist2( &sl, argv[argc-1], utf8_strings );
delete_keys (ctrl, sl,
cmd==aDeleteSecretKeys, cmd==aDeleteSecretAndPublicKeys);
free_strlist(sl);
break;
case aCheckKeys:
opt.check_sigs = 1; /* fall through */
case aListSigs:
opt.list_sigs = 1; /* fall through */
case aListKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
public_key_list (ctrl, sl, 0, 0);
free_strlist(sl);
break;
case aListSecretKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
secret_key_list (ctrl, sl);
free_strlist(sl);
break;
case aLocateKeys:
case aLocateExtKeys:
sl = NULL;
for (; argc; argc--, argv++)
add_to_strlist2( &sl, *argv, utf8_strings );
if (cmd == aLocateExtKeys && akl_empty_or_only_local ())
{
/* This is a kludge to let --locate-external-keys even
* work if the config file has --no-auto-key-locate. This
* better matches the expectations of the user. */
release_akl ();
parse_auto_key_locate (DEFAULT_AKL_LIST);
}
public_key_list (ctrl, sl, 1, cmd == aLocateExtKeys);
free_strlist (sl);
break;
case aQuickKeygen:
{
const char *x_algo, *x_usage, *x_expire;
if (argc < 1 || argc > 4)
wrong_args("--quick-generate-key USER-ID [ALGO [USAGE [EXPIRE]]]");
username = make_username (fname);
argv++, argc--;
x_algo = "";
x_usage = "";
x_expire = "";
if (argc)
{
x_algo = *argv++; argc--;
if (argc)
{
x_usage = *argv++; argc--;
if (argc)
{
x_expire = *argv++; argc--;
}
}
}
if (mopt.forbid_gen_key)
gen_key_forbidden ();
else
quick_generate_keypair (ctrl, username, x_algo, x_usage, x_expire);
xfree (username);
}
break;
case aKeygen: /* generate a key */
if (mopt.forbid_gen_key)
gen_key_forbidden ();
else if( opt.batch )
{
if( argc > 1 )
wrong_args("--generate-key [parameterfile]");
generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
}
else
{
if (opt.command_fd != -1 && argc)
{
if( argc > 1 )
wrong_args("--generate-key [parameterfile]");
opt.batch = 1;
generate_keypair (ctrl, 0, argc? *argv : NULL, NULL, 0);
}
else if (argc)
wrong_args ("--generate-key");
else
generate_keypair (ctrl, 0, NULL, NULL, 0);
}
break;
case aFullKeygen: /* Generate a key with all options. */
if (mopt.forbid_gen_key)
gen_key_forbidden ();
else if (opt.batch)
{
if (argc > 1)
wrong_args ("--full-generate-key [parameterfile]");
generate_keypair (ctrl, 1, argc? *argv : NULL, NULL, 0);
}
else
{
if (argc)
wrong_args("--full-generate-key");
generate_keypair (ctrl, 1, NULL, NULL, 0);
}
break;
case aQuickAddUid:
{
const char *uid, *newuid;
if (argc != 2)
wrong_args ("--quick-add-uid USER-ID NEW-USER-ID");
uid = *argv++; argc--;
newuid = *argv++; argc--;
keyedit_quick_adduid (ctrl, uid, newuid);
}
break;
case aQuickAddKey:
{
const char *x_fpr, *x_algo, *x_usage, *x_expire;
if (argc < 1 || argc > 4)
wrong_args ("--quick-add-key FINGERPRINT [ALGO [USAGE [EXPIRE]]]");
x_fpr = *argv++; argc--;
x_algo = "";
x_usage = "";
x_expire = "";
if (argc)
{
x_algo = *argv++; argc--;
if (argc)
{
x_usage = *argv++; argc--;
if (argc)
{
x_expire = *argv++; argc--;
}
}
}
if (mopt.forbid_gen_key)
gen_key_forbidden ();
else
keyedit_quick_addkey (ctrl, x_fpr, x_algo, x_usage, x_expire);
}
break;
case aQuickAddADSK:
{
if (argc != 2)
wrong_args ("--quick-add-adsk FINGERPRINT ADSK-FINGERPRINT");
if (mopt.forbid_gen_key)
gen_key_forbidden ();
else
keyedit_quick_addadsk (ctrl, argv[0], argv[1]);
}
break;
case aQuickRevUid:
{
const char *uid, *uidtorev;
if (argc != 2)
wrong_args ("--quick-revoke-uid USER-ID USER-ID-TO-REVOKE");
uid = *argv++; argc--;
uidtorev = *argv++; argc--;
keyedit_quick_revuid (ctrl, uid, uidtorev);
}
break;
case aQuickSetExpire:
{
const char *x_fpr, *x_expire;
if (argc < 2)
wrong_args ("--quick-set-exipre FINGERPRINT EXPIRE [SUBKEY-FPRS]");
x_fpr = *argv++; argc--;
x_expire = *argv++; argc--;
keyedit_quick_set_expire (ctrl, x_fpr, x_expire, argv);
}
break;
case aQuickSetPrimaryUid:
{
const char *uid, *primaryuid;
if (argc != 2)
wrong_args ("--quick-set-primary-uid USER-ID PRIMARY-USER-ID");
uid = *argv++; argc--;
primaryuid = *argv++; argc--;
keyedit_quick_set_primary (ctrl, uid, primaryuid);
}
break;
case aQuickUpdatePref:
{
if (argc != 1)
wrong_args ("--quick-update-pref USER-ID");
keyedit_quick_update_pref (ctrl, *argv);
}
break;
case aQuickSetOwnertrust:
{
if (argc != 2)
wrong_args ("--quick-set-ownertrust USER-ID"
" [enable|disable|full|...]");
keyedit_quick_set_ownertrust (ctrl, argv[0], argv[1]);
}
break;
case aFastImport:
opt.import_options |= IMPORT_FAST; /* fall through */
case aImport:
case aShowKeys:
import_keys (ctrl, argc? argv:NULL, argc, NULL,
opt.import_options, opt.key_origin, opt.key_origin_url);
break;
/* TODO: There are a number of command that use this same
"make strlist, call function, report error, free strlist"
pattern. Join them together here and avoid all that
duplicated code. */
case aExport:
case aSendKeys:
case aRecvKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
if( cmd == aSendKeys )
rc = keyserver_export (ctrl, sl );
else if( cmd == aRecvKeys )
rc = keyserver_import (ctrl, sl );
else
{
export_stats_t stats = export_new_stats ();
rc = export_pubkeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
if(rc)
{
if(cmd==aSendKeys)
{
write_status_failure ("send-keys", rc);
log_error(_("keyserver send failed: %s\n"),gpg_strerror (rc));
}
else if(cmd==aRecvKeys)
{
write_status_failure ("recv-keys", rc);
log_error (_("keyserver receive failed: %s\n"),
gpg_strerror (rc));
}
else
{
write_status_failure ("export", rc);
log_error (_("key export failed: %s\n"), gpg_strerror (rc));
}
}
free_strlist(sl);
break;
case aExportSshKey:
if (argc != 1)
wrong_args ("--export-ssh-key <user-id>");
rc = export_ssh_key (ctrl, argv[0]);
if (rc)
{
write_status_failure ("export-ssh-key", rc);
log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc));
}
break;
case aExportSecretSshKey:
if (argc != 1)
wrong_args ("--export-secret-ssh-key <user-id>");
rc = export_secret_ssh_key (ctrl, argv[0]);
if (rc)
{
write_status_failure ("export-ssh-key", rc);
log_error (_("export as ssh key failed: %s\n"), gpg_strerror (rc));
}
break;
case aSearchKeys:
sl = NULL;
for (; argc; argc--, argv++)
append_to_strlist2 (&sl, *argv, utf8_strings);
rc = keyserver_search (ctrl, sl);
if (rc)
{
write_status_failure ("search-keys", rc);
log_error (_("keyserver search failed: %s\n"), gpg_strerror (rc));
}
free_strlist (sl);
break;
case aRefreshKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
rc = keyserver_refresh (ctrl, sl);
if(rc)
{
write_status_failure ("refresh-keys", rc);
log_error (_("keyserver refresh failed: %s\n"),gpg_strerror (rc));
}
free_strlist(sl);
break;
case aFetchKeys:
sl = NULL;
for( ; argc; argc--, argv++ )
append_to_strlist2( &sl, *argv, utf8_strings );
rc = keyserver_fetch (ctrl, sl, opt.key_origin);
free_strlist (sl);
if(rc)
{
write_status_failure ("fetch-keys", rc);
log_error ("key fetch failed: %s\n",gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_NO_DATA)
g10_exit (1); /* In this case return 1 and not 2. */
}
break;
case aExportSecret:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
{
export_stats_t stats = export_new_stats ();
export_seckeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
free_strlist(sl);
break;
case aExportSecretSub:
sl = NULL;
for( ; argc; argc--, argv++ )
add_to_strlist2( &sl, *argv, utf8_strings );
{
export_stats_t stats = export_new_stats ();
export_secsubkeys (ctrl, sl, opt.export_options, stats);
export_print_stats (stats);
export_release_stats (stats);
}
free_strlist(sl);
break;
case aGenRevoke:
if( argc != 1 )
wrong_args("--generate-revocation user-id");
username = make_username(*argv);
gen_revoke (ctrl, username );
xfree( username );
break;
case aDesigRevoke:
if (argc != 1)
wrong_args ("--generate-designated-revocation user-id");
username = make_username (*argv);
gen_desig_revoke (ctrl, username, locusr);
xfree (username);
break;
case aDeArmor:
if( argc > 1 )
wrong_args("--dearmor [file]");
rc = dearmor_file( argc? *argv: NULL );
if( rc )
{
write_status_failure ("dearmor", rc);
log_error (_("dearmoring failed: %s\n"), gpg_strerror (rc));
}
break;
case aEnArmor:
if( argc > 1 )
wrong_args("--enarmor [file]");
rc = enarmor_file( argc? *argv: NULL );
if( rc )
{
write_status_failure ("enarmor", rc);
log_error (_("enarmoring failed: %s\n"), gpg_strerror (rc));
}
break;
case aPrimegen:
#if 0 /*FIXME*/
{ int mode = argc < 2 ? 0 : atoi(*argv);
if( mode == 1 && argc == 2 ) {
mpi_print (es_stdout,
generate_public_prime( atoi(argv[1]) ), 1);
}
else if( mode == 2 && argc == 3 ) {
mpi_print (es_stdout, generate_elg_prime(
0, atoi(argv[1]),
atoi(argv[2]), NULL,NULL ), 1);
}
else if( mode == 3 && argc == 3 ) {
MPI *factors;
mpi_print (es_stdout, generate_elg_prime(
1, atoi(argv[1]),
atoi(argv[2]), NULL,&factors ), 1);
es_putc ('\n', es_stdout);
mpi_print (es_stdout, factors[0], 1 ); /* print q */
}
else if( mode == 4 && argc == 3 ) {
MPI g = mpi_alloc(1);
mpi_print (es_stdout, generate_elg_prime(
0, atoi(argv[1]),
atoi(argv[2]), g, NULL ), 1);
es_putc ('\n', es_stdout);
mpi_print (es_stdout, g, 1 );
mpi_free (g);
}
else
wrong_args("--gen-prime mode bits [qbits] ");
es_putc ('\n', es_stdout);
}
#endif
wrong_args("--gen-prime not yet supported ");
break;
case aGenRandom:
{
int level = argc ? atoi(*argv):0;
int count = argc > 1 ? atoi(argv[1]): 0;
int endless = !count;
int hexhack = (level == 16);
if (hexhack)
level = 1;
/* Level 30 uses the same algorithm as our magic wand in
* pinentry/gpg-agent. */
if (level == 30)
{
unsigned int nbits = 150;
size_t nbytes = (nbits + 7) / 8;
void *rand;
char *generated;
rand = gcry_random_bytes_secure (nbytes, GCRY_STRONG_RANDOM);
if (!rand)
log_fatal ("failed to generate random password\n");
generated = zb32_encode (rand, nbits);
gcry_free (rand);
es_fputs (generated, es_stdout);
es_putc ('\n', es_stdout);
xfree (generated);
break;
}
if (argc < 1 || argc > 2 || level < 0 || level > 2 || count < 0)
wrong_args ("--gen-random 0|1|2|16|30 [count]");
while (endless || count)
{
byte *p;
/* We need a multiple of 3, so that in case of armored
* output we get a correct string. No linefolding is
* done, as it is best to leave this to other tools */
size_t n = !endless && count < 99? count : 99;
size_t nn;
p = gcry_random_bytes (n, level);
if (hexhack)
{
for (nn = 0; nn < n; nn++)
es_fprintf (es_stdout, "%02x", p[nn]);
}
else if (opt.armor)
{
char *tmp = make_radix64_string (p, n);
es_fputs (tmp, es_stdout);
xfree (tmp);
if (n%3 == 1)
es_putc ('=', es_stdout);
if (n%3)
es_putc ('=', es_stdout);
}
else
{
es_set_binary (es_stdout);
es_fwrite( p, n, 1, es_stdout );
}
xfree(p);
if (!endless)
count -= n;
}
if (opt.armor || hexhack)
es_putc ('\n', es_stdout);
}
break;
case aPrintMD:
if( argc < 1)
wrong_args("--print-md algo [files]");
{
int all_algos = (**argv=='*' && !(*argv)[1]);
int algo = all_algos? 0 : gcry_md_map_name (*argv);
if( !algo && !all_algos )
log_error(_("invalid hash algorithm '%s'\n"), *argv );
else {
argc--; argv++;
if( !argc )
print_mds(NULL, algo);
else {
for(; argc; argc--, argv++ )
print_mds(*argv, algo);
}
}
}
break;
case aPrintMDs: /* old option */
if( !argc )
print_mds(NULL,0);
else {
for(; argc; argc--, argv++ )
print_mds(*argv,0);
}
break;
#ifndef NO_TRUST_MODELS
case aListTrustDB:
if( !argc )
list_trustdb (ctrl, es_stdout, NULL);
else {
for( ; argc; argc--, argv++ )
list_trustdb (ctrl, es_stdout, *argv );
}
break;
case aUpdateTrustDB:
if( argc )
wrong_args("--update-trustdb");
update_trustdb (ctrl);
break;
case aCheckTrustDB:
/* Old versions allowed for arguments - ignore them */
check_trustdb (ctrl);
break;
case aFixTrustDB:
how_to_fix_the_trustdb ();
break;
case aListTrustPath:
if( !argc )
wrong_args("--list-trust-path <user-ids>");
for( ; argc; argc--, argv++ ) {
username = make_username( *argv );
list_trust_path( username );
xfree(username);
}
break;
case aExportOwnerTrust:
if( argc )
wrong_args("--export-ownertrust");
export_ownertrust (ctrl);
break;
case aImportOwnerTrust:
if( argc > 1 )
wrong_args("--import-ownertrust [file]");
import_ownertrust (ctrl, argc? *argv:NULL );
break;
#endif /*!NO_TRUST_MODELS*/
case aRebuildKeydbCaches:
if (argc)
wrong_args ("--rebuild-keydb-caches");
keydb_rebuild_caches (ctrl, 1);
break;
#ifdef ENABLE_CARD_SUPPORT
case aCardStatus:
if (argc == 0)
card_status (ctrl, es_stdout, NULL);
else if (argc == 1)
card_status (ctrl, es_stdout, *argv);
else
wrong_args ("--card-status [serialno]");
break;
case aCardEdit:
if (argc) {
sl = NULL;
for (argc--, argv++ ; argc; argc--, argv++)
append_to_strlist (&sl, *argv);
card_edit (ctrl, sl);
free_strlist (sl);
}
else
card_edit (ctrl, NULL);
break;
case aChangePIN:
if (!argc)
change_pin (0,1);
else if (argc == 1)
change_pin (atoi (*argv),1);
else
wrong_args ("--change-pin [no]");
break;
#endif /* ENABLE_CARD_SUPPORT*/
case aListConfig:
{
char *str=collapse_args(argc,argv);
list_config(str);
xfree(str);
}
break;
case aListGcryptConfig:
/* Fixme: It would be nice to integrate that with
--list-config but unfortunately there is no way yet to have
libgcrypt print it to an estream for further parsing. */
gcry_control (GCRYCTL_PRINT_CONFIG, stdout);
break;
case aTOFUPolicy:
#ifdef USE_TOFU
{
int policy;
int i;
KEYDB_HANDLE hd;
if (argc < 2)
wrong_args ("--tofu-policy POLICY KEYID [KEYID...]");
policy = parse_tofu_policy (argv[0]);
hd = keydb_new (ctrl);
if (! hd)
{
write_status_failure ("tofu-driver", gpg_error(GPG_ERR_GENERAL));
g10_exit (1);
}
tofu_begin_batch_update (ctrl);
for (i = 1; i < argc; i ++)
{
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
rc = classify_user_id (argv[i], &desc, 0);
if (rc)
{
log_error (_("error parsing key specification '%s': %s\n"),
argv[i], gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
if (! (desc.mode == KEYDB_SEARCH_MODE_SHORT_KID
|| desc.mode == KEYDB_SEARCH_MODE_LONG_KID
|| desc.mode == KEYDB_SEARCH_MODE_FPR
|| desc.mode == KEYDB_SEARCH_MODE_KEYGRIP))
{
log_error (_("'%s' does not appear to be a valid"
" key ID, fingerprint or keygrip\n"),
argv[i]);
write_status_failure ("tofu-driver",
gpg_error(GPG_ERR_GENERAL));
g10_exit (1);
}
rc = keydb_search_reset (hd);
if (rc)
{
/* This should not happen, thus no need to tranalate
the string. */
log_error ("keydb_search_reset failed: %s\n",
gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
rc = keydb_search (hd, &desc, 1, NULL);
if (rc)
{
log_error (_("key \"%s\" not found: %s\n"), argv[i],
gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
rc = keydb_get_keyblock (hd, &kb);
if (rc)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (rc));
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
merge_keys_and_selfsig (ctrl, kb);
if (tofu_set_policy (ctrl, kb, policy))
{
write_status_failure ("tofu-driver", rc);
g10_exit (1);
}
release_kbnode (kb);
}
tofu_end_batch_update (ctrl);
keydb_release (hd);
}
#endif /*USE_TOFU*/
break;
default:
if (!opt.quiet)
log_info (_("WARNING: no command supplied."
" Trying to guess what you mean ...\n"));
/*FALLTHRU*/
case aListPackets:
if( argc > 1 )
wrong_args("[filename]");
/* Issue some output for the unix newbie */
if (!fname && !opt.outfile
&& gnupg_isatty (fileno (stdin))
&& gnupg_isatty (fileno (stdout))
&& gnupg_isatty (fileno (stderr)))
log_info(_("Go ahead and type your message ...\n"));
a = iobuf_open(fname);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if( !a )
log_error(_("can't open '%s'\n"), print_fname_stdin(fname));
else {
if( !opt.no_armor ) {
if( use_armor_filter( a ) ) {
afx = new_armor_context ();
push_armor_filter (afx, a);
}
}
if( cmd == aListPackets ) {
opt.list_packets=1;
set_packet_list_mode(1);
}
rc = proc_packets (ctrl, NULL, a );
if( rc )
{
write_status_failure ("-", rc);
log_error ("processing message failed: %s\n",
gpg_strerror (rc));
}
iobuf_close(a);
}
break;
}
/* cleanup */
gpg_deinit_default_ctrl (ctrl);
xfree (ctrl);
release_armor_context (afx);
FREE_STRLIST(remusr);
FREE_STRLIST(locusr);
g10_exit(0);
return 8; /*NEVER REACHED*/
}
/* Note: This function is used by signal handlers!. */
static void
emergency_cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM );
}
void
g10_exit( int rc )
{
if (rc)
;
else if (log_get_errorcount(0))
rc = 2;
else if (g10_errors_seen)
rc = 1;
else if (opt.assert_signer_list && !assert_signer_true)
rc = 1;
else if (opt.assert_pubkey_algos && assert_pubkey_algo_false)
rc = 1;
/* If we had an error but not printed an error message, do it now.
* Note that write_status_failure will never print a second failure
* status line. */
if (rc)
write_status_failure ("gpg-exit", gpg_error (GPG_ERR_GENERAL));
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
if (DBG_CLOCK)
log_clock ("stop");
if ( (opt.debug & DBG_MEMSTAT_VALUE) )
{
keydb_dump_stats ();
sig_check_dump_stats ();
objcache_dump_stats ();
gcry_control (GCRYCTL_DUMP_MEMORY_STATS);
gcry_control (GCRYCTL_DUMP_RANDOM_STATS);
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
gnupg_block_all_signals ();
emergency_cleanup ();
exit (rc);
}
/* Pretty-print hex hashes. This assumes at least an 80-character
display, but there are a few other similar assumptions in the
display code. */
static void
print_hex (gcry_md_hd_t md, int algo, const char *fname)
{
int i,n,count,indent=0;
const byte *p;
if (fname)
indent = es_printf("%s: ",fname);
if (indent>40)
{
es_printf ("\n");
indent=0;
}
if (algo==DIGEST_ALGO_RMD160)
indent += es_printf("RMD160 = ");
else if (algo>0)
indent += es_printf("%6s = ", gcry_md_algo_name (algo));
else
algo = abs(algo);
count = indent;
p = gcry_md_read (md, algo);
n = gcry_md_get_algo_dlen (algo);
count += es_printf ("%02X",*p++);
for(i=1;i<n;i++,p++)
{
if(n==16)
{
if(count+2>79)
{
es_printf ("\n%*s",indent,indent?" ":"");
count = indent;
}
else
count += es_printf(" ");
if (!(i%8))
count += es_printf(" ");
}
else if (n==20)
{
if(!(i%2))
{
if(count+4>79)
{
es_printf ("\n%*s",indent,indent?" ":"");
count=indent;
}
else
count += es_printf(" ");
}
if (!(i%10))
count += es_printf(" ");
}
else
{
if(!(i%4))
{
if (count+8>=79)
{
es_printf ("\n%*s",indent, indent?" ":"");
count=indent;
}
else
count += es_printf(" ");
}
}
count += es_printf("%02X",*p);
}
es_printf ("\n");
}
static void
print_hashline( gcry_md_hd_t md, int algo, const char *fname )
{
int i, n;
const byte *p;
if ( fname )
{
for (p = fname; *p; p++ )
{
if ( *p <= 32 || *p > 127 || *p == ':' || *p == '%' )
es_printf ("%%%02X", *p );
else
es_putc (*p, es_stdout);
}
}
es_putc (':', es_stdout);
es_printf ("%d:", algo);
p = gcry_md_read (md, algo);
n = gcry_md_get_algo_dlen (algo);
for(i=0; i < n ; i++, p++ )
es_printf ("%02X", *p);
es_fputs (":\n", es_stdout);
}
static void
print_mds( const char *fname, int algo )
{
estream_t fp;
char buf[1024];
size_t n;
gcry_md_hd_t md;
if (!fname)
{
fp = es_stdin;
es_set_binary (fp);
}
else
{
if (is_secured_filename (fname))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = es_fopen (fname, "rb" );
}
if (!fp)
{
log_error("%s: %s\n", fname?fname:"[stdin]", strerror(errno) );
return;
}
gcry_md_open (&md, 0, 0);
if (algo)
gcry_md_enable (md, algo);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
gcry_md_enable (md, GCRY_MD_MD5);
gcry_md_enable (md, GCRY_MD_SHA1);
if (!gcry_md_test_algo (GCRY_MD_RMD160))
gcry_md_enable (md, GCRY_MD_RMD160);
if (!gcry_md_test_algo (GCRY_MD_SHA224))
gcry_md_enable (md, GCRY_MD_SHA224);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
gcry_md_enable (md, GCRY_MD_SHA256);
if (!gcry_md_test_algo (GCRY_MD_SHA384))
gcry_md_enable (md, GCRY_MD_SHA384);
if (!gcry_md_test_algo (GCRY_MD_SHA512))
gcry_md_enable (md, GCRY_MD_SHA512);
}
while ((n=es_fread (buf, 1, DIM(buf), fp)))
gcry_md_write (md, buf, n);
if (es_ferror(fp))
log_error ("%s: %s\n", fname?fname:"[stdin]", strerror(errno));
else
{
gcry_md_final (md);
if (opt.with_colons)
{
if ( algo )
print_hashline (md, algo, fname);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
print_hashline( md, GCRY_MD_MD5, fname );
print_hashline( md, GCRY_MD_SHA1, fname );
if (!gcry_md_test_algo (GCRY_MD_RMD160))
print_hashline( md, GCRY_MD_RMD160, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA224))
print_hashline (md, GCRY_MD_SHA224, fname);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
print_hashline( md, GCRY_MD_SHA256, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA384))
print_hashline ( md, GCRY_MD_SHA384, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA512))
print_hashline ( md, GCRY_MD_SHA512, fname );
}
}
else
{
if (algo)
print_hex (md, -algo, fname);
else
{
if (!gcry_md_test_algo (GCRY_MD_MD5))
print_hex (md, GCRY_MD_MD5, fname);
print_hex (md, GCRY_MD_SHA1, fname );
if (!gcry_md_test_algo (GCRY_MD_RMD160))
print_hex (md, GCRY_MD_RMD160, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA224))
print_hex (md, GCRY_MD_SHA224, fname);
if (!gcry_md_test_algo (GCRY_MD_SHA256))
print_hex (md, GCRY_MD_SHA256, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA384))
print_hex (md, GCRY_MD_SHA384, fname );
if (!gcry_md_test_algo (GCRY_MD_SHA512))
print_hex (md, GCRY_MD_SHA512, fname );
}
}
}
gcry_md_close (md);
if (fp != es_stdin)
es_fclose (fp);
}
/****************
* Check the supplied name,value string and add it to the notation
* data to be used for signatures. which==0 for sig notations, and 1
* for cert notations.
*/
static void
add_notation_data( const char *string, int which )
{
struct notation *notation;
notation=string_to_notation(string,utf8_strings);
if(notation)
{
if(which)
{
notation->next=opt.cert_notations;
opt.cert_notations=notation;
}
else
{
notation->next=opt.sig_notations;
opt.sig_notations=notation;
}
}
}
static void
add_policy_url( const char *string, int which )
{
unsigned int i,critical=0;
strlist_t sl;
if(*string=='!')
{
string++;
critical=1;
}
for(i=0;i<strlen(string);i++)
if( !isascii (string[i]) || iscntrl(string[i]))
break;
if(i==0 || i<strlen(string))
{
if(which)
log_error(_("the given certification policy URL is invalid\n"));
else
log_error(_("the given signature policy URL is invalid\n"));
}
if(which)
sl=add_to_strlist( &opt.cert_policy_url, string );
else
sl=add_to_strlist( &opt.sig_policy_url, string );
if(critical)
sl->flags |= 1;
}
static void
add_keyserver_url( const char *string, int which )
{
unsigned int i,critical=0;
strlist_t sl;
if(*string=='!')
{
string++;
critical=1;
}
for(i=0;i<strlen(string);i++)
if( !isascii (string[i]) || iscntrl(string[i]))
break;
if(i==0 || i<strlen(string))
{
if(which)
BUG();
else
log_error(_("the given preferred keyserver URL is invalid\n"));
}
if(which)
BUG();
else
sl=add_to_strlist( &opt.sig_keyserver_url, string );
if(critical)
sl->flags |= 1;
}
static void
read_sessionkey_from_fd (int fd)
{
int i, len;
char *line;
if (! gnupg_fd_valid (fd))
log_fatal ("override-session-key-fd is invalid: %s\n", strerror (errno));
for (line = NULL, i = len = 100; ; i++ )
{
if (i >= len-1 )
{
char *tmp = line;
len += 100;
line = xmalloc_secure (len);
if (tmp)
{
memcpy (line, tmp, i);
xfree (tmp);
}
else
i=0;
}
if (read (fd, line + i, 1) != 1 || line[i] == '\n')
break;
}
line[i] = 0;
log_debug ("seskey: %s\n", line);
gpgrt_annotate_leaked_object (line);
opt.override_session_key = line;
}
diff --git a/g10/import.c b/g10/import.c
index bbeeebdfe..bd13c6302 100644
--- a/g10/import.c
+++ b/g10/import.c
@@ -1,4851 +1,4851 @@
/* import.c - import a key into our key storage.
* Copyright (C) 1998-2007, 2010-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2016, 2017, 2019 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "trustdb.h"
#include "main.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "../common/recsel.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/membuf.h"
#include "../common/init.h"
#include "../common/mbox-util.h"
#include "key-check.h"
#include "key-clean.h"
struct import_stats_s
{
ulong count;
ulong no_user_id;
ulong imported;
ulong n_uids;
ulong n_sigs;
ulong n_subk;
ulong unchanged;
ulong n_revoc;
ulong secret_read;
ulong secret_imported;
ulong secret_dups;
ulong skipped_new_keys;
ulong not_imported;
ulong n_sigs_cleaned;
ulong n_uids_cleaned;
ulong v3keys; /* Number of V3 keys seen. */
};
/* Node flag to indicate that a user ID or a subkey has a
* valid self-signature. */
#define NODE_GOOD_SELFSIG 1
/* Node flag to indicate that a user ID or subkey has
* an invalid self-signature. */
#define NODE_BAD_SELFSIG 2
/* Node flag to indicate that the node shall be deleted. */
#define NODE_DELETION_MARK 4
/* A node flag used to temporary mark a node. */
#define NODE_FLAG_A 8
/* A flag used by transfer_secret_keys. */
#define NODE_TRANSFER_SECKEY 16
/* An object and a global instance to store selectors created from
* --import-filter keep-uid=EXPR.
* --import-filter drop-sig=EXPR.
*
* FIXME: We should put this into the CTRL object but that requires a
* lot more changes right now. For now we use save and restore
* function to temporary change them.
*/
/* Definition of the import filters. */
struct import_filter_s
{
recsel_expr_t keep_uid;
recsel_expr_t drop_sig;
};
/* The current instance. */
struct import_filter_s import_filter;
static int import (ctrl_t ctrl,
IOBUF inp, const char* fname, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url);
static int read_block (IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys);
static void revocation_present (ctrl_t ctrl, kbnode_t keyblock);
static gpg_error_t import_one (ctrl_t ctrl,
kbnode_t keyblock,
struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len,
unsigned int options, int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid);
static gpg_error_t import_matching_seckeys (
ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch);
static gpg_error_t import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic);
static int import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats);
static int chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
int *non_self);
static int delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock,
u32 *keyid, unsigned int options,
kbnode_t *r_otherrevsigs);
static int any_uid_left (kbnode_t keyblock);
static void remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid);
static int merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig,
kbnode_t keyblock, u32 *keyid,
u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk );
static gpg_error_t append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node,
u32 curtime, int origin, const char *url,
int *n_sigs);
static int append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs);
static int merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static int merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs);
static void
release_import_filter (import_filter_t filt)
{
recsel_release (filt->keep_uid);
filt->keep_uid = NULL;
recsel_release (filt->drop_sig);
filt->drop_sig = NULL;
}
static void
cleanup_import_globals (void)
{
release_import_filter (&import_filter);
}
int
parse_import_options(char *str,unsigned int *options,int noisy)
{
struct parse_options import_opts[]=
{
{"import-local-sigs",IMPORT_LOCAL_SIGS,NULL,
N_("import signatures that are marked as local-only")},
{"repair-pks-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,
N_("repair damage from the pks keyserver during import")},
{"keep-ownertrust", IMPORT_KEEP_OWNERTTRUST, NULL,
N_("do not clear the ownertrust values during import")},
{"fast-import",IMPORT_FAST,NULL,
N_("do not update the trustdb after import")},
{"bulk-import",IMPORT_BULK, NULL,
N_("enable bulk import mode")},
{"import-show",IMPORT_SHOW,NULL,
N_("show key during import")},
{"show-only", (IMPORT_SHOW | IMPORT_DRY_RUN), NULL,
N_("show key but do not actually import") },
{"merge-only",IMPORT_MERGE_ONLY,NULL,
N_("only accept updates to existing keys")},
{"import-clean",IMPORT_CLEAN,NULL,
N_("remove unusable parts from key after import")},
{"import-minimal",IMPORT_MINIMAL|IMPORT_CLEAN,NULL,
N_("remove as much as possible from key after import")},
{"self-sigs-only", IMPORT_SELF_SIGS_ONLY, NULL,
N_("ignore key-signatures which are not self-signatures")},
{"import-export", IMPORT_EXPORT, NULL,
N_("run import filters and export key immediately")},
{"restore", IMPORT_RESTORE, NULL,
N_("assume the GnuPG key backup format")},
{"import-restore", IMPORT_RESTORE, NULL, NULL},
{"repair-keys", IMPORT_REPAIR_KEYS, NULL,
N_("repair keys on import")},
/* New options. Right now, without description string. */
{"ignore-attributes", IMPORT_IGNORE_ATTRIBUTES, NULL, NULL},
/* Hidden options which are enabled by default and are provided
* in case of problems with the respective implementation. */
{"collapse-uids", IMPORT_COLLAPSE_UIDS, NULL, NULL},
{"collapse-subkeys", IMPORT_COLLAPSE_SUBKEYS, NULL, NULL},
/* Aliases for backward compatibility */
{"allow-local-sigs",IMPORT_LOCAL_SIGS,NULL,NULL},
{"repair-hkp-subkey-bug",IMPORT_REPAIR_PKS_SUBKEY_BUG,NULL,NULL},
/* dummy */
{"import-unusable-sigs",0,NULL,NULL},
{"import-clean-sigs",0,NULL,NULL},
{"import-clean-uids",0,NULL,NULL},
{"convert-sk-to-pk",0, NULL,NULL}, /* Not anymore needed due to
the new design. */
{NULL,0,NULL,NULL}
};
int rc;
int saved_self_sigs_only, saved_import_clean;
- /* We need to set flags indicating wether the user has set certain
+ /* We need to set flags indicating whether the user has set certain
* options or if they came from the default. */
saved_self_sigs_only = (*options & IMPORT_SELF_SIGS_ONLY);
saved_self_sigs_only &= ~IMPORT_SELF_SIGS_ONLY;
saved_import_clean = (*options & IMPORT_CLEAN);
saved_import_clean &= ~IMPORT_CLEAN;
rc = parse_options (str, options, import_opts, noisy);
if (rc && (*options & IMPORT_SELF_SIGS_ONLY))
opt.flags.expl_import_self_sigs_only = 1;
else
*options |= saved_self_sigs_only;
if (rc && (*options & IMPORT_CLEAN))
opt.flags.expl_import_clean = 1;
else
*options |= saved_import_clean;
if (rc && (*options & IMPORT_RESTORE))
{
/* Alter other options we want or don't want for restore. */
*options |= (IMPORT_LOCAL_SIGS | IMPORT_KEEP_OWNERTTRUST);
*options &= ~(IMPORT_MINIMAL | IMPORT_CLEAN
| IMPORT_REPAIR_PKS_SUBKEY_BUG
| IMPORT_MERGE_ONLY);
}
return rc;
}
/* Parse and set an import filter from string. STRING has the format
* "NAME=EXPR" with NAME being the name of the filter. Spaces before
* and after NAME are not allowed. If this function is all called
* several times all expressions for the same NAME are concatenated.
* Supported filter names are:
*
* - keep-uid :: If the expression evaluates to true for a certain
* user ID packet, that packet and all it dependencies
* will be imported. The expression may use these
* variables:
*
* - uid :: The entire user ID.
* - mbox :: The mail box part of the user ID.
* - primary :: Evaluate to true for the primary user ID.
*/
gpg_error_t
parse_and_set_import_filter (const char *string)
{
gpg_error_t err;
/* Auto register the cleanup function. */
register_mem_cleanup_func (cleanup_import_globals);
if (!strncmp (string, "keep-uid=", 9))
err = recsel_parse_expr (&import_filter.keep_uid, string+9);
else if (!strncmp (string, "drop-sig=", 9))
err = recsel_parse_expr (&import_filter.drop_sig, string+9);
else
err = gpg_error (GPG_ERR_INV_NAME);
return err;
}
/* Save the current import filters, return them, and clear the current
* filters. Returns NULL on error and sets ERRNO. */
import_filter_t
save_and_clear_import_filter (void)
{
import_filter_t filt;
filt = xtrycalloc (1, sizeof *filt);
if (!filt)
return NULL;
*filt = import_filter;
memset (&import_filter, 0, sizeof import_filter);
return filt;
}
/* Release the current import filters and restore them from NEWFILT.
* Ownership of NEWFILT is moved to this function. */
void
restore_import_filter (import_filter_t filt)
{
if (filt)
{
release_import_filter (&import_filter);
import_filter = *filt;
xfree (filt);
}
}
import_stats_t
import_new_stats_handle (void)
{
return xmalloc_clear ( sizeof (struct import_stats_s) );
}
void
import_release_stats_handle (import_stats_t p)
{
xfree (p);
}
/* Read a key from a file. Only the first key in the file is
* considered and stored at R_KEYBLOCK. FNAME is the name of the
* file.
*/
gpg_error_t
read_key_from_file_or_buffer (ctrl_t ctrl, const char *fname,
const void *buffer, size_t buflen,
kbnode_t *r_keyblock)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL;
u32 keyid[2];
int v3keys; /* Dummy */
int non_self; /* Dummy */
(void)ctrl;
*r_keyblock = NULL;
log_assert (!!fname ^ !!buffer);
if (fname)
{
inp = iobuf_open (fname);
if (!inp)
err = gpg_error_from_syserror ();
else if (is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
err = gpg_error (GPG_ERR_EPERM);
}
else
err = 0;
if (err)
{
log_error (_("can't open '%s': %s\n"),
iobuf_is_pipe_filename (fname)? "[stdin]": fname,
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_ENOENT)
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
/* Push the armor filter. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
}
else /* Read from buffer (No armor expected). */
{
inp = iobuf_temp_with_content (buffer, buflen);
}
/* Read the first non-v3 keyblock. */
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
break;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
release_kbnode (keyblock);
keyblock = NULL;
}
if (err)
{
if (gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"),
fname? (iobuf_is_pipe_filename (fname)? "[stdin]": fname)
/* */ : "[buffer]",
gpg_strerror (err));
goto leave;
}
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
if (!find_next_kbnode (keyblock, PKT_USER_ID))
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
/* We do the collapsing unconditionally although it is expected that
* clean keys are provided here. */
collapse_uids (&keyblock);
collapse_subkeys (&keyblock);
clear_kbnode_flags (keyblock);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self))
{
err = gpg_error (GPG_ERR_INV_KEYRING);
goto leave;
}
if (!delete_inv_parts (ctrl, keyblock, keyid, 0, NULL) )
{
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
leave:
if (inp)
{
iobuf_close (inp);
/* Must invalidate that ugly cache to actually close the file. */
if (fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
}
release_kbnode (keyblock);
/* FIXME: Do we need to free PENDING_PKT ? */
return err;
}
/* Import an already checked public key which was included in a
* signature and the signature verified out using this key. */
gpg_error_t
import_included_key_block (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
struct import_stats_s *stats;
import_filter_t save_filt;
int save_armor = opt.armor;
opt.armor = 0;
stats = import_new_stats_handle ();
save_filt = save_and_clear_import_filter ();
if (!save_filt)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: Should we introduce a dedicated KEYORG ? */
err = import_one (ctrl, keyblock,
stats, NULL, 0, 0, 0, 0,
NULL, NULL, KEYORG_UNKNOWN, NULL, NULL);
leave:
restore_import_filter (save_filt);
import_release_stats_handle (stats);
opt.armor = save_armor;
return err;
}
/*
* Import the public keys from the given filename. Input may be armored.
* This function rejects all keys which are not validly self signed on at
* least one userid. Only user ids which are self signed will be imported.
* Other signatures are not checked.
*
* Actually this function does a merge. It works like this:
*
* - get the keyblock
* - check self-signatures and remove all userids and their signatures
* without/invalid self-signatures.
* - reject the keyblock, if we have no valid userid.
* - See whether we have this key already in one of our pubrings.
* If not, simply add it to the default keyring.
* - Compare the key and the self-signatures of the new and the one in
* our keyring. If they are different something weird is going on;
* ask what to do.
* - See whether we have only non-self-signature on one user id; if not
* ask the user what to do.
* - compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* (consider looking at the timestamp and use the newest?)
* - Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* - Proceed with next signature.
*
* Key revocation certificates have special handling.
*/
static gpg_error_t
import_keys_internal (ctrl_t ctrl, iobuf_t inp, char **fnames, int nnames,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
int i;
gpg_error_t err = 0;
struct import_stats_s *stats = stats_handle;
if (!stats)
stats = import_new_stats_handle ();
if (inp)
{
err = import (ctrl, inp, "[stream]", stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
}
else
{
if (!fnames && !nnames)
nnames = 1; /* Ohh what a ugly hack to jump into the loop */
for (i=0; i < nnames; i++)
{
const char *fname = fnames? fnames[i] : NULL;
IOBUF inp2 = iobuf_open(fname);
if (!fname)
fname = "[stdin]";
if (inp2 && is_secured_file (iobuf_get_fd (inp2)))
{
iobuf_close (inp2);
inp2 = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp2)
log_error (_("can't open '%s': %s\n"), fname, strerror (errno));
else
{
err = import (ctrl, inp2, fname, stats, fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp2);
/* Must invalidate that ugly cache to actually close it. */
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
if (err)
log_error ("import from '%s' failed: %s\n",
fname, gpg_strerror (err) );
}
if (!fname)
break;
}
}
if (!stats_handle)
{
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN))
!= (IMPORT_SHOW | IMPORT_DRY_RUN))
import_print_stats (stats);
import_release_stats_handle (stats);
}
/* If no fast import and the trustdb is dirty (i.e. we added a key
or userID that had something other than a selfsig, a signature
that was other than a selfsig, or any revocation), then
update/check the trustdb if the user specified by setting
interactive or by not setting no-auto-check-trustdb */
if (!(options & IMPORT_FAST))
check_or_update_trustdb (ctrl);
return err;
}
void
import_keys (ctrl_t ctrl, char **fnames, int nnames,
import_stats_t stats_handle, unsigned int options,
int origin, const char *url)
{
import_keys_internal (ctrl, NULL, fnames, nnames, stats_handle,
NULL, NULL, options, NULL, NULL, origin, url);
}
gpg_error_t
import_keys_es_stream (ctrl_t ctrl, estream_t fp,
import_stats_t stats_handle,
unsigned char **fpr, size_t *fpr_len,
unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
gpg_error_t err;
iobuf_t inp;
inp = iobuf_esopen (fp, "rb", 1, 0);
if (!inp)
{
err = gpg_error_from_syserror ();
log_error ("iobuf_esopen failed: %s\n", gpg_strerror (err));
return err;
}
err = import_keys_internal (ctrl, inp, NULL, 0, stats_handle,
fpr, fpr_len, options,
screener, screener_arg, origin, url);
iobuf_close (inp);
return err;
}
static int
import (ctrl_t ctrl, IOBUF inp, const char* fname,struct import_stats_s *stats,
unsigned char **fpr,size_t *fpr_len, unsigned int options,
import_screener_t screener, void *screener_arg,
int origin, const char *url)
{
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
kbnode_t secattic = NULL; /* Kludge for PGP desktop percularity */
int rc = 0;
int v3keys;
getkey_disable_caches ();
if (!opt.no_armor) /* Armored reading is not disabled. */
{
armor_filter_context_t *afx;
afx = new_armor_context ();
afx->only_keyblocks = 1;
push_armor_filter (afx, inp);
release_armor_context (afx);
}
while (!(rc = read_block (inp, options, &pending_pkt, &keyblock, &v3keys)))
{
stats->v3keys += v3keys;
if (keyblock->pkt->pkttype == PKT_PUBLIC_KEY)
{
rc = import_one (ctrl, keyblock,
stats, fpr, fpr_len, options, 0, 0,
screener, screener_arg, origin, url, NULL);
if (secattic)
{
byte tmpfpr[MAX_FINGERPRINT_LEN];
size_t tmpfprlen;
if (!rc && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
/* Kudge for PGP desktop - see below. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key,
tmpfpr, &tmpfprlen);
rc = import_matching_seckeys (ctrl, secattic,
tmpfpr, tmpfprlen,
stats, opt.batch);
}
release_kbnode (secattic);
secattic = NULL;
}
}
else if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
release_kbnode (secattic);
secattic = NULL;
rc = import_secret_one (ctrl, keyblock, stats,
opt.batch, options, 0,
screener, screener_arg, &secattic);
keyblock = NULL; /* Ownership was transferred. */
if (secattic)
{
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
rc = 0; /* Try import after the next pubkey. */
/* The attic is a workaround for the peculiar PGP
* Desktop method of exporting a secret key: The
* exported file is the concatenation of two armored
* keyblocks; first the private one and then the public
* one. The strange thing is that the secret one has no
* binding signatures at all and thus we have not
* imported it. The attic stores that secret keys and
* we try to import it once after the very next public
* keyblock. */
}
}
else if (keyblock->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (keyblock->pkt->pkt.signature) )
{
release_kbnode (secattic);
secattic = NULL;
rc = import_revoke_cert (ctrl, keyblock, options, stats);
}
else
{
release_kbnode (secattic);
secattic = NULL;
log_info (_("skipping block of type %d\n"), keyblock->pkt->pkttype);
}
release_kbnode (keyblock);
/* fixme: we should increment the not imported counter but
this does only make sense if we keep on going despite of
errors. For now we do this only if the imported key is too
large. */
if (gpg_err_code (rc) == GPG_ERR_TOO_LARGE
&& gpg_err_source (rc) == GPG_ERR_SOURCE_KEYBOX)
{
stats->not_imported++;
}
else if (rc)
break;
if (!(++stats->count % 100) && !opt.quiet)
log_info (_("%lu keys processed so far\n"), stats->count );
if (origin == KEYORG_WKD && stats->count >= 5)
{
/* We limit the number of keys _received_ from the WKD to 5.
* In fact there should be only one key but some sites want
* to store a few expired keys there also. gpg's key
* selection will later figure out which key to use. Note
* that for WKD we always return the fingerprint of the
* first imported key. */
log_info ("import from WKD stopped after %d keys\n", 5);
break;
}
}
stats->v3keys += v3keys;
if (rc == -1)
rc = 0;
else if (rc && gpg_err_code (rc) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (rc));
release_kbnode (secattic);
/* When read_block loop was stopped by error, we have PENDING_PKT left. */
if (pending_pkt)
{
free_packet (pending_pkt, NULL);
xfree (pending_pkt);
}
return rc;
}
/* Helper to migrate secring.gpg to GnuPG 2.1. */
gpg_error_t
import_old_secring (ctrl_t ctrl, const char *fname)
{
gpg_error_t err;
iobuf_t inp;
PACKET *pending_pkt = NULL;
kbnode_t keyblock = NULL; /* Need to initialize because gcc can't
grasp the return semantics of
read_block. */
struct import_stats_s *stats;
int v3keys;
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
getkey_disable_caches();
stats = import_new_stats_handle ();
while (!(err = read_block (inp, 0, &pending_pkt, &keyblock, &v3keys)))
{
if (keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
err = import_secret_one (ctrl, keyblock, stats, 1, 0, 1,
NULL, NULL, NULL);
keyblock = NULL; /* Ownership was transferred. */
}
release_kbnode (keyblock);
if (err)
break;
}
import_release_stats_handle (stats);
if (err == -1)
err = 0;
else if (err && gpg_err_code (err) != GPG_ERR_INV_KEYRING)
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
else if (err)
log_error ("import from '%s' failed: %s\n", fname, gpg_strerror (err));
iobuf_close (inp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
return err;
}
void
import_print_stats (import_stats_t stats)
{
if (!opt.quiet)
{
log_info(_("Total number processed: %lu\n"),
stats->count + stats->v3keys);
if (stats->v3keys)
log_info(_(" skipped PGP-2 keys: %lu\n"), stats->v3keys);
if (stats->skipped_new_keys )
log_info(_(" skipped new keys: %lu\n"),
stats->skipped_new_keys );
if (stats->no_user_id )
log_info(_(" w/o user IDs: %lu\n"), stats->no_user_id );
if (stats->imported)
{
log_info(_(" imported: %lu"), stats->imported );
log_printf ("\n");
}
if (stats->unchanged )
log_info(_(" unchanged: %lu\n"), stats->unchanged );
if (stats->n_uids )
log_info(_(" new user IDs: %lu\n"), stats->n_uids );
if (stats->n_subk )
log_info(_(" new subkeys: %lu\n"), stats->n_subk );
if (stats->n_sigs )
log_info(_(" new signatures: %lu\n"), stats->n_sigs );
if (stats->n_revoc )
log_info(_(" new key revocations: %lu\n"), stats->n_revoc );
if (stats->secret_read )
log_info(_(" secret keys read: %lu\n"), stats->secret_read );
if (stats->secret_imported )
log_info(_(" secret keys imported: %lu\n"), stats->secret_imported );
if (stats->secret_dups )
log_info(_(" secret keys unchanged: %lu\n"), stats->secret_dups );
if (stats->not_imported )
log_info(_(" not imported: %lu\n"), stats->not_imported );
if (stats->n_sigs_cleaned)
log_info(_(" signatures cleaned: %lu\n"),stats->n_sigs_cleaned);
if (stats->n_uids_cleaned)
log_info(_(" user IDs cleaned: %lu\n"),stats->n_uids_cleaned);
}
if (is_status_enabled ())
{
char buf[15*20];
snprintf (buf, sizeof buf,
"%lu %lu %lu 0 %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
stats->count + stats->v3keys,
stats->no_user_id,
stats->imported,
stats->unchanged,
stats->n_uids,
stats->n_subk,
stats->n_sigs,
stats->n_revoc,
stats->secret_read,
stats->secret_imported,
stats->secret_dups,
stats->skipped_new_keys,
stats->not_imported,
stats->v3keys );
write_status_text (STATUS_IMPORT_RES, buf);
}
}
/* Return true if PKTTYPE is valid in a keyblock. */
static int
valid_keyblock_packet (int pkttype)
{
switch (pkttype)
{
case PKT_PUBLIC_KEY:
case PKT_PUBLIC_SUBKEY:
case PKT_SECRET_KEY:
case PKT_SECRET_SUBKEY:
case PKT_SIGNATURE:
case PKT_USER_ID:
case PKT_ATTRIBUTE:
case PKT_RING_TRUST:
return 1;
default:
return 0;
}
}
/* Read the next keyblock from stream A. Meta data (ring trust
* packets) are only considered if OPTIONS has the IMPORT_RESTORE flag
* set. PENDING_PKT should be initialized to NULL and not changed by
* the caller.
*
* Returns 0 for okay, -1 no more blocks, or any other errorcode. The
* integer at R_V3KEY counts the number of unsupported v3 keyblocks.
*/
static int
read_block( IOBUF a, unsigned int options,
PACKET **pending_pkt, kbnode_t *ret_root, int *r_v3keys)
{
int rc;
struct parse_packet_ctx_s parsectx;
PACKET *pkt;
kbnode_t root = NULL;
kbnode_t lastnode = NULL;
int in_cert, in_v3key, skip_sigs;
u32 keyid[2];
int got_keyid = 0;
unsigned int dropped_nonselfsigs = 0;
*r_v3keys = 0;
if (*pending_pkt)
{
root = lastnode = new_kbnode( *pending_pkt );
*pending_pkt = NULL;
log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY
|| root->pkt->pkttype == PKT_SECRET_KEY);
in_cert = 1;
keyid_from_pk (root->pkt->pkt.public_key, keyid);
got_keyid = 1;
}
else
in_cert = 0;
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
if (!(options & IMPORT_RESTORE))
parsectx.skip_meta = 1;
in_v3key = 0;
skip_sigs = 0;
while ((rc=parse_packet (&parsectx, pkt)) != -1)
{
if (rc && (gpg_err_code (rc) == GPG_ERR_LEGACY_KEY
&& (pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY)))
{
in_v3key = 1;
++*r_v3keys;
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
else if (rc ) /* (ignore errors) */
{
skip_sigs = 0;
if (gpg_err_code (rc) == GPG_ERR_UNKNOWN_PACKET)
; /* Do not show a diagnostic. */
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_USER_ID
|| pkt->pkttype == PKT_ATTRIBUTE))
{
/* This indicates a too large user id or attribute
* packet. We skip this packet and all following
* signatures. Sure, this won't allow to repair a
* garbled keyring in case one of the signatures belong
* to another user id. However, this better mitigates
* DoS using inserted user ids. */
skip_sigs = 1;
}
else if (gpg_err_code (rc) == GPG_ERR_INV_PACKET
&& (pkt->pkttype == PKT_OLD_COMMENT
|| pkt->pkttype == PKT_COMMENT))
; /* Ignore too large comment packets. */
else
{
log_error("read_block: read error: %s\n", gpg_strerror (rc) );
rc = GPG_ERR_INV_KEYRING;
goto ready;
}
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
else if ((opt.import_options & IMPORT_IGNORE_ATTRIBUTES)
&& (pkt->pkttype == PKT_USER_ID || pkt->pkttype == PKT_ATTRIBUTE)
&& pkt->pkt.user_id->attrib_data)
{
skip_sigs = 1;
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
if (skip_sigs)
{
if (pkt->pkttype == PKT_SIGNATURE)
{
free_packet (pkt, &parsectx);
init_packet (pkt);
continue;
}
skip_sigs = 0;
}
if (in_v3key && !(pkt->pkttype == PKT_PUBLIC_KEY
|| pkt->pkttype == PKT_SECRET_KEY))
{
free_packet (pkt, &parsectx);
init_packet(pkt);
continue;
}
in_v3key = 0;
if (!root && pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (pkt->pkt.signature) )
{
/* This is a revocation certificate which is handled in a
* special way. */
root = new_kbnode( pkt );
pkt = NULL;
goto ready;
}
/* Make a linked list of all packets. */
switch (pkt->pkttype)
{
case PKT_COMPRESSED:
if (check_compress_algo (pkt->pkt.compressed->algorithm))
{
rc = GPG_ERR_COMPR_ALGO;
goto ready;
}
else
{
compress_filter_context_t *cfx = xmalloc_clear( sizeof *cfx );
pkt->pkt.compressed->buf = NULL;
if (push_compress_filter2 (a, cfx,
pkt->pkt.compressed->algorithm, 1))
xfree (cfx); /* e.g. in case of compression_algo NONE. */
}
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_RING_TRUST:
/* Skip those packets unless we are in restore mode. */
if ((opt.import_options & IMPORT_RESTORE))
goto x_default;
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_SIGNATURE:
if (!in_cert)
goto x_default;
if (!(options & IMPORT_SELF_SIGS_ONLY))
goto x_default;
log_assert (got_keyid);
if (pkt->pkt.signature->keyid[0] == keyid[0]
&& pkt->pkt.signature->keyid[1] == keyid[1])
{ /* This is likely a self-signature. We import this one.
* Eventually we should use the ISSUER_FPR to compare
* self-signatures, but that will work only for v5 keys
* which are currently not even deployed.
* Note that we do not do any crypto verify here because
* that would defeat this very mitigation of DoS by
* importing a key with a huge amount of faked
* key-signatures. A verification will be done later in
* the processing anyway. Here we want a cheap an early
* way to drop non-self-signatures. */
goto x_default;
}
/* Skip this signature. */
dropped_nonselfsigs++;
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
case PKT_PUBLIC_KEY:
case PKT_SECRET_KEY:
if (!got_keyid)
{
keyid_from_pk (pkt->pkt.public_key, keyid);
got_keyid = 1;
}
if (in_cert) /* Store this packet. */
{
*pending_pkt = pkt;
pkt = NULL;
goto ready;
}
in_cert = 1;
goto x_default;
default:
x_default:
if (in_cert && valid_keyblock_packet (pkt->pkttype))
{
if (!root )
root = lastnode = new_kbnode (pkt);
else
{
lastnode->next = new_kbnode (pkt);
lastnode = lastnode->next;
}
pkt = xmalloc (sizeof *pkt);
}
else
free_packet (pkt, &parsectx);
init_packet(pkt);
break;
}
}
ready:
if (rc == -1 && root )
rc = 0;
if (rc )
release_kbnode( root );
else
*ret_root = root;
free_packet (pkt, &parsectx);
deinit_parse_packet (&parsectx);
xfree( pkt );
if (!rc && dropped_nonselfsigs && opt.verbose)
log_info ("key %s: number of dropped non-self-signatures: %u\n",
keystr (keyid), dropped_nonselfsigs);
return rc;
}
/* Walk through the subkeys on a pk to find if we have the PKS
disease: multiple subkeys with their binding sigs stripped, and the
sig for the first subkey placed after the last subkey. That is,
instead of "pk uid sig sub1 bind1 sub2 bind2 sub3 bind3" we have
"pk uid sig sub1 sub2 sub3 bind1". We can't do anything about sub2
and sub3, as they are already lost, but we can try and rescue sub1
by reordering the keyblock so that it reads "pk uid sig sub1 bind1
sub2 sub3". Returns TRUE if the keyblock was modified. */
static int
fix_pks_corruption (ctrl_t ctrl, kbnode_t keyblock)
{
int changed = 0;
int keycount = 0;
kbnode_t node;
kbnode_t last = NULL;
kbnode_t sknode=NULL;
/* First determine if we have the problem at all. Look for 2 or
more subkeys in a row, followed by a single binding sig. */
for (node=keyblock; node; last=node, node=node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
keycount++;
if(!sknode)
sknode=node;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_SUBKEY_SIG (node->pkt->pkt.signature)
&& keycount >= 2
&& !node->next)
{
/* We might have the problem, as this key has two subkeys in
a row without any intervening packets. */
/* Sanity check */
if (!last)
break;
/* Temporarily attach node to sknode. */
node->next = sknode->next;
sknode->next = node;
last->next = NULL;
/* Note we aren't checking whether this binding sig is a
selfsig. This is not necessary here as the subkey and
binding sig will be rejected later if that is the
case. */
if (check_key_signature (ctrl, keyblock,node,NULL))
{
/* Not a match, so undo the changes. */
sknode->next = node->next;
last->next = node;
node->next = NULL;
break;
}
else
{
/* Mark it good so we don't need to check it again */
sknode->flag |= NODE_GOOD_SELFSIG;
changed = 1;
break;
}
}
else
keycount = 0;
}
return changed;
}
/* Versions of GnuPG before 1.4.11 and 2.0.16 allowed to import bogus
direct key signatures. A side effect of this was that a later
import of the same good direct key signatures was not possible
because the cmp_signature check in merge_blocks considered them
equal. Although direct key signatures are now checked during
import, there might still be bogus signatures sitting in a keyring.
We need to detect and delete them before doing a merge. This
function returns the number of removed sigs. */
static int
fix_bad_direct_key_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid)
{
gpg_error_t err;
kbnode_t node;
int count = 0;
for (node = keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
break;
if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
err = check_key_signature (ctrl, keyblock, node, NULL);
if (err && gpg_err_code (err) != GPG_ERR_PUBKEY_ALGO )
{
/* If we don't know the error, we can't decide; this is
not a problem because cmp_signature can't compare the
signature either. */
log_info ("key %s: invalid direct key signature removed\n",
keystr (keyid));
delete_kbnode (node);
count++;
}
}
}
return count;
}
static void
print_import_ok (PKT_public_key *pk, unsigned int reason)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char buf[MAX_FINGERPRINT_LEN*2+30], *p;
size_t i, n;
snprintf (buf, sizeof buf, "%u ", reason);
p = buf + strlen (buf);
fingerprint_from_pk (pk, array, &n);
s = array;
for (i=0; i < n ; i++, s++, p += 2)
sprintf (p, "%02X", *s);
write_status_text (STATUS_IMPORT_OK, buf);
}
static void
print_import_check (PKT_public_key * pk, PKT_user_id * id)
{
byte hexfpr[2*MAX_FINGERPRINT_LEN+1];
u32 keyid[2];
keyid_from_pk (pk, keyid);
hexfingerprint (pk, hexfpr, sizeof hexfpr);
write_status_printf (STATUS_IMPORT_CHECK, "%08X%08X %s %s",
keyid[0], keyid[1], hexfpr, id->name);
}
static void
check_prefs_warning(PKT_public_key *pk)
{
log_info(_("WARNING: key %s contains preferences for unavailable\n"
"algorithms on these user IDs:\n"), keystr_from_pk(pk));
}
static void
check_prefs (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk;
int problem=0;
merge_keys_and_selfsig (ctrl, keyblock);
pk=keyblock->pkt->pkt.public_key;
for(node=keyblock;node;node=node->next)
{
if(node->pkt->pkttype==PKT_USER_ID
&& node->pkt->pkt.user_id->created
&& node->pkt->pkt.user_id->prefs)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
prefitem_t *prefs = uid->prefs;
char *user = utf8_to_native(uid->name,strlen(uid->name),0);
for(;prefs->type;prefs++)
{
char num[10]; /* prefs->value is a byte, so we're over
safe here */
sprintf(num,"%u",prefs->value);
if(prefs->type==PREFTYPE_SYM)
{
if (openpgp_cipher_test_algo (prefs->value))
{
const char *algo =
(openpgp_cipher_test_algo (prefs->value)
? num
: openpgp_cipher_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for cipher"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_AEAD)
{
if (openpgp_aead_test_algo (prefs->value))
{
/* FIXME: The test below is wrong. We should
* check if ...algo_name yields a "?" and
* only in that case use NUM. */
const char *algo =
(openpgp_aead_test_algo (prefs->value)
? num
: openpgp_aead_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for AEAD"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_HASH)
{
if(openpgp_md_test_algo(prefs->value))
{
const char *algo =
(gcry_md_test_algo (prefs->value)
? num
: gcry_md_algo_name (prefs->value));
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for digest"
" algorithm %s\n"), user, algo);
problem=1;
}
}
else if(prefs->type==PREFTYPE_ZIP)
{
if(check_compress_algo (prefs->value))
{
const char *algo=compress_algo_to_string(prefs->value);
if(!problem)
check_prefs_warning(pk);
log_info(_(" \"%s\": preference for compression"
" algorithm %s\n"),user,algo?algo:num);
problem=1;
}
}
}
xfree(user);
}
}
if(problem)
{
log_info(_("it is strongly suggested that you update"
" your preferences and\n"));
log_info(_("re-distribute this key to avoid potential algorithm"
" mismatch problems\n"));
if(!opt.batch)
{
strlist_t sl = NULL;
strlist_t locusr = NULL;
size_t fprlen=0;
byte fpr[MAX_FINGERPRINT_LEN], *p;
char username[(MAX_FINGERPRINT_LEN*2)+1];
unsigned int i;
p = fingerprint_from_pk (pk,fpr,&fprlen);
for(i=0;i<fprlen;i++,p++)
sprintf(username+2*i,"%02X",*p);
add_to_strlist(&locusr,username);
append_to_strlist(&sl,"updpref");
append_to_strlist(&sl,"save");
keyedit_menu (ctrl, username, locusr, sl, 1, 1 );
free_strlist(sl);
free_strlist(locusr);
}
else if(!opt.quiet)
log_info(_("you can update your preferences with:"
" gpg --edit-key %s updpref save\n"),keystr_from_pk(pk));
}
}
/* Helper for apply_*_filter in import.c and export.c and also used by
* keylist.c. */
const char *
impex_filter_getval (void *cookie, const char *propname)
{
/* FIXME: Malloc our static buffers and access them via PARM. */
struct impex_filter_parm_s *parm = cookie;
ctrl_t ctrl = parm->ctrl;
kbnode_t node = parm->node;
static char numbuf[20];
const char *result;
const char *s;
enum { scpNone = 0, scpPub, scpSub, scpUid, scpSig} scope = 0;
log_assert (ctrl && ctrl->magic == SERVER_CONTROL_MAGIC);
/* We allow a prefix delimited by a slash to limit the scope of the
* keyword. Note that "pub" also includes "sec" and "sub" includes
* "ssb". */
if ((s=strchr (propname, '/')) && s != propname)
{
size_t n = s - propname;
if (!strncmp (propname, "pub", n))
scope = scpPub;
else if (!strncmp (propname, "sub", n))
scope = scpSub;
else if (!strncmp (propname, "uid", n))
scope = scpUid;
else if (!strncmp (propname, "sig", n))
scope = scpSig;
propname = s + 1;
}
if ((node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
&& (!scope || scope == scpUid))
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (!strcmp (propname, "uid"))
result = uid->name;
else if (!strcmp (propname, "mbox"))
{
if (!uid->mbox)
{
uid->mbox = mailbox_from_userid (uid->name, 0);
}
result = uid->mbox;
}
else if (!strcmp (propname, "primary"))
{
result = uid->flags.primary? "1":"0";
}
else if (!strcmp (propname, "expired"))
{
result = uid->flags.expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = uid->flags.revoked? "1":"0";
}
else
result = NULL;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (!scope || scope == scpSig))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (!strcmp (propname, "sig_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "sig_created_d"))
{
result = dateonlystr_from_sig (sig);
}
else if (!strcmp (propname, "sig_expires"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)sig->expiredate);
result = numbuf;
}
else if (!strcmp (propname, "sig_expires_d"))
{
static char exdatestr[MK_DATESTR_SIZE];
if (sig->expiredate)
result = mk_datestr (exdatestr, sizeof exdatestr, sig->expiredate);
else
result = "";
}
else if (!strcmp (propname, "sig_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "sig_digest_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", sig->digest_algo);
result = numbuf;
}
else if (!strcmp (propname, "expired"))
{
result = sig->flags.expired? "1":"0";
}
else
result = NULL;
}
else if (((node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
&& (!scope || scope == scpPub))
|| ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
&& (!scope || scope == scpSub)))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
if (!strcmp (propname, "secret"))
{
result = (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)? "1":"0";
}
else if (!strcmp (propname, "key_algo"))
{
snprintf (numbuf, sizeof numbuf, "%d", pk->pubkey_algo);
result = numbuf;
}
else if (!strcmp (propname, "key_size"))
{
snprintf (numbuf, sizeof numbuf, "%u", nbits_from_pk (pk));
result = numbuf;
}
else if (!strcmp (propname, "algostr"))
{
pubkey_string (pk, parm->hexfpr, sizeof parm->hexfpr);
result = parm->hexfpr;
}
else if (!strcmp (propname, "key_created"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->timestamp);
result = numbuf;
}
else if (!strcmp (propname, "key_created_d"))
{
result = dateonlystr_from_pk (pk);
}
else if (!strcmp (propname, "key_expires"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->expiredate);
result = numbuf;
}
else if (!strcmp (propname, "key_expires_d"))
{
static char exdatestr[MK_DATESTR_SIZE];
if (pk->expiredate)
result = mk_datestr (exdatestr, sizeof exdatestr, pk->expiredate);
else
result = "";
}
else if (!strcmp (propname, "expired"))
{
result = pk->has_expired? "1":"0";
}
else if (!strcmp (propname, "revoked"))
{
result = pk->flags.revoked? "1":"0";
}
else if (!strcmp (propname, "disabled"))
{
result = pk_is_disabled (pk)? "1":"0";
}
else if (!strcmp (propname, "usage"))
{
snprintf (numbuf, sizeof numbuf, "%s%s%s%s%s",
(pk->pubkey_usage & PUBKEY_USAGE_ENC)?"e":"",
(pk->pubkey_usage & PUBKEY_USAGE_SIG)?"s":"",
(pk->pubkey_usage & PUBKEY_USAGE_CERT)?"c":"",
(pk->pubkey_usage & PUBKEY_USAGE_AUTH)?"a":"",
(pk->pubkey_usage & PUBKEY_USAGE_UNKNOWN)?"?":"");
result = numbuf;
}
else if (!strcmp (propname, "fpr"))
{
hexfingerprint (pk, parm->hexfpr, sizeof parm->hexfpr);
result = parm->hexfpr;
}
else if (!strcmp (propname, "origin"))
{
result = key_origin_string (pk->keyorg);
}
else if (!strcmp (propname, "lastupd"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (ulong)pk->keyupdate);
result = numbuf;
}
else if (!strcmp (propname, "url"))
{
if (pk->updateurl && *pk->updateurl)
{
/* Fixme: This might get truncated. */
mem2str (parm->hexfpr, pk->updateurl, sizeof parm->hexfpr);
result = parm->hexfpr;
}
else
result = "";
}
else
result = NULL;
}
else
result = NULL;
return result;
}
/*
* Apply the keep-uid filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_keep_uid_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
parm.node = node;
if (!recsel_select (selector, impex_filter_getval, &parm))
{
/* log_debug ("keep-uid: deleting '%s'\n", */
/* node->pkt->pkt.user_id->name); */
/* The UID packet and all following packets up to the
* next UID or a subkey. */
delete_kbnode (node);
for (; node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ;
node = node->next)
delete_kbnode (node->next);
}
/* else */
/* log_debug ("keep-uid: keeping '%s'\n", */
/* node->pkt->pkt.user_id->name); */
}
}
}
/*
* Apply the drop-sig filter to the keyblock. The deleted nodes are
* marked and thus the caller should call commit_kbnode afterwards.
* KEYBLOCK must not have any blocks marked as deleted.
*/
static void
apply_drop_sig_filter (ctrl_t ctrl, kbnode_t keyblock, recsel_expr_t selector)
{
kbnode_t node;
int active = 0;
u32 main_keyid[2];
PKT_signature *sig;
struct impex_filter_parm_s parm;
parm.ctrl = ctrl;
keyid_from_pk (keyblock->pkt->pkt.public_key, main_keyid);
/* Loop over all signatures for user id and attribute packets which
* are not self signatures. */
for (node = keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break; /* ready. */
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_ATTRIBUTE)
active = 1;
if (!active)
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
if (main_keyid[0] == sig->keyid[0] || main_keyid[1] == sig->keyid[1])
continue; /* Skip self-signatures. */
if (IS_UID_SIG(sig) || IS_UID_REV(sig))
{
parm.node = node;
if (recsel_select (selector, impex_filter_getval, &parm))
delete_kbnode (node);
}
}
}
/* Insert a key origin into a public key packet. */
static gpg_error_t
insert_key_origin_pk (PKT_public_key *pk, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* For WKD and DANE we insert origin information also for the
* key but we don't record the URL because we have have no use
* for that: An update using a keyserver has higher precedence
* and will thus update this origin info. For refresh using WKD
* or DANE we need to go via the User ID anyway. Recall that we
* are only inserting a new key. */
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we add the meta information. Note that the use of a
* fingerprint needs to be enforced by the caller of the import
* function. This is commonly triggered by verifying a modern
* signature which has an Issuer Fingerprint signature
* subpacket. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
else if (origin == KEYORG_FILE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
if (url)
{
xfree (pk->updateurl);
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/* Insert a key origin into a user id packet. */
static gpg_error_t
insert_key_origin_uid (PKT_user_id *uid, u32 curtime,
int origin, const char *url)
{
if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We insert origin information on a UID only when we received
* them via the Web Key Directory or a DANE record. The key we
* receive here from the WKD has been filtered to contain only
* the user ID as looked up in the WKD. For a DANE origin we
* this should also be the case. Thus we will see here only one
* user id. */
uid->keyorg = origin;
uid->keyupdate = curtime;
if (url)
{
xfree (uid->updateurl);
uid->updateurl = xtrystrdup (url);
if (!uid->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_KS && url)
{
/* If the key was retrieved from a keyserver using a fingerprint
* request we mark that also in the user ID. However we do not
* store the keyserver URL in the UID. A later update (merge)
* from a more trusted source will replace this info. */
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_FILE)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
else if (origin == KEYORG_URL)
{
uid->keyorg = origin;
uid->keyupdate = curtime;
}
return 0;
}
/* Apply meta data to KEYBLOCK. This sets the origin of the key to
* ORIGIN and the updateurl to URL. Note that this function is only
* used for a new key, that is not when we are merging keys. */
static gpg_error_t
insert_key_origin (kbnode_t keyblock, int origin, const char *url)
{
gpg_error_t err;
kbnode_t node;
u32 curtime = make_timestamp ();
for (node = keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
;
else if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
err = insert_key_origin_pk (node->pkt->pkt.public_key, curtime,
origin, url);
if (err)
return err;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
err = insert_key_origin_uid (node->pkt->pkt.user_id, curtime,
origin, url);
if (err)
return err;
}
}
return 0;
}
/* Update meta data on KEYBLOCK. This updates the key origin on the
* public key according to ORIGIN and URL. The UIDs are already
* updated when this function is called. */
static gpg_error_t
update_key_origin (kbnode_t keyblock, u32 curtime, int origin, const char *url)
{
PKT_public_key *pk;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = keyblock->pkt->pkt.public_key;
if (pk->keyupdate > curtime)
; /* Don't do it for a time warp. */
else if (origin == KEYORG_WKD || origin == KEYORG_DANE)
{
/* We only update the origin info if they either have never been
* set or are the origin was the same as the new one. If this
* is WKD we also update the UID to show from which user id this
* was updated. */
if (!pk->keyorg || pk->keyorg == KEYORG_WKD || pk->keyorg == KEYORG_DANE)
{
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (origin == KEYORG_WKD && url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
}
else if (origin == KEYORG_KS)
{
/* All updates from a keyserver are considered to have the
* freshed key. Thus we always set the new key origin. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
else if (origin == KEYORG_FILE)
{
/* Updates from a file are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
}
else if (origin == KEYORG_URL)
{
/* Updates from a URL are considered to be fresh. */
pk->keyorg = origin;
pk->keyupdate = curtime;
xfree (pk->updateurl);
pk->updateurl = NULL;
if (url)
{
pk->updateurl = xtrystrdup (url);
if (!pk->updateurl)
return gpg_error_from_syserror ();
}
}
return 0;
}
/*
* Try to import one keyblock. Return an error only in serious cases,
* but never for an invalid keyblock. It uses log_error to increase
* the internal errorcount, so that invalid input can be detected by
* programs which called gpg. If SILENT is no messages are printed -
* even most error messages are suppressed. ORIGIN is the origin of
* the key (0 for unknown) and URL the corresponding URL. FROM_SK
* indicates that the key has been made from a secret key. If R_SAVED
* is not NULL a boolean will be stored indicating whether the
* keyblock has valid parts. Unless OTHERREVSIGS is NULL it is
* updated with encountered new revocation signatures.
*/
static gpg_error_t
import_one_real (ctrl_t ctrl,
kbnode_t keyblock, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid,
kbnode_t *otherrevsigs)
{
gpg_error_t err = 0;
PKT_public_key *pk;
kbnode_t node, uidnode;
kbnode_t keyblock_orig = NULL;
byte fpr2[MAX_FINGERPRINT_LEN];
size_t fpr2len;
u32 keyid[2];
int new_key = 0;
int mod_key = 0;
int same_key = 0;
int non_self = 0;
size_t an;
char pkstrbuf[PUBKEY_STRING_SIZE];
int merge_keys_done = 0;
int any_filter = 0;
KEYDB_HANDLE hd = NULL;
if (r_valid)
*r_valid = 0;
/* If show-only is active we don't won't any extra output. */
if ((options & (IMPORT_SHOW | IMPORT_DRY_RUN)))
silent = 1;
/* Get the key and print some info about it. */
node = find_kbnode( keyblock, PKT_PUBLIC_KEY );
if (!node )
BUG();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr2, &fpr2len);
for (an = fpr2len; an < MAX_FINGERPRINT_LEN; an++)
fpr2[an] = 0;
keyid_from_pk( pk, keyid );
uidnode = find_next_kbnode( keyblock, PKT_USER_ID );
if (opt.verbose && !opt.interactive && !silent && !from_sk)
{
/* Note that we do not print this info in FROM_SK mode
* because import_secret_one already printed that. */
log_info ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk), datestr_from_pk(pk) );
if (uidnode)
print_utf8_buffer (log_get_stream (),
uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len );
log_printf ("\n");
}
if (!uidnode)
{
if (!silent)
log_error( _("key %s: no user ID\n"), keystr_from_pk(pk));
return 0;
}
if (screener && screener (keyblock, screener_arg))
{
log_error (_("key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
return 0;
}
if (opt.interactive && !silent)
{
if (is_status_enabled())
print_import_check (pk, uidnode->pkt->pkt.user_id);
merge_keys_and_selfsig (ctrl, keyblock);
tty_printf ("\n");
show_basic_key_info (ctrl, keyblock, from_sk);
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("import.okay",
"Do you want to import this key? (y/N) "))
return 0;
}
/* Remove all non-self-sigs if requested. Note that this is a NOP if
* that option has been globally set but we may also be called
* latter with the already parsed keyblock and a locally changed
* option. This is why we need to remove them here as well. */
if ((options & IMPORT_SELF_SIGS_ONLY))
remove_all_non_self_sigs (&keyblock, keyid);
/* Remove or collapse the user ids. */
if ((options & IMPORT_COLLAPSE_UIDS))
collapse_uids (&keyblock);
if ((options & IMPORT_COLLAPSE_SUBKEYS))
collapse_subkeys (&keyblock);
/* Clean the key that we're about to import, to cut down on things
that we have to clean later. This has no practical impact on the
end result, but does result in less logging which might confuse
the user. */
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock,
opt.verbose,
(options&IMPORT_MINIMAL)? EXPORT_MINIMAL : 0,
NULL, NULL);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
clear_kbnode_flags( keyblock );
if ((options&IMPORT_REPAIR_PKS_SUBKEY_BUG)
&& fix_pks_corruption (ctrl, keyblock)
&& opt.verbose)
log_info (_("key %s: PKS subkey corruption repaired\n"),
keystr_from_pk(pk));
if ((options & IMPORT_REPAIR_KEYS))
key_check_all_keysigs (ctrl, 1, keyblock, 0, 0);
if (chk_self_sigs (ctrl, keyblock, keyid, &non_self))
return 0; /* Invalid keyblock - error already printed. */
/* If we allow such a thing, mark unsigned uids as valid */
if (opt.allow_non_selfsigned_uid)
{
for (node=keyblock; node; node = node->next )
if (node->pkt->pkttype == PKT_USER_ID
&& !(node->flag & NODE_GOOD_SELFSIG)
&& !(node->flag & NODE_BAD_SELFSIG) )
{
char *user=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
/* Fake a good signature status for the user id. */
node->flag |= NODE_GOOD_SELFSIG;
log_info( _("key %s: accepted non self-signed user ID \"%s\"\n"),
keystr_from_pk(pk),user);
xfree(user);
}
}
/* Delete invalid parts and bail out if there are no user ids left. */
if (!delete_inv_parts (ctrl, keyblock, keyid, options, otherrevsigs))
{
if (!silent)
{
log_error ( _("key %s: no valid user IDs\n"), keystr_from_pk(pk));
if (!opt.quiet)
log_info(_("this may be caused by a missing self-signature\n"));
}
stats->no_user_id++;
return 0;
}
/* Get rid of deleted nodes. */
commit_kbnode (&keyblock);
/* Apply import filter. */
if (import_filter.keep_uid)
{
apply_keep_uid_filter (ctrl, keyblock, import_filter.keep_uid);
commit_kbnode (&keyblock);
any_filter = 1;
}
if (import_filter.drop_sig)
{
apply_drop_sig_filter (ctrl, keyblock, import_filter.drop_sig);
commit_kbnode (&keyblock);
any_filter = 1;
}
/* If we ran any filter we need to check that at least one user id
* is left in the keyring. Note that we do not use log_error in
* this case. */
if (any_filter && !any_uid_left (keyblock))
{
if (!opt.quiet )
log_info ( _("key %s: no valid user IDs\n"), keystr_from_pk (pk));
stats->no_user_id++;
return 0;
}
/* The keyblock is valid and ready for real import. */
if (r_valid)
*r_valid = 1;
/* Show the key in the form it is merged or inserted. We skip this
* if "import-export" is also active without --armor or the output
* file has explicily been given. */
if ((options & IMPORT_SHOW)
&& !((options & IMPORT_EXPORT) && !opt.armor && !opt.outfile))
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
/* Note that we do not want to show the validity because the key
* has not yet imported. */
err = list_keyblock_direct (ctrl, keyblock, from_sk, 0,
opt.fingerprint || opt.with_fingerprint, 1);
es_fflush (es_stdout);
no_usable_encr_subkeys_warning (keyblock);
if (err)
goto leave;
}
/* Write the keyblock to the output and do not actually import. */
if ((options & IMPORT_EXPORT))
{
if (!merge_keys_done)
{
merge_keys_and_selfsig (ctrl, keyblock);
merge_keys_done = 1;
}
err = write_keyblock_to_output (keyblock, opt.armor, opt.export_options);
goto leave;
}
if (opt.dry_run || (options & IMPORT_DRY_RUN))
goto leave;
/* Do we have this key already in one of our pubrings ? */
err = get_keyblock_byfprint_fast (ctrl, &keyblock_orig, &hd,
fpr2, fpr2len, 1/*locked*/);
if ((err
&& gpg_err_code (err) != GPG_ERR_NO_PUBKEY
&& gpg_err_code (err) != GPG_ERR_UNUSABLE_PUBKEY)
|| !hd)
{
/* The !hd above is to catch a misbehaving function which
* returns NO_PUBKEY for failing to allocate a handle. */
if (!silent)
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (err));
}
else if (err && (opt.import_options&IMPORT_MERGE_ONLY) )
{
if (opt.verbose && !silent )
log_info( _("key %s: new key - skipped\n"), keystr(keyid));
err = 0;
stats->skipped_new_keys++;
}
else if (err) /* Insert this key. */
{
/* Note: ERR can only be NO_PUBKEY or UNUSABLE_PUBKEY. */
int n_sigs_cleaned, n_uids_cleaned;
err = keydb_locate_writable (hd);
if (err)
{
log_error (_("no writable keyring found: %s\n"), gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (opt.verbose > 1 )
log_info (_("writing to '%s'\n"), keydb_get_resource_name (hd) );
if ((options & IMPORT_CLEAN))
{
merge_keys_and_selfsig (ctrl, keyblock);
clean_all_uids (ctrl, keyblock, opt.verbose,
(options&IMPORT_MINIMAL)? EXPORT_MINIMAL : 0,
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = insert_key_origin (keyblock, origin, url);
if (err)
{
log_error ("insert_key_origin failed: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
}
err = keydb_insert_keyblock (hd, keyblock );
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (!(opt.import_options & IMPORT_KEEP_OWNERTTRUST))
{
/* This should not be possible since we delete the
ownertrust when a key is deleted, but it can happen if
the keyring and trustdb are out of sync. It can also
be made to happen with the trusted-key command and by
importing and locally exported key. */
clear_ownertrusts (ctrl, pk);
if (non_self)
revalidation_mark (ctrl);
}
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* We are ready. */
if (!err && !opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info (_("key %s: public key \"%s\" imported\n"),
keystr(keyid), p);
xfree(p);
}
if (!err && is_status_enabled())
{
char *us = get_long_user_id_string (ctrl, keyid);
write_status_text( STATUS_IMPORTED, us );
xfree(us);
print_import_ok (pk, 1);
}
if (!err)
{
stats->imported++;
new_key = 1;
}
}
else /* Key already exists - merge. */
{
int n_uids, n_sigs, n_subk, n_sigs_cleaned, n_uids_cleaned;
u32 curtime = make_timestamp ();
/* Compare the original against the new key; just to be sure nothing
* weird is going on */
if (cmp_public_keys (keyblock_orig->pkt->pkt.public_key, pk))
{
if (!silent)
log_error( _("key %s: doesn't match our copy\n"),keystr(keyid));
goto leave;
}
/* Make sure the original direct key sigs are all sane. */
n_sigs_cleaned = fix_bad_direct_key_sigs (ctrl, keyblock_orig, keyid);
if (n_sigs_cleaned)
commit_kbnode (&keyblock_orig);
/* Try to merge KEYBLOCK into KEYBLOCK_ORIG. */
clear_kbnode_flags( keyblock_orig );
clear_kbnode_flags( keyblock );
n_uids = n_sigs = n_subk = n_uids_cleaned = 0;
err = merge_blocks (ctrl, options, keyblock_orig, keyblock, keyid,
curtime, origin, url,
&n_uids, &n_sigs, &n_subk );
if (err)
goto leave;
/* Clean the final keyblock again if requested. we can't do
* this if only self-signatures are imported; see bug #4628. */
if ((options & IMPORT_CLEAN)
&& !(options & IMPORT_SELF_SIGS_ONLY))
{
merge_keys_and_selfsig (ctrl, keyblock_orig);
clean_all_uids (ctrl, keyblock_orig, opt.verbose,
(options&IMPORT_MINIMAL)? EXPORT_MINIMAL : 0,
&n_uids_cleaned,&n_sigs_cleaned);
clean_all_subkeys (ctrl, keyblock_orig, opt.verbose, KEY_CLEAN_NONE,
NULL, NULL);
}
if (n_uids || n_sigs || n_subk || n_sigs_cleaned || n_uids_cleaned)
{
/* Unless we are in restore mode apply meta data to the
* keyblock. Note that this will never change the first packet
* and thus the address of KEYBLOCK won't change. */
if ( !(options & IMPORT_RESTORE) )
{
err = update_key_origin (keyblock_orig, curtime, origin, url);
if (err)
{
log_error ("update_key_origin failed: %s\n",
gpg_strerror (err));
goto leave;
}
}
mod_key = 1;
/* KEYBLOCK_ORIG has been updated; write */
err = keydb_update_keyblock (ctrl, hd, keyblock_orig);
if (err)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (err));
else if (non_self)
revalidation_mark (ctrl);
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* We are ready. Print and update stats if we got no error.
* An error here comes from writing the keyblock and thus
* very likely means that no update happened. */
if (!err && !opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
if (n_uids == 1 )
log_info( _("key %s: \"%s\" 1 new user ID\n"),
keystr(keyid),p);
else if (n_uids )
log_info( _("key %s: \"%s\" %d new user IDs\n"),
keystr(keyid),p,n_uids);
if (n_sigs == 1 )
log_info( _("key %s: \"%s\" 1 new signature\n"),
keystr(keyid), p);
else if (n_sigs )
log_info( _("key %s: \"%s\" %d new signatures\n"),
keystr(keyid), p, n_sigs );
if (n_subk == 1 )
log_info( _("key %s: \"%s\" 1 new subkey\n"),
keystr(keyid), p);
else if (n_subk )
log_info( _("key %s: \"%s\" %d new subkeys\n"),
keystr(keyid), p, n_subk );
if (n_sigs_cleaned==1)
log_info(_("key %s: \"%s\" %d signature cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
else if (n_sigs_cleaned)
log_info(_("key %s: \"%s\" %d signatures cleaned\n"),
keystr(keyid),p,n_sigs_cleaned);
if (n_uids_cleaned==1)
log_info(_("key %s: \"%s\" %d user ID cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
else if (n_uids_cleaned)
log_info(_("key %s: \"%s\" %d user IDs cleaned\n"),
keystr(keyid),p,n_uids_cleaned);
xfree(p);
}
if (!err)
{
stats->n_uids +=n_uids;
stats->n_sigs +=n_sigs;
stats->n_subk +=n_subk;
stats->n_sigs_cleaned +=n_sigs_cleaned;
stats->n_uids_cleaned +=n_uids_cleaned;
if (is_status_enabled () && !silent)
print_import_ok (pk, ((n_uids?2:0)|(n_sigs?4:0)|(n_subk?8:0)));
}
}
else
{
/* Release the handle and thus unlock the keyring asap. */
keydb_release (hd);
hd = NULL;
/* FIXME: We do not track the time we last checked a key for
* updates. To do this we would need to rewrite even the
* keys which have no changes. Adding this would be useful
* for the automatic update of expired keys via the WKD in
* case the WKD still carries the expired key. See
* get_best_pubkey_byname. */
same_key = 1;
if (is_status_enabled ())
print_import_ok (pk, 0);
if (!opt.quiet && !silent)
{
char *p = get_user_id_byfpr_native (ctrl, fpr2, fpr2len);
log_info( _("key %s: \"%s\" not changed\n"),keystr(keyid),p);
xfree(p);
}
stats->unchanged++;
}
}
leave:
keydb_release (hd);
if (mod_key || new_key || same_key)
{
/* A little explanation for this: we fill in the fingerprint
when importing keys as it can be useful to know the
fingerprint in certain keyserver-related cases (a keyserver
asked for a particular name, but the key doesn't have that
name). However, in cases where we're importing more than
one key at a time, we cannot know which key to fingerprint.
In these cases, rather than guessing, we do not
fingerprinting at all, and we must hope the user ID on the
keys are useful. Note that we need to do this for new
keys, merged keys and even for unchanged keys. This is
required because for example the --auto-key-locate feature
may import an already imported key and needs to know the
fingerprint of the key in all cases. */
if (fpr)
{
/* Note that we need to compare against 0 here because
COUNT gets only incremented after returning from this
function. */
if (!stats->count)
{
xfree (*fpr);
*fpr = fingerprint_from_pk (pk, NULL, fpr_len);
}
else if (origin != KEYORG_WKD)
{
xfree (*fpr);
*fpr = NULL;
}
}
}
/* Now that the key is definitely incorporated into the keydb, we
need to check if a designated revocation is present or if the
prefs are not rational so we can warn the user. */
if (mod_key)
{
revocation_present (ctrl, keyblock_orig);
if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock_orig);
}
else if (new_key)
{
revocation_present (ctrl, keyblock);
if (!from_sk && have_secret_key_with_kid (ctrl, keyid))
check_prefs (ctrl, keyblock);
}
release_kbnode( keyblock_orig );
return err;
}
/* Wrapper around import_one_real to retry the import in some cases. */
static gpg_error_t
import_one (ctrl_t ctrl,
kbnode_t keyblock, struct import_stats_s *stats,
unsigned char **fpr, size_t *fpr_len, unsigned int options,
int from_sk, int silent,
import_screener_t screener, void *screener_arg,
int origin, const char *url, int *r_valid)
{
gpg_error_t err;
kbnode_t otherrevsigs = NULL;
kbnode_t node;
err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
from_sk, silent, screener, screener_arg,
origin, url, r_valid, &otherrevsigs);
if (gpg_err_code (err) == GPG_ERR_TOO_LARGE
&& gpg_err_source (err) == GPG_ERR_SOURCE_KEYBOX
&& ((options & (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN))
!= (IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN)))
{
/* We hit the maximum image length. Ask the wrapper to do
* everything again but this time with some extra options. */
u32 keyid[2];
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
log_info ("key %s: keyblock too large, retrying with self-sigs-only\n",
keystr (keyid));
options |= IMPORT_SELF_SIGS_ONLY | IMPORT_CLEAN;
err = import_one_real (ctrl, keyblock, stats, fpr, fpr_len, options,
from_sk, silent, screener, screener_arg,
origin, url, r_valid, &otherrevsigs);
}
/* Finally try to import other revocation certificates. For example
* those of a former key appended to the current key. */
if (!err)
{
for (node = otherrevsigs; node; node = node->next)
import_revoke_cert (ctrl, node, options, stats);
}
release_kbnode (otherrevsigs);
return err;
}
/* Transfer all the secret keys in SEC_KEYBLOCK to the gpg-agent. The
* function prints diagnostics and returns an error code. If BATCH is
* true the secret keys are stored by gpg-agent in the transfer format
* (i.e. no re-protection and aksing for passphrases). If ONLY_MARKED
* is set, only those nodes with flag NODE_TRANSFER_SECKEY are
* processed. */
gpg_error_t
transfer_secret_keys (ctrl_t ctrl, struct import_stats_s *stats,
kbnode_t sec_keyblock, int batch, int force,
int only_marked)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
kbnode_t ctx = NULL;
kbnode_t node;
PKT_public_key *main_pk, *pk;
struct seckey_info *ski;
int nskey;
membuf_t mbuf;
int i, j;
void *format_args[2*PUBKEY_MAX_NSKEY];
gcry_sexp_t skey, prot, tmpsexp;
gcry_sexp_t curve = NULL;
unsigned char *transferkey = NULL;
size_t transferkeylen;
gcry_cipher_hd_t cipherhd = NULL;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
char *cache_nonce = NULL;
int stub_key_skipped = 0;
/* Get the current KEK. */
err = agent_keywrap_key (ctrl, 0, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Prepare a cipher context. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
/* Note: We need to use walk_kbnode so that we skip nodes which are
* marked as deleted. */
main_pk = NULL;
while ((node = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
if (only_marked && !(node->flag & NODE_TRANSFER_SECKEY))
continue;
pk = node->pkt->pkt.public_key;
if (!main_pk)
main_pk = pk;
/* Make sure the keyids are available. */
keyid_from_pk (pk, NULL);
if (node->pkt->pkttype == PKT_SECRET_KEY)
{
pk->main_keyid[0] = pk->keyid[0];
pk->main_keyid[1] = pk->keyid[1];
}
else
{
pk->main_keyid[0] = main_pk->keyid[0];
pk->main_keyid[1] = main_pk->keyid[1];
}
ski = pk->seckey_info;
if (!ski)
BUG ();
if (stats)
{
stats->count++;
stats->secret_read++;
}
/* We ignore stub keys. The way we handle them in other parts
of the code is by asking the agent whether any secret key is
available for a given keyblock and then concluding that we
have a secret key; all secret (sub)keys of the keyblock the
agent does not know of are then stub keys. This works also
for card stub keys. The learn command or the card-status
command may be used to check with the agent whether a card
has been inserted and a stub key is in turn generated by the
agent. */
if (ski->s2k.mode == 1001 || ski->s2k.mode == 1002)
{
stub_key_skipped = 1;
continue;
}
/* Convert our internal secret key object into an S-expression. */
nskey = pubkey_get_nskey (pk->pubkey_algo);
if (!nskey || nskey > PUBKEY_MAX_NSKEY)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
log_error ("internal error: %s\n", gpg_strerror (err));
goto leave;
}
init_membuf (&mbuf, 50);
put_membuf_str (&mbuf, "(skey");
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pk->pubkey_algo == PUBKEY_ALGO_EDDSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
/* The ECC case. */
char *curvestr = openpgp_oid_to_str (pk->pkey[0]);
if (!curvestr)
err = gpg_error_from_syserror ();
else
{
const char *curvename = openpgp_oid_to_curve (curvestr, 1);
gcry_sexp_release (curve);
err = gcry_sexp_build (&curve, NULL, "(curve %s)",
curvename?curvename:curvestr);
if (!err)
{
j = 0;
/* Append the public key element Q. */
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + 1;
/* Append the secret key element D. For ECDH we
skip PKEY[2] because this holds the KEK which is
not needed by gpg-agent. */
i = pk->pubkey_algo == PUBKEY_ALGO_ECDH? 3 : 2;
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
/* Simple hack to print a warning for an invalid key
* in case of cv25519. We have only opaque MPIs here. */
if (pk->pubkey_algo == PUBKEY_ALGO_ECDH
&& !strcmp (curvestr, "1.3.6.1.4.1.3029.1.5.1")
&& !gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1)
&& gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE))
{
const unsigned char *pp;
unsigned int nn;
pp = gcry_mpi_get_opaque (pk->pkey[i], &nn);
nn = (nn+7)/8;
if (pp && nn && (pp[nn-1] & 7))
log_info ("warning: lower 3 bits of the secret key"
" are not cleared\n");
}
}
xfree (curvestr);
}
}
else
{
/* Standard case for the old (non-ECC) algorithms. */
for (i=j=0; i < nskey; i++)
{
if (!pk->pkey[i])
continue; /* Protected keys only have NPKEY+1 elements. */
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER1))
put_membuf_str (&mbuf, " e %m");
else
put_membuf_str (&mbuf, " _ %m");
format_args[j++] = pk->pkey + i;
}
}
put_membuf_str (&mbuf, ")");
put_membuf (&mbuf, "", 1);
if (err)
xfree (get_membuf (&mbuf, NULL));
else
{
char *format = get_membuf (&mbuf, NULL);
if (!format)
err = gpg_error_from_syserror ();
else
err = gcry_sexp_build_array (&skey, NULL, format, format_args);
xfree (format);
}
if (err)
{
log_error ("error building skey array: %s\n", gpg_strerror (err));
goto leave;
}
if (ski->is_protected)
{
char countbuf[35];
/* FIXME: Support AEAD */
/* Note that the IVLEN may be zero if we are working on a
dummy key. We can't express that in an S-expression and
thus we send dummy data for the IV. */
snprintf (countbuf, sizeof countbuf, "%lu",
(unsigned long)ski->s2k.count);
err = gcry_sexp_build
(&prot, NULL,
" (protection %s %s %b %d %s %b %s)\n",
ski->sha1chk? "sha1":"sum",
openpgp_cipher_algo_name (ski->algo),
ski->ivlen? (int)ski->ivlen:1,
ski->ivlen? ski->iv: (const unsigned char*)"X",
ski->s2k.mode,
openpgp_md_algo_name (ski->s2k.hash_algo),
(int)sizeof (ski->s2k.salt), ski->s2k.salt,
countbuf);
}
else
err = gcry_sexp_build (&prot, NULL, " (protection none)\n");
tmpsexp = NULL;
xfree (transferkey);
transferkey = NULL;
if (!err)
err = gcry_sexp_build (&tmpsexp, NULL,
"(openpgp-private-key\n"
" (version %d)\n"
" (algo %s)\n"
" %S%S\n"
" (csum %d)\n"
" %S)\n",
pk->version,
openpgp_pk_algo_name (pk->pubkey_algo),
curve, skey,
(int)(unsigned long)ski->csum, prot);
gcry_sexp_release (skey);
gcry_sexp_release (prot);
if (!err)
err = make_canon_sexp_pad (tmpsexp, 1, &transferkey, &transferkeylen);
gcry_sexp_release (tmpsexp);
if (err)
{
log_error ("error building transfer key: %s\n", gpg_strerror (err));
goto leave;
}
/* Wrap the key. */
wrappedkeylen = transferkeylen + 8;
xfree (wrappedkey);
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
err = gpg_error_from_syserror ();
else
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen,
transferkey, transferkeylen);
if (err)
goto leave;
xfree (transferkey);
transferkey = NULL;
/* Send the wrapped key to the agent. */
{
char *desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_IMPORT, 1);
err = agent_import_key (ctrl, desc, &cache_nonce,
wrappedkey, wrappedkeylen, batch, force,
pk->keyid, pk->main_keyid, pk->pubkey_algo,
pk->timestamp);
xfree (desc);
}
if (!err)
{
if (opt.verbose)
log_info (_("key %s: secret key imported\n"),
keystr_from_pk_with_sub (main_pk, pk));
if (stats)
stats->secret_imported++;
}
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
{
if (opt.verbose)
log_info (_("key %s: secret key already exists\n"),
keystr_from_pk_with_sub (main_pk, pk));
err = 0;
if (stats)
stats->secret_dups++;
}
else
{
log_error (_("key %s: error sending to agent: %s\n"),
keystr_from_pk_with_sub (main_pk, pk),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break; /* Don't try the other subkeys. */
}
}
if (!err && stub_key_skipped)
/* We need to notify user how to migrate stub keys. */
err = gpg_error (GPG_ERR_NOT_PROCESSED);
leave:
gcry_sexp_release (curve);
xfree (cache_nonce);
xfree (wrappedkey);
xfree (transferkey);
gcry_cipher_close (cipherhd);
xfree (kek);
return err;
}
/* Walk a secret keyblock and produce a public keyblock out of it.
* Returns a new node or NULL on error. Modifies the tag field of the
* nodes. */
static kbnode_t
sec_to_pub_keyblock (kbnode_t sec_keyblock)
{
kbnode_t pub_keyblock = NULL;
kbnode_t ctx = NULL;
kbnode_t secnode, pubnode;
kbnode_t lastnode = NULL;
unsigned int tag = 0;
/* Set a tag to all nodes. */
for (secnode = sec_keyblock; secnode; secnode = secnode->next)
secnode->tag = ++tag;
/* Copy. */
while ((secnode = walk_kbnode (sec_keyblock, &ctx, 0)))
{
if (secnode->pkt->pkttype == PKT_SECRET_KEY
|| secnode->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* Make a public key. */
PACKET *pkt;
PKT_public_key *pk;
pkt = xtrycalloc (1, sizeof *pkt);
pk = pkt? copy_public_key (NULL, secnode->pkt->pkt.public_key): NULL;
if (!pk)
{
xfree (pkt);
release_kbnode (pub_keyblock);
return NULL;
}
if (secnode->pkt->pkttype == PKT_SECRET_KEY)
pkt->pkttype = PKT_PUBLIC_KEY;
else
pkt->pkttype = PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
pubnode = new_kbnode (pkt);
}
else
{
pubnode = clone_kbnode (secnode);
}
pubnode->tag = secnode->tag;
if (!pub_keyblock)
pub_keyblock = lastnode = pubnode;
else
{
lastnode->next = pubnode;
lastnode = pubnode;
}
}
return pub_keyblock;
}
/* Delete all notes in the keyblock at R_KEYBLOCK which are not in
* PUB_KEYBLOCK. Modifies the tags of both keyblock's nodes. */
static gpg_error_t
resync_sec_with_pub_keyblock (kbnode_t *r_keyblock, kbnode_t pub_keyblock,
kbnode_t *r_removedsecs)
{
kbnode_t sec_keyblock = *r_keyblock;
kbnode_t node, prevnode;
unsigned int *taglist;
unsigned int ntaglist, n;
kbnode_t attic = NULL;
kbnode_t *attic_head = &attic;
/* Collect all tags in an array for faster searching. */
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
ntaglist++;
taglist = xtrycalloc (ntaglist, sizeof *taglist);
if (!taglist)
return gpg_error_from_syserror ();
for (ntaglist = 0, node = pub_keyblock; node; node = node->next)
taglist[ntaglist++] = node->tag;
/* Walks over the secret keyblock and delete all nodes which are not
* in the tag list. Those nodes have been deleted in the
* pub_keyblock. Sequential search is a bit lazy and could be
* optimized by sorting and bsearch; however secret keyrings are
* short and there are easier ways to DoS the import. */
again:
for (prevnode=NULL, node=sec_keyblock; node; prevnode=node, node=node->next)
{
for (n=0; n < ntaglist; n++)
if (taglist[n] == node->tag)
break;
if (n == ntaglist) /* Not in public keyblock. */
{
if (node->pkt->pkttype == PKT_SECRET_KEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (!prevnode)
sec_keyblock = node->next;
else
prevnode->next = node->next;
node->next = NULL;
*attic_head = node;
attic_head = &node->next;
goto again; /* That's lame; I know. */
}
else
delete_kbnode (node);
}
}
xfree (taglist);
/* Commit the as deleted marked nodes and return the possibly
* modified keyblock and a list of removed secret key nodes. */
commit_kbnode (&sec_keyblock);
*r_keyblock = sec_keyblock;
*r_removedsecs = attic;
return 0;
}
/* Helper for import_secret_one. */
static gpg_error_t
do_transfer (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk,
struct import_stats_s *stats, int batch, int only_marked)
{
gpg_error_t err;
struct import_stats_s subkey_stats = {0};
int force = 0;
int already_exist = agent_probe_secret_key (ctrl, pk);
if (already_exist == 2 || already_exist == 4)
{
if (!opt.quiet)
log_info (_("key %s: card reference is overridden by key material\n"),
keystr_from_pk (pk));
force = 1;
}
err = transfer_secret_keys (ctrl, &subkey_stats, keyblock,
batch, force, only_marked);
if (gpg_err_code (err) == GPG_ERR_NOT_PROCESSED)
{
/* TRANSLATORS: For a smartcard, each private key on host has a
* reference (stub) to a smartcard and actual private key data
* is stored on the card. A single smartcard can have up to
* three private key data. Importing private key stub is always
* skipped in 2.1, and it returns GPG_ERR_NOT_PROCESSED.
* Instead, user should be suggested to run 'gpg --card-status',
* then, references to a card will be automatically created
* again. */
log_info (_("To migrate '%s', with each smartcard, "
"run: %s\n"), "secring.gpg", "gpg --card-status");
err = 0;
}
if (!err)
{
int status = 16;
if (!opt.quiet)
log_info (_("key %s: secret key imported\n"), keystr_from_pk (pk));
if (subkey_stats.secret_imported)
{
status |= 1;
stats->secret_imported += 1;
}
if (subkey_stats.secret_dups)
stats->secret_dups += 1;
if (is_status_enabled ())
print_import_ok (pk, status);
}
return err;
}
/* If the secret keys (main or subkey) in SECKEYS have a corresponding
* public key in the public key described by (FPR,FPRLEN) import these
* parts.
*/
static gpg_error_t
import_matching_seckeys (ctrl_t ctrl, kbnode_t seckeys,
const byte *mainfpr, size_t mainfprlen,
struct import_stats_s *stats, int batch)
{
gpg_error_t err;
kbnode_t pub_keyblock = NULL;
kbnode_t node;
struct { byte fpr[MAX_FINGERPRINT_LEN]; size_t fprlen; } *fprlist = NULL;
size_t n, nfprlist;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
PKT_public_key *pk;
/* Get the entire public key block from our keystore and put all its
* fingerprints into an array. */
err = get_pubkey_byfprint (ctrl, NULL, &pub_keyblock, mainfpr, mainfprlen);
if (err)
goto leave;
log_assert (pub_keyblock && pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
for (nfprlist = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
nfprlist++;
log_assert (nfprlist);
fprlist = xtrycalloc (nfprlist, sizeof *fprlist);
if (!fprlist)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (n = 0, node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
fingerprint_from_pk (node->pkt->pkt.public_key,
fprlist[n].fpr, &fprlist[n].fprlen);
n++;
}
log_assert (n == nfprlist);
/* for (n=0; n < nfprlist; n++) */
/* log_printhex (fprlist[n].fpr, fprlist[n].fprlen, "pubkey %zu:", n); */
/* Mark all secret keys which have a matching public key part in
* PUB_KEYBLOCK. */
for (node = seckeys; node; node = node->next)
{
if (node->pkt->pkttype != PKT_SECRET_KEY
&& node->pkt->pkttype != PKT_SECRET_SUBKEY)
continue; /* Should not happen. */
fingerprint_from_pk (node->pkt->pkt.public_key, fpr, &fprlen);
node->flag &= ~NODE_TRANSFER_SECKEY;
for (n=0; n < nfprlist; n++)
if (fprlist[n].fprlen == fprlen && !memcmp (fprlist[n].fpr,fpr,fprlen))
{
node->flag |= NODE_TRANSFER_SECKEY;
/* log_debug ("found matching seckey\n"); */
break;
}
}
/* Transfer all marked keys. */
err = do_transfer (ctrl, seckeys, pk, stats, batch, 1);
leave:
xfree (fprlist);
release_kbnode (pub_keyblock);
return err;
}
/* Import function for a single secret keyblock. Handling is simpler
* than for public keys. We allow secret key importing only when
* allow is true, this is so that a secret key can not be imported
* accidentally and thereby tampering with the trust calculation.
*
* Ownership of KEYBLOCK is transferred to this function!
*
* If R_SECATTIC is not null the last special sec_keyblock is stored
* there.
*/
static gpg_error_t
import_secret_one (ctrl_t ctrl, kbnode_t keyblock,
struct import_stats_s *stats, int batch,
unsigned int options, int for_migration,
import_screener_t screener, void *screener_arg,
kbnode_t *r_secattic)
{
PKT_public_key *pk;
struct seckey_info *ski;
kbnode_t node, uidnode;
u32 keyid[2];
gpg_error_t err = 0;
int nr_prev;
kbnode_t pub_keyblock;
kbnode_t attic = NULL;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Get the key and print some info about it */
node = find_kbnode (keyblock, PKT_SECRET_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fprlen);
keyid_from_pk (pk, keyid);
uidnode = find_next_kbnode (keyblock, PKT_USER_ID);
if (screener && screener (keyblock, screener_arg))
{
log_error (_("secret key %s: %s\n"), keystr_from_pk (pk),
_("rejected by import screener"));
release_kbnode (keyblock);
return 0;
}
if (opt.verbose && !for_migration)
{
log_info ("sec %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk), datestr_from_pk (pk));
if (uidnode)
print_utf8_buffer (log_get_stream (), uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len);
log_printf ("\n");
}
stats->secret_read++;
if ((options & IMPORT_NO_SECKEY))
{
if (!for_migration)
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
if (!uidnode)
{
if (!for_migration)
log_error( _("key %s: no user ID\n"), keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
ski = pk->seckey_info;
if (!ski)
{
/* Actually an internal error. */
log_error ("key %s: secret key info missing\n", keystr_from_pk (pk));
release_kbnode (keyblock);
return 0;
}
/* A quick check to not import keys with an invalid protection
cipher algorithm (only checks the primary key, though). */
if (ski->algo > 110)
{
if (!for_migration)
log_error (_("key %s: secret key with invalid cipher %d"
" - skipped\n"), keystr_from_pk (pk), ski->algo);
release_kbnode (keyblock);
return 0;
}
#ifdef ENABLE_SELINUX_HACKS
if (1)
{
/* We don't allow importing secret keys because that may be used
to put a secret key into the keyring and the user might later
be tricked into signing stuff with that key. */
log_error (_("importing secret keys not allowed\n"));
release_kbnode (keyblock);
return 0;
}
#endif
clear_kbnode_flags (keyblock);
nr_prev = stats->skipped_new_keys;
/* Make a public key out of the key. */
pub_keyblock = sec_to_pub_keyblock (keyblock);
if (!pub_keyblock)
{
err = gpg_error_from_syserror ();
log_error ("key %s: failed to create public key from secret key\n",
keystr_from_pk (pk));
}
else
{
int valid;
/* Note that this outputs an IMPORT_OK status message for the
public key block, and below we will output another one for
the secret keys. FIXME? */
import_one (ctrl, pub_keyblock, stats,
NULL, NULL, options, 1, for_migration,
screener, screener_arg, 0, NULL, &valid);
/* The secret keyblock may not have nodes which are deleted in
* the public keyblock. Otherwise we would import just the
* secret key without having the public key. That would be
* surprising and clutters our private-keys-v1.d. */
err = resync_sec_with_pub_keyblock (&keyblock, pub_keyblock, &attic);
if (err)
goto leave;
if (!valid)
{
/* If the block was not valid the primary key is left in the
* original keyblock because we require that for the first
* node. Move it to ATTIC. */
if (keyblock && keyblock->pkt->pkttype == PKT_SECRET_KEY)
{
node = keyblock;
keyblock = node->next;
node->next = NULL;
if (attic)
{
node->next = attic;
attic = node;
}
else
attic = node;
}
/* Try to import the secret key iff we have a public key. */
if (attic && !(opt.dry_run || (options & IMPORT_DRY_RUN)))
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
else
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
/* log_debug ("attic is:\n"); */
/* dump_kbnode (attic); */
/* Proceed with the valid parts of PUBKEYBLOCK. */
/* At least we cancel the secret key import when the public key
import was skipped due to MERGE_ONLY option and a new
key. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN))
&& stats->skipped_new_keys <= nr_prev)
{
/* Read the keyblock again to get the effects of a merge for
* the public key. */
err = get_pubkey_byfprint (ctrl, NULL, &node, fpr, fprlen);
if (err || !node)
log_error ("key %s: failed to re-lookup public key: %s\n",
keystr_from_pk (pk), gpg_strerror (err));
else
{
err = do_transfer (ctrl, keyblock, pk, stats, batch, 0);
if (!err)
check_prefs (ctrl, node);
release_kbnode (node);
if (!err && attic)
{
/* Try to import invalid subkeys. This can be the
* case if the primary secret key was imported due
* to --allow-non-selfsigned-uid. */
err = import_matching_seckeys (ctrl, attic, fpr, fprlen,
stats, batch);
}
}
}
}
leave:
release_kbnode (keyblock);
release_kbnode (pub_keyblock);
if (r_secattic)
*r_secattic = attic;
else
release_kbnode (attic);
return err;
}
/* Return the recocation reason from signature SIG. If no revocation
* reason is available 0 is returned, in other cases the reason
* (0..255). If R_REASON is not NULL a malloced textual
* representation of the code is stored there. If R_COMMENT is not
* NULL the comment from the reason is stored there and its length at
* R_COMMENTLEN. Note that the value at R_COMMENT is not filtered but
* user supplied data in UTF8; thus it needs to be escaped for display
* purposes. Both return values are either NULL or a malloced
* string/buffer. */
int
get_revocation_reason (PKT_signature *sig, char **r_reason,
char **r_comment, size_t *r_commentlen)
{
int reason_seq = 0;
size_t reason_n;
const byte *reason_p;
char reason_code_buf[20];
const char *reason_text = NULL;
int reason_code = 0;
if (r_reason)
*r_reason = NULL;
if (r_comment)
*r_comment = NULL;
/* Skip over empty reason packets. */
while ((reason_p = enum_sig_subpkt (sig, 1, SIGSUBPKT_REVOC_REASON,
&reason_n, &reason_seq, NULL))
&& !reason_n)
;
if (reason_p)
{
reason_code = *reason_p;
reason_n--; reason_p++;
switch (reason_code)
{
case 0x00: reason_text = _("No reason specified"); break;
case 0x01: reason_text = _("Key is superseded"); break;
case 0x02: reason_text = _("Key has been compromised"); break;
case 0x03: reason_text = _("Key is no longer used"); break;
case 0x20: reason_text = _("User ID is no longer valid"); break;
default:
snprintf (reason_code_buf, sizeof reason_code_buf,
"code=%02x", reason_code);
reason_text = reason_code_buf;
break;
}
if (r_reason)
*r_reason = xstrdup (reason_text);
if (r_comment && reason_n)
{
*r_comment = xmalloc (reason_n);
memcpy (*r_comment, reason_p, reason_n);
*r_commentlen = reason_n;
}
}
return reason_code;
}
/* List the recocation signature as a "rvs" record. SIGRC shows the
* character from the signature verification or 0 if no public key was
* found. */
static void
list_standalone_revocation (ctrl_t ctrl, PKT_signature *sig, int sigrc)
{
char *siguid = NULL;
size_t siguidlen = 0;
char *issuer_fpr = NULL;
int reason_code = 0;
char *reason_text = NULL;
char *reason_comment = NULL;
size_t reason_commentlen;
if (sigrc != '%' && sigrc != '?' && !opt.fast_list_mode)
{
int nouid;
siguid = get_user_id (ctrl, sig->keyid, &siguidlen, &nouid);
if (nouid)
sigrc = '?';
}
reason_code = get_revocation_reason (sig, &reason_text,
&reason_comment, &reason_commentlen);
if (opt.with_colons)
{
es_fputs ("rvs:", es_stdout);
if (sigrc)
es_putc (sigrc, es_stdout);
es_fprintf (es_stdout, "::%d:%08lX%08lX:%s:%s:::",
sig->pubkey_algo,
(ulong) sig->keyid[0], (ulong) sig->keyid[1],
colon_datestr_from_sig (sig),
colon_expirestr_from_sig (sig));
if (siguid)
es_write_sanitized (es_stdout, siguid, siguidlen, ":", NULL);
es_fprintf (es_stdout, ":%02x%c", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (reason_text)
es_fprintf (es_stdout, ",%02x", reason_code);
es_fputs ("::", es_stdout);
if ((issuer_fpr = issuer_fpr_string (sig)))
es_fputs (issuer_fpr, es_stdout);
es_fprintf (es_stdout, ":::%d:", sig->digest_algo);
if (reason_comment)
{
es_fputs ("::::", es_stdout);
es_write_sanitized (es_stdout, reason_comment, reason_commentlen,
":", NULL);
es_putc (':', es_stdout);
}
es_putc ('\n', es_stdout);
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
else /* Human readable. */
{
es_fputs ("rvs", es_stdout);
es_fprintf (es_stdout, "%c%c %c%c%c%c%c%c %s %s",
sigrc, (sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ', keystr (sig->keyid),
datestr_from_sig (sig));
if (siguid)
{
es_fprintf (es_stdout, " ");
print_utf8_buffer (es_stdout, siguid, siguidlen);
}
es_putc ('\n', es_stdout);
if (sig->flags.policy_url
&& (opt.list_options & LIST_SHOW_POLICY_URLS))
show_policy_url (sig, 3, 0);
if (sig->flags.notation && (opt.list_options & LIST_SHOW_NOTATIONS))
show_notation (sig, 3, 0,
((opt.list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0)
+
((opt.list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0));
if (sig->flags.pref_ks
&& (opt.list_options & LIST_SHOW_KEYSERVER_URLS))
show_keyserver_url (sig, 3, 0);
if (reason_text)
{
es_fprintf (es_stdout, " %s%s\n",
_("reason for revocation: "), reason_text);
if (reason_comment)
{
const byte *s, *s_lf;
size_t n, n_lf;
s = reason_comment;
n = reason_commentlen;
s_lf = NULL;
do
{
/* We don't want any empty lines, so we skip them. */
for (;n && *s == '\n'; s++, n--)
;
if (n)
{
s_lf = memchr (s, '\n', n);
n_lf = s_lf? s_lf - s : n;
es_fprintf (es_stdout, " %s",
_("revocation comment: "));
es_write_sanitized (es_stdout, s, n_lf, NULL, NULL);
es_putc ('\n', es_stdout);
s += n_lf; n -= n_lf;
}
} while (s_lf);
}
}
}
es_fflush (es_stdout);
xfree (reason_text);
xfree (reason_comment);
xfree (siguid);
xfree (issuer_fpr);
}
/* Import a revocation certificate; only the first packet in the
* NODE-list is considered. */
static int
import_revoke_cert (ctrl_t ctrl, kbnode_t node, unsigned int options,
struct import_stats_s *stats)
{
PKT_public_key *pk = NULL;
kbnode_t onode;
kbnode_t keyblock = NULL;
KEYDB_HANDLE hd = NULL;
u32 keyid[2];
int rc = 0;
int sigrc = 0;
int silent;
/* No error output for --show-keys. */
silent = (options & (IMPORT_SHOW | IMPORT_DRY_RUN));
log_assert (node->pkt->pkttype == PKT_SIGNATURE );
log_assert (IS_KEY_REV (node->pkt->pkt.signature));
/* FIXME: We can do better here by using the issuer fingerprint if
* available. We should also make use of get_keyblock_byfprint_fast. */
keyid[0] = node->pkt->pkt.signature->keyid[0];
keyid[1] = node->pkt->pkt.signature->keyid[1];
pk = xmalloc_clear( sizeof *pk );
rc = get_pubkey (ctrl, pk, keyid );
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY )
{
if (!silent)
log_error (_("key %s: no public key -"
" can't apply revocation certificate\n"), keystr(keyid));
rc = 0;
goto leave;
}
else if (rc )
{
log_error (_("key %s: public key not found: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* Read the original keyblock. */
hd = keydb_new (ctrl);
if (!hd)
{
rc = gpg_error_from_syserror ();
goto leave;
}
{
byte afp[MAX_FINGERPRINT_LEN];
size_t an;
fingerprint_from_pk (pk, afp, &an);
rc = keydb_search_fpr (hd, afp, an);
}
if (rc)
{
log_error (_("key %s: can't locate original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
rc = keydb_get_keyblock (hd, &keyblock );
if (rc)
{
log_error (_("key %s: can't read original keyblock: %s\n"),
keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* it is okay, that node is not in keyblock because
* check_key_signature works fine for sig_class 0x20 (KEY_REV) in
* this special case. SIGRC is only used for IMPORT_SHOW. */
rc = check_key_signature (ctrl, keyblock, node, NULL);
switch (gpg_err_code (rc))
{
case 0: sigrc = '!'; break;
case GPG_ERR_BAD_SIGNATURE: sigrc = '-'; break;
case GPG_ERR_NO_PUBKEY: sigrc = '?'; break;
case GPG_ERR_UNUSABLE_PUBKEY: sigrc = '?'; break;
default: sigrc = '%'; break;
}
if (rc )
{
if (!silent)
log_error (_("key %s: invalid revocation certificate"
": %s - rejected\n"), keystr(keyid), gpg_strerror (rc));
goto leave;
}
/* check whether we already have this */
for(onode=keyblock->next; onode; onode=onode->next ) {
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& !cmp_signatures(node->pkt->pkt.signature,
onode->pkt->pkt.signature))
{
rc = 0;
goto leave; /* yes, we already know about it */
}
}
/* insert it */
insert_kbnode( keyblock, clone_kbnode(node), 0 );
/* and write the keyblock back unless in dry run mode. */
if (!(opt.dry_run || (options & IMPORT_DRY_RUN)))
{
rc = keydb_update_keyblock (ctrl, hd, keyblock );
if (rc)
log_error (_("error writing keyring '%s': %s\n"),
keydb_get_resource_name (hd), gpg_strerror (rc) );
keydb_release (hd);
hd = NULL;
/* we are ready */
if (!opt.quiet )
{
char *p=get_user_id_native (ctrl, keyid);
log_info( _("key %s: \"%s\" revocation certificate imported\n"),
keystr(keyid),p);
xfree(p);
}
/* If the key we just revoked was ultimately trusted, remove its
* ultimate trust. This doesn't stop the user from putting the
* ultimate trust back, but is a reasonable solution for now. */
if (get_ownertrust (ctrl, pk) == TRUST_ULTIMATE)
clear_ownertrusts (ctrl, pk);
revalidation_mark (ctrl);
}
stats->n_revoc++;
leave:
if ((options & IMPORT_SHOW))
list_standalone_revocation (ctrl, node->pkt->pkt.signature, sigrc);
keydb_release (hd);
release_kbnode( keyblock );
free_public_key( pk );
return rc;
}
/* Loop over the KEYBLOCK and check all self signatures. KEYID is the
* keyid of the primary key for reporting purposes. On return the
* following bits in the node flags are set:
*
* - NODE_GOOD_SELFSIG :: User ID or subkey has a self-signature
* - NODE_BAD_SELFSIG :: Used ID or subkey has an invalid self-signature
* - NODE_DELETION_MARK :: This node shall be deleted
*
* NON_SELF is set to true if there are any sigs other than self-sigs
* in this keyblock.
*
* Returns 0 on success or -1 (but not an error code) if the keyblock
* is invalid.
*/
static int
chk_self_sigs (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid, int *non_self)
{
kbnode_t knode = NULL; /* The node of the current subkey. */
PKT_public_key *subpk = NULL; /* and its packet. */
kbnode_t bsnode = NULL; /* Subkey binding signature node. */
u32 bsdate = 0; /* Timestamp of that node. */
kbnode_t rsnode = NULL; /* Subkey recocation signature node. */
u32 rsdate = 0; /* Timestamp of that node. */
PKT_signature *sig;
int rc;
kbnode_t n;
for (n=keyblock; (n = find_next_kbnode (n, 0)); )
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
knode = n;
subpk = knode->pkt->pkt.public_key;
bsdate = 0;
rsdate = 0;
bsnode = NULL;
rsnode = NULL;
continue;
}
if ( n->pkt->pkttype != PKT_SIGNATURE )
continue;
sig = n->pkt->pkt.signature;
if ( keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1] )
{
*non_self = 1;
continue;
}
/* This just caches the sigs for later use. That way we
import a fully-cached key which speeds things up. */
if (!opt.no_sig_cache)
check_key_signature (ctrl, keyblock, n, NULL);
if ( IS_UID_SIG(sig) || IS_UID_REV(sig) )
{
kbnode_t unode = find_prev_kbnode( keyblock, n, PKT_USER_ID );
if ( !unode )
{
log_error( _("key %s: no user ID for signature\n"),
keystr(keyid));
return -1; /* The complete keyblock is invalid. */
}
/* If it hasn't been marked valid yet, keep trying. */
if (!(unode->flag & NODE_GOOD_SELFSIG))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if ( opt.verbose )
{
char *p = utf8_to_native
(unode->pkt->pkt.user_id->name,
strlen (unode->pkt->pkt.user_id->name),0);
log_info (gpg_err_code(rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key "
"algorithm on user ID \"%s\"\n"):
_("key %s: invalid self-signature "
"on user ID \"%s\"\n"),
keystr (keyid),p);
xfree (p);
}
}
else
unode->flag |= NODE_GOOD_SELFSIG;
}
}
else if (IS_KEY_SIG (sig))
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key algorithm\n"):
_("key %s: invalid direct key signature\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
}
else if ( IS_SUBKEY_SIG (sig) )
{
/* Note that this works based solely on the timestamps like
the rest of gpg. If the standard gets revocation
targets, this may need to be revised. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key binding\n"),
keystr (keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public key"
" algorithm\n"):
_("key %s: invalid subkey binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= bsdate)
{
knode->flag |= NODE_GOOD_SELFSIG; /* Subkey is valid. */
if (bsnode)
{
/* Delete the last binding sig since this
one is newer */
bsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
{
keyid_from_pk (subpk, NULL);
log_info (_("key %s: removed multiple subkey"
" binding\n"),
keystr_with_sub (keyid, subpk->keyid));
}
}
bsnode = n;
bsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
else if ( IS_SUBKEY_REV (sig) )
{
/* We don't actually mark the subkey as revoked right now,
so just check that the revocation sig is the most recent
valid one. Note that we don't care if the binding sig is
newer than the revocation sig. See the comment in
getkey.c:merge_selfsigs_subkey for more. */
if ( !knode )
{
if (opt.verbose)
log_info (_("key %s: no subkey for key revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
rc = check_key_signature (ctrl, keyblock, n, NULL);
if ( rc )
{
if(opt.verbose)
log_info (gpg_err_code (rc) == GPG_ERR_PUBKEY_ALGO ?
_("key %s: unsupported public"
" key algorithm\n"):
_("key %s: invalid subkey revocation\n"),
keystr(keyid));
n->flag |= NODE_DELETION_MARK;
}
else
{
/* It's valid, so is it newer? */
if (sig->timestamp >= rsdate)
{
if (rsnode)
{
/* Delete the last revocation sig since
this one is newer. */
rsnode->flag |= NODE_DELETION_MARK;
if (opt.verbose)
log_info (_("key %s: removed multiple subkey"
" revocation\n"),keystr(keyid));
}
rsnode = n;
rsdate = sig->timestamp;
}
else
n->flag |= NODE_DELETION_MARK; /* older */
}
}
}
}
return 0;
}
/* Delete all parts which are invalid and those signatures whose
* public key algorithm is not available in this implementation; but
* consider RSA as valid, because parse/build_packets knows about it.
* If R_OTHERREVSIGS is not NULL, it is used to return a list of
* revocation certificates which have been deleted from KEYBLOCK but
* should be handled later.
*
* Returns: True if at least one valid user-id is left over.
*/
static int
delete_inv_parts (ctrl_t ctrl, kbnode_t keyblock, u32 *keyid,
unsigned int options, kbnode_t *r_otherrevsigs)
{
kbnode_t node;
int nvalid=0, uid_seen=0, subkey_seen=0;
PKT_public_key *pk;
for (node=keyblock->next; node; node = node->next )
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid_seen = 1;
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
char *p=utf8_to_native(node->pkt->pkt.user_id->name,
node->pkt->pkt.user_id->len,0);
log_info( _("key %s: skipped user ID \"%s\"\n"),
keystr(keyid),p);
xfree(p);
}
delete_kbnode( node ); /* the user-id */
/* and all following packets up to the next user-id */
while (node->next
&& node->next->pkt->pkttype != PKT_USER_ID
&& node->next->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& node->next->pkt->pkttype != PKT_SECRET_SUBKEY ){
delete_kbnode( node->next );
node = node->next;
}
}
else
nvalid++;
}
else if ( node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY )
{
if ((node->flag & NODE_BAD_SELFSIG)
|| !(node->flag & NODE_GOOD_SELFSIG))
{
if (opt.verbose )
{
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, NULL);
log_info (_("key %s: skipped subkey\n"),
keystr_with_sub (keyid, pk->keyid));
}
delete_kbnode( node ); /* the subkey */
/* and all following signature packets */
while (node->next
&& node->next->pkt->pkttype == PKT_SIGNATURE ) {
delete_kbnode( node->next );
node = node->next;
}
}
else
subkey_seen = 1;
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& openpgp_pk_test_algo (node->pkt->pkt.signature->pubkey_algo)
&& node->pkt->pkt.signature->pubkey_algo != PUBKEY_ALGO_RSA )
{
delete_kbnode( node ); /* build_packet() can't handle this */
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !node->pkt->pkt.signature->flags.exportable
&& !(options&IMPORT_LOCAL_SIGS)
&& !have_secret_key_with_kid (ctrl,
node->pkt->pkt.signature->keyid))
{
/* here we violate the rfc a bit by still allowing
* to import non-exportable signature when we have the
* the secret key used to create this signature - it
* seems that this makes sense */
if(opt.verbose)
log_info( _("key %s: non exportable signature"
" (class 0x%02X) - skipped\n"),
keystr(keyid), node->pkt->pkt.signature->sig_class );
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
if (uid_seen )
{
if(opt.verbose)
log_info( _("key %s: revocation certificate"
" at wrong place - skipped\n"),keystr(keyid));
if (r_otherrevsigs)
{
PACKET *pkt;
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = copy_signature
(NULL, node->pkt->pkt.signature);
*r_otherrevsigs = new_kbnode2 (*r_otherrevsigs, pkt);
}
delete_kbnode( node );
}
else
{
/* If the revocation cert is from a different key than
the one we're working on don't check it - it's
probably from a revocation key and won't be
verifiable with this key anyway. */
if(node->pkt->pkt.signature->keyid[0]==keyid[0]
&& node->pkt->pkt.signature->keyid[1]==keyid[1])
{
int rc = check_key_signature (ctrl, keyblock, node, NULL);
if (rc )
{
if(opt.verbose)
log_info( _("key %s: invalid revocation"
" certificate: %s - skipped\n"),
keystr(keyid), gpg_strerror (rc));
delete_kbnode( node );
}
}
else if (r_otherrevsigs)
{
PACKET *pkt;
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = copy_signature
(NULL, node->pkt->pkt.signature);
*r_otherrevsigs = new_kbnode2 (*r_otherrevsigs, pkt);
}
}
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (IS_SUBKEY_SIG (node->pkt->pkt.signature)
|| IS_SUBKEY_REV (node->pkt->pkt.signature))
&& !subkey_seen )
{
if(opt.verbose)
log_info( _("key %s: subkey signature"
" in wrong place - skipped\n"), keystr(keyid));
delete_kbnode( node );
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& !IS_CERT(node->pkt->pkt.signature))
{
if(opt.verbose)
log_info(_("key %s: unexpected signature class (0x%02X) -"
" skipped\n"),keystr(keyid),
node->pkt->pkt.signature->sig_class);
delete_kbnode(node);
}
else if ((node->flag & NODE_DELETION_MARK))
delete_kbnode( node );
}
/* note: because keyblock is the public key, it is never marked
* for deletion and so keyblock cannot change */
commit_kbnode( &keyblock );
return nvalid;
}
/* This function returns true if any UID is left in the keyring. */
static int
any_uid_left (kbnode_t keyblock)
{
kbnode_t node;
for (node=keyblock->next; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
return 1;
return 0;
}
/* Delete all non-self-sigs from KEYBLOCK.
* Returns: True if the keyblock has changed. */
static void
remove_all_non_self_sigs (kbnode_t *keyblock, u32 *keyid)
{
kbnode_t node;
unsigned int dropped = 0;
for (node = *keyblock; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
if (node->pkt->pkt.signature->keyid[0] == keyid[0]
&& node->pkt->pkt.signature->keyid[1] == keyid[1])
continue;
delete_kbnode (node);
dropped++;
}
if (dropped)
commit_kbnode (keyblock);
if (dropped && opt.verbose)
log_info ("key %s: number of dropped non-self-signatures: %u\n",
keystr (keyid), dropped);
}
/*
* It may happen that the imported keyblock has duplicated user IDs.
* We check this here and collapse those user IDs together with their
* sigs into one.
* Returns: True if the keyblock has changed.
*/
int
collapse_uids (kbnode_t *keyblock)
{
kbnode_t uid1;
int any=0;
for(uid1=*keyblock;uid1;uid1=uid1->next)
{
kbnode_t uid2;
if(is_deleted_kbnode(uid1))
continue;
if(uid1->pkt->pkttype!=PKT_USER_ID)
continue;
for(uid2=uid1->next;uid2;uid2=uid2->next)
{
if(is_deleted_kbnode(uid2))
continue;
if(uid2->pkt->pkttype!=PKT_USER_ID)
continue;
if(cmp_user_ids(uid1->pkt->pkt.user_id,
uid2->pkt->pkt.user_id)==0)
{
/* We have a duplicated uid */
kbnode_t sig1,last;
any=1;
/* Now take uid2's signatures, and attach them to
uid1 */
for(last=uid2;last->next;last=last->next)
{
if(is_deleted_kbnode(last))
continue;
if(last->next->pkt->pkttype==PKT_USER_ID
|| last->next->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| last->next->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
}
/* Snip out uid2 */
(find_prev_kbnode(*keyblock,uid2,0))->next=last->next;
/* Now put uid2 in place as part of uid1 */
last->next=uid1->next;
uid1->next=uid2;
delete_kbnode(uid2);
/* Now dedupe uid1 */
for(sig1=uid1->next;sig1;sig1=sig1->next)
{
kbnode_t sig2;
if(is_deleted_kbnode(sig1))
continue;
if(sig1->pkt->pkttype==PKT_USER_ID
|| sig1->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig1->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig1->pkt->pkttype!=PKT_SIGNATURE)
continue;
for(sig2=sig1->next,last=sig1;sig2;last=sig2,sig2=sig2->next)
{
if(is_deleted_kbnode(sig2))
continue;
if(sig2->pkt->pkttype==PKT_USER_ID
|| sig2->pkt->pkttype==PKT_PUBLIC_SUBKEY
|| sig2->pkt->pkttype==PKT_SECRET_SUBKEY)
break;
if(sig2->pkt->pkttype!=PKT_SIGNATURE)
continue;
if(cmp_signatures(sig1->pkt->pkt.signature,
sig2->pkt->pkt.signature)==0)
{
/* We have a match, so delete the second
signature */
delete_kbnode(sig2);
sig2=last;
}
}
}
}
}
}
commit_kbnode(keyblock);
if(any && !opt.quiet)
{
const char *key="???";
if ((uid1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
else if ((uid1 = find_kbnode( *keyblock, PKT_SECRET_KEY)) )
key = keystr_from_pk (uid1->pkt->pkt.public_key);
log_info (_("key %s: duplicated user ID detected - merged\n"), key);
}
return any;
}
/*
* It may happen that the imported keyblock has duplicated subkeys.
* We check this here and collapse those subkeys along with their
* binding self-signatures.
* Returns: True if the keyblock has changed.
*/
int
collapse_subkeys (kbnode_t *keyblock)
{
kbnode_t kb1, kb2, sig1, sig2, last;
int any = 0;
for (kb1 = *keyblock; kb1; kb1 = kb1->next)
{
if (is_deleted_kbnode (kb1))
continue;
if (kb1->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& kb1->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
/* We assume just a few duplicates and use a straightforward
* algorithm. */
for (kb2 = kb1->next; kb2; kb2 = kb2->next)
{
if (is_deleted_kbnode (kb2))
continue;
if (kb2->pkt->pkttype != PKT_PUBLIC_SUBKEY
&& kb2->pkt->pkttype != PKT_SECRET_SUBKEY)
continue;
if (cmp_public_keys (kb1->pkt->pkt.public_key,
kb2->pkt->pkt.public_key))
continue;
/* We have a duplicated subkey. */
any = 1;
/* Take subkey-2's signatures, and attach them to subkey-1. */
for (last = kb2; last->next; last = last->next)
{
if (is_deleted_kbnode (last))
continue;
if (last->next->pkt->pkttype != PKT_SIGNATURE)
break;
}
/* Snip out subkye-2 */
find_prev_kbnode (*keyblock, kb2, 0)->next = last->next;
/* Put subkey-2 in place as part of subkey-1 */
last->next = kb1->next;
kb1->next = kb2;
delete_kbnode (kb2);
/* Now dedupe kb1 */
for (sig1 = kb1->next; sig1; sig1 = sig1->next)
{
if (is_deleted_kbnode (sig1))
continue;
if (sig1->pkt->pkttype != PKT_SIGNATURE)
break;
for (sig2 = sig1->next, last = sig1;
sig2;
last = sig2, sig2 = sig2->next)
{
if (is_deleted_kbnode (sig2))
continue;
if (sig2->pkt->pkttype != PKT_SIGNATURE)
break;
if (!cmp_signatures (sig1->pkt->pkt.signature,
sig2->pkt->pkt.signature))
{
/* We have a match, so delete the second
signature */
delete_kbnode (sig2);
sig2 = last;
}
}
}
}
}
commit_kbnode (keyblock);
if (any && !opt.quiet)
{
const char *key="???";
if ((kb1 = find_kbnode (*keyblock, PKT_PUBLIC_KEY)) )
key = keystr_from_pk (kb1->pkt->pkt.public_key);
else if ((kb1 = find_kbnode (*keyblock, PKT_SECRET_KEY)) )
key = keystr_from_pk (kb1->pkt->pkt.public_key);
log_info (_("key %s: duplicated subkeys detected - merged\n"), key);
}
return any;
}
/* Check for a 0x20 revocation from a revocation key that is not
present. This may be called without the benefit of merge_xxxx so
you can't rely on pk->revkey and friends. */
static void
revocation_present (ctrl_t ctrl, kbnode_t keyblock)
{
kbnode_t onode, inode;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
for(onode=keyblock->next;onode;onode=onode->next)
{
/* If we reach user IDs, we're done. */
if(onode->pkt->pkttype==PKT_USER_ID)
break;
if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& onode->pkt->pkt.signature->revkey)
{
int idx;
PKT_signature *sig=onode->pkt->pkt.signature;
for(idx=0;idx<sig->numrevkeys;idx++)
{
u32 keyid[2];
keyid_from_fingerprint (ctrl, sig->revkey[idx].fpr,
sig->revkey[idx].fprlen, keyid);
for(inode=keyblock->next;inode;inode=inode->next)
{
/* If we reach user IDs, we're done. */
if(inode->pkt->pkttype==PKT_USER_ID)
break;
if (inode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (inode->pkt->pkt.signature)
&& inode->pkt->pkt.signature->keyid[0]==keyid[0]
&& inode->pkt->pkt.signature->keyid[1]==keyid[1])
{
/* Okay, we have a revocation key, and a
* revocation issued by it. Do we have the key
* itself? */
gpg_error_t err;
err = get_pubkey_byfprint_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
{
char *tempkeystr = xstrdup (keystr_from_pk (pk));
/* No, so try and get it */
if ((opt.keyserver_options.options
& KEYSERVER_AUTO_KEY_RETRIEVE)
&& keyserver_any_configured (ctrl))
{
log_info(_("WARNING: key %s may be revoked:"
" fetching revocation key %s\n"),
tempkeystr,keystr(keyid));
keyserver_import_fprint (ctrl,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen,
opt.keyserver, 0);
/* Do we have it now? */
err = get_pubkey_byfprint_fast (ctrl, NULL,
sig->revkey[idx].fpr,
sig->revkey[idx].fprlen);
}
if (gpg_err_code (err) == GPG_ERR_NO_PUBKEY
|| gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
log_info(_("WARNING: key %s may be revoked:"
" revocation key %s not present.\n"),
tempkeystr,keystr(keyid));
xfree(tempkeystr);
}
}
}
}
}
}
}
/*
* compare and merge the blocks
*
* o compare the signatures: If we already have this signature, check
* that they compare okay; if not, issue a warning and ask the user.
* o Simply add the signature. Can't verify here because we may not have
* the signature's public key yet; verification is done when putting it
* into the trustdb, which is done automagically as soon as this pubkey
* is used.
* Note: We indicate newly inserted packets with NODE_FLAG_A.
*/
static int
merge_blocks (ctrl_t ctrl, unsigned int options,
kbnode_t keyblock_orig, kbnode_t keyblock,
u32 *keyid, u32 curtime, int origin, const char *url,
int *n_uids, int *n_sigs, int *n_subk )
{
kbnode_t onode, node;
int rc, found;
/* 1st: handle revocation certificates */
for (node=keyblock->next; node; node=node->next )
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID )
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_REV (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found)
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
{
char *p = get_user_id_native (ctrl, keyid);
log_info(_("key %s: \"%s\" revocation"
" certificate added\n"), keystr(keyid),p);
xfree(p);
}
}
}
}
/* 2nd: merge in any direct key (0x1F) sigs */
for(node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID )
break;
else if (node->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (node->pkt->pkt.signature))
{
/* check whether we already have this */
found = 0;
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (onode->pkt->pkttype == PKT_USER_ID)
break;
else if (onode->pkt->pkttype == PKT_SIGNATURE
&& IS_KEY_SIG (onode->pkt->pkt.signature)
&& !cmp_signatures(onode->pkt->pkt.signature,
node->pkt->pkt.signature))
{
found = 1;
break;
}
}
if (!found )
{
kbnode_t n2 = clone_kbnode(node);
insert_kbnode( keyblock_orig, n2, 0 );
n2->flag |= NODE_FLAG_A;
++*n_sigs;
if(!opt.quiet)
log_info( _("key %s: direct key signature added\n"),
keystr(keyid));
}
}
}
/* 3rd: try to merge new certificates in */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A) && onode->pkt->pkttype == PKT_USER_ID)
{
/* find the user id in the imported keyblock */
for (node=keyblock->next; node; node=node->next)
if (node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (node ) /* found: merge */
{
rc = merge_sigs (onode, node, n_sigs);
if (rc )
return rc;
}
}
}
/* 4th: add new user-ids */
for (node=keyblock->next; node; node=node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
/* do we have this in the original keyblock */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids( onode->pkt->pkt.user_id,
node->pkt->pkt.user_id ) )
break;
if (!onode ) /* this is a new user id: append */
{
rc = append_new_uid (options, keyblock_orig, node,
curtime, origin, url, n_sigs);
if (rc )
return rc;
++*n_uids;
}
}
}
/* 5th: add new subkeys */
for (node=keyblock->next; node; node=node->next)
{
onode = NULL;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
/* do we have this in the original keyblock? */
for(onode=keyblock_orig->next; onode; onode=onode->next)
if (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key))
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc)
return rc;
++*n_subk;
}
}
else if (node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
/* do we have this in the original keyblock? */
for (onode=keyblock_orig->next; onode; onode=onode->next )
if (onode->pkt->pkttype == PKT_SECRET_SUBKEY
&& !cmp_public_keys (onode->pkt->pkt.public_key,
node->pkt->pkt.public_key) )
break;
if (!onode ) /* This is a new subkey: append. */
{
rc = append_key (keyblock_orig, node, n_sigs);
if (rc )
return rc;
++*n_subk;
}
}
}
/* 6th: merge subkey certificates */
for (onode=keyblock_orig->next; onode; onode=onode->next)
{
if (!(onode->flag & NODE_FLAG_A)
&& (onode->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| onode->pkt->pkttype == PKT_SECRET_SUBKEY))
{
/* find the subkey in the imported keyblock */
for(node=keyblock->next; node; node=node->next)
{
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
&& !cmp_public_keys( onode->pkt->pkt.public_key,
node->pkt->pkt.public_key ) )
break;
}
if (node) /* Found: merge. */
{
rc = merge_keysigs( onode, node, n_sigs);
if (rc )
return rc;
}
}
}
return 0;
}
/* Helper function for merge_blocks.
*
* Append the new userid starting with NODE and all signatures to
* KEYBLOCK. ORIGIN and URL conveys the usual key origin info. The
* integer at N_SIGS is updated with the number of new signatures.
*/
static gpg_error_t
append_new_uid (unsigned int options,
kbnode_t keyblock, kbnode_t node, u32 curtime,
int origin, const char *url, int *n_sigs)
{
gpg_error_t err;
kbnode_t n;
kbnode_t n_where = NULL;
log_assert (node->pkt->pkttype == PKT_USER_ID);
/* Find the right position for the new user id and its signatures. */
for (n = keyblock; n; n_where = n, n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_SECRET_SUBKEY )
break;
}
if (!n)
n_where = NULL;
/* and append/insert */
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first. */
n = clone_kbnode(node);
if (n->pkt->pkttype == PKT_USER_ID
&& !(options & IMPORT_RESTORE) )
{
err = insert_key_origin_uid (n->pkt->pkt.user_id,
curtime, origin, url);
if (err)
{
release_kbnode (n);
return err;
}
}
if (n_where)
{
insert_kbnode( n_where, n, 0 );
n_where = n;
}
else
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_USER_ID.
* (how should we handle comment packets here?)
*/
static int
merge_sigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_USER_ID);
log_assert (src->pkt->pkttype == PKT_USER_ID);
for (n=src->next; n && n->pkt->pkttype != PKT_USER_ID; n = n->next)
{
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
if (IS_SUBKEY_SIG (n->pkt->pkt.signature)
|| IS_SUBKEY_REV (n->pkt->pkt.signature) )
continue; /* skip signatures which are only valid on subkeys */
found = 0;
for (n2=dst->next; n2 && n2->pkt->pkttype != PKT_USER_ID; n2 = n2->next)
if (!cmp_signatures(n->pkt->pkt.signature,n2->pkt->pkt.signature))
{
found++;
break;
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks
* Merge the sigs from SRC onto DST. SRC and DST are both a PKT_xxx_SUBKEY.
*/
static int
merge_keysigs (kbnode_t dst, kbnode_t src, int *n_sigs)
{
kbnode_t n, n2;
int found = 0;
log_assert (dst->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| dst->pkt->pkttype == PKT_SECRET_SUBKEY);
for (n=src->next; n ; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n->pkt->pkttype != PKT_SIGNATURE )
continue;
found = 0;
for (n2=dst->next; n2; n2 = n2->next)
{
if (n2->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n2->pkt->pkttype == PKT_PUBLIC_KEY )
break;
if (n2->pkt->pkttype == PKT_SIGNATURE
&& (n->pkt->pkt.signature->keyid[0]
== n2->pkt->pkt.signature->keyid[0])
&& (n->pkt->pkt.signature->keyid[1]
== n2->pkt->pkt.signature->keyid[1])
&& (n->pkt->pkt.signature->timestamp
<= n2->pkt->pkt.signature->timestamp)
&& (n->pkt->pkt.signature->sig_class
== n2->pkt->pkt.signature->sig_class))
{
found++;
break;
}
}
if (!found )
{
/* This signature is new or newer, append N to DST.
* We add a clone to the original keyblock, because this
* one is released first */
n2 = clone_kbnode(n);
insert_kbnode( dst, n2, PKT_SIGNATURE );
n2->flag |= NODE_FLAG_A;
n->flag |= NODE_FLAG_A;
++*n_sigs;
}
}
return 0;
}
/* Helper function for merge_blocks.
* Append the subkey starting with NODE and all signatures to KEYBLOCK.
* Mark all new and copied packets by setting flag bit 0.
*/
static int
append_key (kbnode_t keyblock, kbnode_t node, int *n_sigs)
{
kbnode_t n;
log_assert (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
while (node)
{
/* we add a clone to the original keyblock, because this
* one is released first */
n = clone_kbnode(node);
add_kbnode( keyblock, n );
n->flag |= NODE_FLAG_A;
node->flag |= NODE_FLAG_A;
if (n->pkt->pkttype == PKT_SIGNATURE )
++*n_sigs;
node = node->next;
if (node && node->pkt->pkttype != PKT_SIGNATURE )
break;
}
return 0;
}
diff --git a/g10/key-clean.c b/g10/key-clean.c
index ca8ca40d9..16d725079 100644
--- a/g10/key-clean.c
+++ b/g10/key-clean.c
@@ -1,645 +1,645 @@
/* key-clean.c - Functions to clean a keyblock
* Copyright (C) 1998-2008, 2010-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2016-2018 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "keydb.h"
#include "../common/util.h"
#include "../common/host2net.h"
#include "../common/i18n.h"
#include "options.h"
#include "packet.h"
#include "main.h"
#include "key-clean.h"
#define NF_USABLE 8 /* Usable signature and not a revocation. */
#define NF_CONSIDER 9 /* Internal use. */
#define NF_PROCESSED 10 /* Internal use. */
#define NF_REVOC 11 /* Usable revocation. */
#define NF_NOKEY 12 /* Key not available. */
/*
* Mark the signature of the given UID which are used to certify it.
* To do this, we first remove all signatures which are not valid and
* from the remaining we look for the latest one. If this is not a
* certification revocation signature we mark the signature by setting
* node flag bit NF_USABLE. Revocations are marked with NF_REVOC, and
* sigs from unavailable keys are marked with NF_NOKEY.
*/
void
mark_usable_uid_certs (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode,
u32 *main_kid, struct key_item *klist,
u32 curtime, u32 *next_expire)
{
kbnode_t node;
PKT_signature *sig;
/* First check all signatures. */
for (node=uidnode->next; node; node = node->next)
{
int rc;
node->flag &= ~(1<<NF_USABLE | 1<<NF_CONSIDER
| 1<<NF_PROCESSED | 1<<NF_REVOC | 1<<NF_NOKEY);
if (node->pkt->pkttype == PKT_USER_ID
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break; /* ready */
if (node->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = node->pkt->pkt.signature;
if (main_kid
&& sig->keyid[0] == main_kid[0] && sig->keyid[1] == main_kid[1])
continue; /* ignore self-signatures if we pass in a main_kid */
if (!IS_UID_SIG(sig) && !IS_UID_REV(sig))
continue; /* we only look at these signature classes */
if(sig->sig_class>=0x11 && sig->sig_class<=0x13 &&
sig->sig_class-0x10<opt.min_cert_level)
continue; /* treat anything under our min_cert_level as an
invalid signature */
if (klist && !is_in_klist (klist, sig))
continue; /* no need to check it then */
if ((rc=check_key_signature (ctrl, keyblock, node, NULL)))
{
/* we ignore anything that won't verify, but tag the
no_pubkey case */
if (gpg_err_code (rc) == GPG_ERR_NO_PUBKEY)
node->flag |= 1<<NF_NOKEY;
continue;
}
node->flag |= 1<<NF_CONSIDER;
}
/* Reset the remaining flags. */
for (; node; node = node->next)
node->flag &= ~(1<<NF_USABLE | 1<<NF_CONSIDER
| 1<<NF_PROCESSED | 1<<NF_REVOC | 1<<NF_NOKEY);
/* kbnode flag usage: bit NF_CONSIDER is here set for signatures to consider,
* bit NF_PROCESSED will be set by the loop to keep track of keyIDs already
* processed, bit NF_USABLE will be set for the usable signatures, and bit
* NF_REVOC will be set for usable revocations. */
/* For each cert figure out the latest valid one. */
for (node=uidnode->next; node; node = node->next)
{
KBNODE n, signode;
u32 kid[2];
u32 sigdate;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
break;
if ( !(node->flag & (1<<NF_CONSIDER)) )
continue; /* not a node to look at */
if ( (node->flag & (1<<NF_PROCESSED)) )
continue; /* signature with a keyID already processed */
node->flag |= (1<<NF_PROCESSED); /* mark this node as processed */
sig = node->pkt->pkt.signature;
signode = node;
sigdate = sig->timestamp;
kid[0] = sig->keyid[0]; kid[1] = sig->keyid[1];
/* Now find the latest and greatest signature */
for (n=uidnode->next; n; n = n->next)
{
if (n->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| n->pkt->pkttype == PKT_SECRET_SUBKEY)
break;
if ( !(n->flag & (1<<NF_CONSIDER)) )
continue;
if ( (n->flag & (1<<NF_PROCESSED)) )
continue; /* shortcut already processed signatures */
sig = n->pkt->pkt.signature;
if (kid[0] != sig->keyid[0] || kid[1] != sig->keyid[1])
continue;
n->flag |= (1<<NF_PROCESSED); /* mark this node as processed */
/* If signode is nonrevocable and unexpired and n isn't,
then take signode (skip). It doesn't matter which is
older: if signode was older then we don't want to take n
as signode is nonrevocable. If n was older then we're
automatically fine. */
if(((IS_UID_SIG(signode->pkt->pkt.signature) &&
!signode->pkt->pkt.signature->flags.revocable &&
(signode->pkt->pkt.signature->expiredate==0 ||
signode->pkt->pkt.signature->expiredate>curtime))) &&
(!(IS_UID_SIG(n->pkt->pkt.signature) &&
!n->pkt->pkt.signature->flags.revocable &&
(n->pkt->pkt.signature->expiredate==0 ||
n->pkt->pkt.signature->expiredate>curtime))))
continue;
/* If n is nonrevocable and unexpired and signode isn't,
then take n. Again, it doesn't matter which is older: if
n was older then we don't want to take signode as n is
nonrevocable. If signode was older then we're
automatically fine. */
if((!(IS_UID_SIG(signode->pkt->pkt.signature) &&
!signode->pkt->pkt.signature->flags.revocable &&
(signode->pkt->pkt.signature->expiredate==0 ||
signode->pkt->pkt.signature->expiredate>curtime))) &&
((IS_UID_SIG(n->pkt->pkt.signature) &&
!n->pkt->pkt.signature->flags.revocable &&
(n->pkt->pkt.signature->expiredate==0 ||
n->pkt->pkt.signature->expiredate>curtime))))
{
signode = n;
sigdate = sig->timestamp;
continue;
}
/* At this point, if it's newer, it goes in as the only
remaining possibilities are signode and n are both either
revocable or expired or both nonrevocable and unexpired.
If the timestamps are equal take the later ordered
packet, presuming that the key packets are hopefully in
their original order. */
if (sig->timestamp >= sigdate)
{
signode = n;
sigdate = sig->timestamp;
}
}
sig = signode->pkt->pkt.signature;
if (IS_UID_SIG (sig))
{ /* this seems to be a usable one which is not revoked.
* Just need to check whether there is an expiration time,
* We do the expired certification after finding a suitable
* certification, the assumption is that a signator does not
* want that after the expiration of his certificate the
* system falls back to an older certification which has a
* different expiration time */
const byte *p;
u32 expire;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_SIG_EXPIRE, NULL );
expire = p? sig->timestamp + buf32_to_u32(p) : 0;
if (expire==0 || expire > curtime )
{
signode->flag |= (1<<NF_USABLE); /* yeah, found a good cert */
if (next_expire && expire && expire < *next_expire)
*next_expire = expire;
}
}
else
signode->flag |= (1<<NF_REVOC);
}
}
/* Return true if the signature at NODE has is from a key specified by
* the --trusted-key option and is exportable. */
static int
is_trusted_key_sig (kbnode_t node)
{
if (!node->pkt->pkt.signature->flags.exportable)
return 0;
/* Not yet implemented. */
return 0;
}
/* Note: OPTIONS are from the EXPORT_* set. */
static int
clean_sigs_from_uid (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode,
int noisy, unsigned int options)
{
int deleted = 0;
kbnode_t node;
u32 keyid[2];
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
keyid_from_pk (keyblock->pkt->pkt.public_key, keyid);
/* Passing in a 0 for current time here means that we'll never weed
out an expired sig. This is correct behavior since we want to
keep the most recent expired sig in a series. */
mark_usable_uid_certs (ctrl, keyblock, uidnode, NULL, NULL, 0, NULL);
/* What we want to do here is remove signatures that are not
considered as part of the trust calculations. Thus, all invalid
signatures are out, as are any signatures that aren't the last of
a series of uid sigs or revocations It breaks down like this:
coming out of mark_usable_uid_certs, if a sig is unflagged, it is
not even a candidate. If a sig has flag NF_CONSIDER or
NF_PROCESSED, that means it was selected as a candidate and
vetted. If a sig has flag NF_USABLE it is a usable signature.
If a sig has flag NF_REVOC it is a usable revocation. If a sig
has flag NF_NOKEY it was issued by an unavailable key. "Usable"
here means the most recent valid signature/revocation in a series
from a particular signer.
Delete everything that isn't a usable uid sig (which might be
expired), a usable revocation, or a sig from an unavailable
key. */
for (node=uidnode->next;
node && node->pkt->pkttype==PKT_SIGNATURE;
node=node->next)
{
int keep;
if ((options & EXPORT_REALCLEAN))
keep = ((node->pkt->pkt.signature->keyid[0] == keyid[0]
&& node->pkt->pkt.signature->keyid[1] == keyid[1])
|| is_trusted_key_sig (node));
else if ((options & EXPORT_MINIMAL))
keep = (node->pkt->pkt.signature->keyid[0] == keyid[0]
&& node->pkt->pkt.signature->keyid[1] == keyid[1]);
else
keep = 1;
/* Keep usable uid sigs ... */
if ((node->flag & (1<<NF_USABLE)) && keep)
continue;
/* ... and usable revocations... */
if ((node->flag & (1<<NF_REVOC)) && keep)
continue;
/* ... and sigs from unavailable keys. */
/* disabled for now since more people seem to want sigs from
unavailable keys removed altogether. */
/*
if(node->flag & (1<<NF_NOKEY))
continue;
*/
/* Everything else we delete */
/* At this point, if NF_NOKEY is set, the signing key was
* unavailable. If NF_CONSIDER or NF_PROCESSED is set, it's
* superseded. Otherwise, it's invalid. */
if (noisy)
log_info ("removing signature from key %s on user ID \"%s\": %s\n",
keystr (node->pkt->pkt.signature->keyid),
uidnode->pkt->pkt.user_id->name,
node->flag&(1<<NF_NOKEY)? "key unavailable":
node->flag&(1<<NF_CONSIDER)? "signature superseded"
/* */ : "invalid signature" );
delete_kbnode (node);
deleted++;
}
return deleted;
}
/* This is substantially easier than clean_sigs_from_uid since we just
have to establish if the uid has a valid self-sig, is not revoked,
and is not expired. Note that this does not take into account
whether the uid has a trust path to it - just whether the keyholder
themselves has certified the uid. Returns true if the uid was
compacted. To "compact" a user ID, we simply remove ALL signatures
except the self-sig that caused the user ID to be remove-worthy.
We don't actually remove the user ID packet itself since it might
be resurrected in a later merge. Note that this function requires
that the caller has already done a merge_keys_and_selfsig().
TODO: change the import code to allow importing a uid with only a
revocation if the uid already exists on the keyring. */
static int
clean_uid_from_key (kbnode_t keyblock, kbnode_t uidnode, int noisy)
{
kbnode_t node;
PKT_user_id *uid = uidnode->pkt->pkt.user_id;
int deleted = 0;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
log_assert (uidnode->pkt->pkttype==PKT_USER_ID);
/* Skip valid user IDs, compacted user IDs, and non-self-signed user
IDs if --allow-non-selfsigned-uid is set. */
if (uid->created
|| uid->flags.compacted
|| (!uid->flags.expired && !uid->flags.revoked && opt.allow_non_selfsigned_uid))
return 0;
for (node=uidnode->next;
node && node->pkt->pkttype == PKT_SIGNATURE;
node=node->next)
{
if (!node->pkt->pkt.signature->flags.chosen_selfsig)
{
delete_kbnode (node);
deleted = 1;
uidnode->pkt->pkt.user_id->flags.compacted = 1;
}
}
if (noisy)
{
const char *reason;
char *user = utf8_to_native (uid->name, uid->len, 0);
if (uid->flags.revoked)
reason = _("revoked");
else if (uid->flags.expired)
reason = _("expired");
else
reason = _("invalid");
log_info ("compacting user ID \"%s\" on key %s: %s\n",
user, keystr_from_pk (keyblock->pkt->pkt.public_key),
reason);
xfree (user);
}
return deleted;
}
/* Needs to be called after a merge_keys_and_selfsig().
* Note: OPTIONS are from the EXPORT_* set. */
void
clean_one_uid (ctrl_t ctrl, kbnode_t keyblock, kbnode_t uidnode,
int noisy, unsigned int options,
int *uids_cleaned, int *sigs_cleaned)
{
int dummy = 0;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
log_assert (uidnode->pkt->pkttype==PKT_USER_ID);
if (!uids_cleaned)
uids_cleaned = &dummy;
if (!sigs_cleaned)
sigs_cleaned = &dummy;
/* Do clean_uid_from_key first since if it fires off, we don't have
to bother with the other. */
*uids_cleaned += clean_uid_from_key (keyblock, uidnode, noisy);
if (!uidnode->pkt->pkt.user_id->flags.compacted)
*sigs_cleaned += clean_sigs_from_uid (ctrl, keyblock, uidnode,
noisy, options);
}
/* NB: This function marks the deleted nodes only and the caller is
* responsible to skip or remove them. Needs to be called after a
* merge_keys_and_selfsig. Note: OPTIONS are from the EXPORT_* set. */
void
clean_all_uids (ctrl_t ctrl, kbnode_t keyblock, int noisy, unsigned int options,
int *uids_cleaned, int *sigs_cleaned)
{
kbnode_t node;
for (node = keyblock->next;
node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
clean_one_uid (ctrl, keyblock, node, noisy, options,
uids_cleaned, sigs_cleaned);
}
/* Remove bogus subkey binding signatures: The only signatures
* allowed are of class 0x18 and 0x28. */
log_assert (!node || (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY));
}
/* Helper for clean_all_subkeys. */
static int
clean_one_subkey (ctrl_t ctrl, kbnode_t subkeynode, int noisy, int clean_level)
{
kbnode_t node;
PKT_public_key *pk = subkeynode->pkt->pkt.public_key;
unsigned int use = pk->pubkey_usage;
int do_clean = 0;
(void)ctrl;
(void)noisy;
log_assert (subkeynode->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| subkeynode->pkt->pkttype == PKT_SECRET_SUBKEY);
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX [%c%c%c%c%c]\n",
(ulong) keyid_from_pk (pk, NULL),
(use & PUBKEY_USAGE_ENC)? 'e':'-',
(use & PUBKEY_USAGE_SIG)? 's':'-',
(use & PUBKEY_USAGE_CERT)? 'c':'-',
(use & PUBKEY_USAGE_AUTH)? 'a':'-',
(use & PUBKEY_USAGE_UNKNOWN)? '?':'-');
if (!pk->flags.valid)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey not valid\n");
if (clean_level == KEY_CLEAN_INVALID)
do_clean = 1;
}
if (pk->has_expired)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has expired\n");
if (clean_level == KEY_CLEAN_ALL)
do_clean = 1;
else if (clean_level == KEY_CLEAN_AUTHENCR
&& (use & (PUBKEY_USAGE_ENC | PUBKEY_USAGE_AUTH))
&& !(use & (PUBKEY_USAGE_SIG | PUBKEY_USAGE_CERT)))
do_clean = 1;
else if (clean_level == KEY_CLEAN_ENCR
&& (use & PUBKEY_USAGE_ENC)
&& !(use & (PUBKEY_USAGE_SIG | PUBKEY_USAGE_CERT
| PUBKEY_USAGE_AUTH)))
do_clean = 1;
}
if (pk->flags.revoked)
{
if (DBG_LOOKUP)
log_debug ("\tsubkey has been revoked (keeping)\n");
/* Avoid any cleaning because revocations are important. */
do_clean = 0;
}
if (!do_clean)
return 0;
if (DBG_LOOKUP)
log_debug ("\t=> removing this subkey\n");
delete_kbnode (subkeynode);
for (node = subkeynode->next;
node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
node = node->next)
delete_kbnode (node);
return 1;
}
/* Helper for clean_all_subkeys. Here duplicate signatures from a
* subkey are removed. This should in general not happen because
* import takes care of that. However, sometimes other tools are used
* to manage a keyring or key has been imported a long time ago. */
static int
clean_one_subkey_dupsigs (ctrl_t ctrl, kbnode_t subkeynode)
{
kbnode_t node;
PKT_public_key *pk = subkeynode->pkt->pkt.public_key;
int any_choosen = 0;
int count = 0;
(void)ctrl;
log_assert (subkeynode->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| subkeynode->pkt->pkttype == PKT_SECRET_SUBKEY);
if (DBG_LOOKUP)
log_debug ("\tchecking subkey %08lX for dupsigs\n",
(ulong) keyid_from_pk (pk, NULL));
/* First check that the chosen flag has been set. Note that we
* only look at plain signatures so to keep all revocation
* signatures which may carry important information. */
for (node = subkeynode->next;
node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
node = node->next)
{
if (!is_deleted_kbnode (node)
&& node->pkt->pkttype == PKT_SIGNATURE
&& IS_SUBKEY_SIG (node->pkt->pkt.signature)
&& node->pkt->pkt.signature->flags.chosen_selfsig)
{
any_choosen = 1;
break;
}
}
if (!any_choosen)
return 0; /* Ooops no chosen flag set - we can't decide. */
for (node = subkeynode->next;
node && !(node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY);
node = node->next)
{
if (!is_deleted_kbnode (node)
&& node->pkt->pkttype == PKT_SIGNATURE
&& IS_SUBKEY_SIG (node->pkt->pkt.signature)
&& !node->pkt->pkt.signature->flags.chosen_selfsig)
{
delete_kbnode (node);
count++;
}
}
return count;
}
/* This function only marks the deleted nodes and the caller is
* responsible to skip or remove them. Needs to be called after a
* merge_keys_and_selfsig. CLEAN_LEVEL is one of the KEY_CLEAN_*
* values. */
void
clean_all_subkeys (ctrl_t ctrl, kbnode_t keyblock, int noisy, int clean_level,
int *subkeys_cleaned, int *sigs_cleaned)
{
kbnode_t first_subkey, node;
int n;
if (DBG_LOOKUP)
log_debug ("clean_all_subkeys: checking key %08lX\n",
(ulong) keyid_from_pk (keyblock->pkt->pkt.public_key, NULL));
for (node = keyblock->next; node; node = node->next)
if (!is_deleted_kbnode (node)
&& (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY))
break;
first_subkey = node;
/* Remove bogus subkey binding signatures: The only signatures
* allowed are of class 0x18 and 0x28. */
for (node = first_subkey; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_SIGNATURE
&& !(IS_SUBKEY_SIG (node->pkt->pkt.signature)
|| IS_SUBKEY_REV (node->pkt->pkt.signature)))
{
delete_kbnode (node);
if (sigs_cleaned)
++*sigs_cleaned;
}
}
/* Do the selected cleaning. */
if (clean_level > KEY_CLEAN_NONE)
{
- /* Clean enitre subkeys. */
+ /* Clean entire subkeys. */
for (node = first_subkey; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
if (clean_one_subkey (ctrl, node, noisy, clean_level))
{
if (subkeys_cleaned)
++*subkeys_cleaned;
}
}
}
/* Clean duplicate signatures from a subkey. */
for (node = first_subkey; node; node = node->next)
{
if (is_deleted_kbnode (node))
continue;
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
n = clean_one_subkey_dupsigs (ctrl, node);
if (sigs_cleaned)
*sigs_cleaned += n;
}
}
}
}
diff --git a/g10/keyedit.c b/g10/keyedit.c
index 81ea06c24..f41e53f6d 100644
--- a/g10/keyedit.c
+++ b/g10/keyedit.c
@@ -1,6956 +1,6956 @@
/* keyedit.c - Edit properties of a key
* Copyright (C) 1998-2010 Free Software Foundation, Inc.
* Copyright (C) 1998-2017 Werner Koch
* Copyright (C) 2015, 2016, 2022-2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "photoid.h"
#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "../common/host2net.h"
#include "tofu.h"
#include "key-check.h"
#include "key-clean.h"
#include "keyedit.h"
static void show_prefs (PKT_user_id * uid, PKT_signature * selfsig,
int verbose);
static void show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk,
unsigned int flag, int with_prefs);
static void show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked,
int with_revoker, int with_fpr,
int with_subkeys, int with_prefs,
int nowarn);
static void show_key_and_fingerprint (ctrl_t ctrl,
kbnode_t keyblock, int with_subkeys);
static void show_key_and_grip (kbnode_t keyblock);
static void subkey_expire_warning (kbnode_t keyblock);
static int menu_adduid (ctrl_t ctrl, kbnode_t keyblock,
int photo, const char *photo_name, const char *uidstr);
static void menu_deluid (KBNODE pub_keyblock);
static int menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_clean (ctrl_t ctrl, kbnode_t keyblock, unsigned int options);
static void menu_delkey (KBNODE pub_keyblock);
static int menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive);
static int menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock,
const char *adskfpr);
static gpg_error_t menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock,
int unattended, u32 newexpiration);
static int menu_changeusage (ctrl_t ctrl, kbnode_t keyblock);
static int menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock,
int unattended);
static int menu_set_keyserver_url (ctrl_t ctrl,
const char *url, kbnode_t pub_keyblock);
static int menu_set_notation (ctrl_t ctrl,
const char *string, kbnode_t pub_keyblock);
static int menu_select_uid (KBNODE keyblock, int idx);
static int menu_select_uid_namehash (KBNODE keyblock, const char *namehash);
static int menu_select_key (KBNODE keyblock, int idx, char *p);
static int count_uids (KBNODE keyblock);
static int count_uids_with_flag (KBNODE keyblock, unsigned flag);
static int count_keys_with_flag (KBNODE keyblock, unsigned flag);
static int count_selected_uids (KBNODE keyblock);
static int real_uids_left (KBNODE keyblock);
static int count_selected_keys (KBNODE keyblock);
static int menu_revsig (ctrl_t ctrl, kbnode_t keyblock);
static int menu_revuid (ctrl_t ctrl, kbnode_t keyblock);
static int core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason,
int *modified);
static int menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock);
static int menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock);
#ifndef NO_TRUST_MODELS
static int enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable);
#endif /*!NO_TRUST_MODELS*/
static void menu_showphoto (ctrl_t ctrl, kbnode_t keyblock);
static int update_trust = 0;
#define CONTROL_D ('D' - 'A' + 1)
struct sign_attrib
{
int non_exportable, non_revocable;
struct revocation_reason_info *reason;
byte trust_depth, trust_value;
char *trust_regexp;
};
/* TODO: Fix duplicated code between here and the check-sigs/list-sigs
code in keylist.c. */
static int
print_and_check_one_sig_colon (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key)
{
PKT_signature *sig = node->pkt->pkt.signature;
int rc, sigrc;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
rc = check_key_signature (ctrl, keyblock, node, is_selfsig);
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
es_printf ("sig:%c::%d:%08lX%08lX:%lu:%lu:",
sigrc, sig->pubkey_algo, (ulong) sig->keyid[0],
(ulong) sig->keyid[1], (ulong) sig->timestamp,
(ulong) sig->expiredate);
if (sig->trust_depth || sig->trust_value)
es_printf ("%d %d", sig->trust_depth, sig->trust_value);
es_printf (":");
if (sig->trust_regexp)
es_write_sanitized (es_stdout,
sig->trust_regexp, strlen (sig->trust_regexp),
":", NULL);
es_printf ("::%02x%c\n", sig->sig_class,
sig->flags.exportable ? 'x' : 'l');
if (opt.show_subpackets)
print_subpackets_colon (sig);
}
return (sigrc == '!');
}
/*
* Print information about a signature (rc is its status), check it
* and return true if the signature is okay. NODE must be a signature
* packet. With EXTENDED set all possible signature list options will
* always be printed.
*/
int
keyedit_print_one_sig (ctrl_t ctrl, estream_t fp,
int rc, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int is_selfsig, int print_without_key, int extended)
{
PKT_signature *sig = node->pkt->pkt.signature;
int sigrc;
int is_rev = sig->sig_class == 0x30;
/* TODO: Make sure a cached sig record here still has the pk that
issued it. See also keylist.c:list_keyblock_print */
switch (gpg_err_code (rc))
{
case 0:
node->flag &= ~(NODFLG_BADSIG | NODFLG_NOKEY | NODFLG_SIGERR);
sigrc = '!';
break;
case GPG_ERR_BAD_SIGNATURE:
node->flag = NODFLG_BADSIG;
sigrc = '-';
if (inv_sigs)
++ * inv_sigs;
break;
case GPG_ERR_NO_PUBKEY:
case GPG_ERR_UNUSABLE_PUBKEY:
node->flag = NODFLG_NOKEY;
sigrc = '?';
if (no_key)
++ * no_key;
break;
default:
node->flag = NODFLG_SIGERR;
sigrc = '%';
if (oth_err)
++ * oth_err;
break;
}
if (sigrc != '?' || print_without_key)
{
tty_fprintf (fp, "%s%c%c %c%c%c%c%c%c %s %s",
is_rev ? "rev" : "sig", sigrc,
(sig->sig_class - 0x10 > 0 &&
sig->sig_class - 0x10 <
4) ? '0' + sig->sig_class - 0x10 : ' ',
sig->flags.exportable ? ' ' : 'L',
sig->flags.revocable ? ' ' : 'R',
sig->flags.policy_url ? 'P' : ' ',
sig->flags.notation ? 'N' : ' ',
sig->flags.expired ? 'X' : ' ',
(sig->trust_depth > 9) ? 'T' : (sig->trust_depth >
0) ? '0' +
sig->trust_depth : ' ',
keystr (sig->keyid),
datestr_from_sig (sig));
if ((opt.list_options & LIST_SHOW_SIG_EXPIRE) || extended )
tty_fprintf (fp, " %s", expirestr_from_sig (sig));
tty_fprintf (fp, " ");
if (sigrc == '%')
tty_fprintf (fp, "[%s] ", gpg_strerror (rc));
else if (sigrc == '?')
;
else if (is_selfsig)
{
tty_fprintf (fp, is_rev ? _("[revocation]") : _("[self-signature]"));
if (extended && sig->flags.chosen_selfsig)
tty_fprintf (fp, "*");
}
else
{
size_t n;
char *p = get_user_id (ctrl, sig->keyid, &n, NULL);
tty_print_utf8_string2 (fp, p, n,
opt.screen_columns - keystrlen () - 26 -
((opt.
list_options & LIST_SHOW_SIG_EXPIRE) ? 11
: 0));
xfree (p);
}
if (fp == log_get_stream ())
log_printf ("\n");
else
tty_fprintf (fp, "\n");
if (sig->flags.policy_url
&& ((opt.list_options & LIST_SHOW_POLICY_URLS) || extended))
show_policy_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0));
if (sig->flags.notation
&& ((opt.list_options & LIST_SHOW_NOTATIONS) || extended))
show_notation (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0),
((opt.
list_options & LIST_SHOW_STD_NOTATIONS) ? 1 : 0) +
((opt.
list_options & LIST_SHOW_USER_NOTATIONS) ? 2 : 0));
if (sig->flags.pref_ks
&& ((opt.list_options & LIST_SHOW_KEYSERVER_URLS) || extended))
show_keyserver_url (sig, 3, (!fp? -1 : fp == log_get_stream ()? 1 : 0));
if (extended)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
const unsigned char *s;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_PRIMARY_UID, NULL);
if (s && *s)
tty_fprintf (fp, " [primary]\n");
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_KEY_EXPIRE, NULL);
if (s && buf32_to_u32 (s))
tty_fprintf (fp, " [expires: %s]\n",
isotimestamp (pk->timestamp + buf32_to_u32 (s)));
}
}
return (sigrc == '!');
}
static int
print_and_check_one_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node,
int *inv_sigs, int *no_key, int *oth_err,
int *is_selfsig, int print_without_key, int extended)
{
int rc;
rc = check_key_signature (ctrl, keyblock, node, is_selfsig);
return keyedit_print_one_sig (ctrl, NULL, rc,
keyblock, node, inv_sigs, no_key, oth_err,
*is_selfsig, print_without_key, extended);
}
static int
sign_mk_attrib (PKT_signature * sig, void *opaque)
{
struct sign_attrib *attrib = opaque;
byte buf[8];
if (attrib->non_exportable)
{
buf[0] = 0; /* not exportable */
build_sig_subpkt (sig, SIGSUBPKT_EXPORTABLE, buf, 1);
}
if (attrib->non_revocable)
{
buf[0] = 0; /* not revocable */
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
}
if (attrib->reason)
revocation_reason_build_cb (sig, attrib->reason);
if (attrib->trust_depth)
{
/* Not critical. If someone doesn't understand trust sigs,
this can still be a valid regular signature. */
buf[0] = attrib->trust_depth;
buf[1] = attrib->trust_value;
build_sig_subpkt (sig, SIGSUBPKT_TRUST, buf, 2);
/* Critical. If someone doesn't understands regexps, this
whole sig should be invalid. Note the +1 for the length -
regexps are null terminated. */
if (attrib->trust_regexp)
build_sig_subpkt (sig, SIGSUBPKT_FLAG_CRITICAL | SIGSUBPKT_REGEXP,
attrib->trust_regexp,
strlen (attrib->trust_regexp) + 1);
}
return 0;
}
static void
trustsig_prompt (byte * trust_value, byte * trust_depth, char **regexp)
{
char *p;
*trust_value = 0;
*trust_depth = 0;
*regexp = NULL;
/* Same string as pkclist.c:do_edit_ownertrust */
tty_printf (_
("Please decide how far you trust this user to correctly verify"
" other users' keys\n(by looking at passports, checking"
" fingerprints from different sources, etc.)\n"));
tty_printf ("\n");
tty_printf (_(" %d = I trust marginally\n"), 1);
tty_printf (_(" %d = I trust fully\n"), 2);
tty_printf ("\n");
while (*trust_value == 0)
{
p = cpr_get ("trustsig_prompt.trust_value", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
/* 60 and 120 are as per RFC2440 */
if (p[0] == '1' && !p[1])
*trust_value = 60;
else if (p[0] == '2' && !p[1])
*trust_value = 120;
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter the depth of this trust signature.\n"
"A depth greater than 1 allows the key you are"
" signing to make\n"
"trust signatures on your behalf.\n"));
tty_printf ("\n");
while (*trust_depth == 0)
{
p = cpr_get ("trustsig_prompt.trust_depth", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
*trust_depth = atoi (p);
xfree (p);
}
tty_printf ("\n");
tty_printf (_("Please enter a domain to restrict this signature, "
"or enter for none.\n"));
tty_printf ("\n");
p = cpr_get ("trustsig_prompt.trust_regexp", _("Your selection? "));
trim_spaces (p);
cpr_kill_prompt ();
if (strlen (p) > 0)
{
char *q = p;
int regexplen = 100, ind;
*regexp = xmalloc (regexplen);
/* Now mangle the domain the user entered into a regexp. To do
this, \-escape everything that isn't alphanumeric, and attach
"<[^>]+[@.]" to the front, and ">$" to the end. */
strcpy (*regexp, "<[^>]+[@.]");
ind = strlen (*regexp);
while (*q)
{
if (!((*q >= 'A' && *q <= 'Z')
|| (*q >= 'a' && *q <= 'z') || (*q >= '0' && *q <= '9')))
(*regexp)[ind++] = '\\';
(*regexp)[ind++] = *q;
if ((regexplen - ind) < 3)
{
regexplen += 100;
*regexp = xrealloc (*regexp, regexplen);
}
q++;
}
(*regexp)[ind] = '\0';
strcat (*regexp, ">$");
}
xfree (p);
tty_printf ("\n");
}
/*
* Loop over all LOCUSR and sign the uids after asking. If no
* user id is marked, all user ids will be signed; if some user_ids
* are marked only those will be signed. If QUICK is true the
* function won't ask the user and use sensible defaults.
*/
static int
sign_uids (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, strlist_t locusr, int *ret_modified,
int local, int nonrevocable, int trust, int interactive,
int quick)
{
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
PKT_public_key *pk = NULL;
KBNODE node, uidnode;
PKT_public_key *primary_pk = NULL;
int select_all = !count_selected_uids (keyblock) || interactive;
/* Build a list of all signators.
*
* We use the CERT flag to request the primary which must always
* be one which is capable of signing keys. I can't see a reason
* why to sign keys using a subkey. Implementation of USAGE_CERT
* is just a hack in getkey.c and does not mean that a subkey
* marked as certification capable will be used. */
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_CERT);
if (rc)
goto leave;
/* Loop over all signators. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
u32 sk_keyid[2], pk_keyid[2];
char *p, *trust_regexp = NULL;
int class = 0, selfsig = 0;
u32 duration = 0, timestamp = 0;
byte trust_depth = 0, trust_value = 0;
pk = sk_rover->pk;
keyid_from_pk (pk, sk_keyid);
/* Set mark A for all selected user ids. */
for (node = keyblock; node; node = node->next)
{
if (select_all || (node->flag & NODFLG_SELUID))
node->flag |= NODFLG_MARK_A;
else
node->flag &= ~NODFLG_MARK_A;
}
/* Reset mark for uids which are already signed. */
uidnode = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
primary_pk = node->pkt->pkt.public_key;
keyid_from_pk (primary_pk, pk_keyid);
/* Is this a self-sig? */
if (pk_keyid[0] == sk_keyid[0] && pk_keyid[1] == sk_keyid[1])
selfsig = 1;
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uidnode = (node->flag & NODFLG_MARK_A) ? node : NULL;
if (uidnode)
{
int yesreally = 0;
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
if (opt.only_sign_text_ids
&& uidnode->pkt->pkt.user_id->attribs)
{
tty_fprintf (fp, _("Skipping user ID \"%s\","
" which is not a text ID.\n"),
user);
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (uidnode->pkt->pkt.user_id->flags.revoked)
{
tty_fprintf (fp, _("User ID \"%s\" is revoked."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.revoke_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (uidnode->pkt->pkt.user_id->flags.expired)
{
tty_fprintf (fp, _("User ID \"%s\" is expired."), user);
if (selfsig)
tty_fprintf (fp, "\n");
else if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.expire_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
else if (!uidnode->pkt->pkt.user_id->created && !selfsig)
{
tty_fprintf (fp, _("User ID \"%s\" is not self-signed."),
user);
if (opt.expert && !quick)
{
tty_fprintf (fp, "\n");
/* No, so remove the mark and continue */
if (!cpr_get_answer_is_yes ("sign_uid.nosig_okay",
_("Are you sure you "
"still want to sign "
"it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
else if (interactive)
yesreally = 1;
}
else
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
tty_fprintf (fp, _(" Unable to sign.\n"));
}
}
if (uidnode && interactive && !yesreally && !quick)
{
tty_fprintf (fp,
_("User ID \"%s\" is signable. "), user);
if (!cpr_get_answer_is_yes ("sign_uid.sign_okay",
_("Sign it? (y/N) ")))
{
uidnode->flag &= ~NODFLG_MARK_A;
uidnode = NULL;
}
}
xfree (user);
}
}
else if (uidnode && node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class & ~3) == 0x10)
{
if (sk_keyid[0] == node->pkt->pkt.signature->keyid[0]
&& sk_keyid[1] == node->pkt->pkt.signature->keyid[1])
{
char buf[50];
char *user;
user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len, 0);
/* It's a v3 self-sig. Make it into a v4 self-sig? */
if (node->pkt->pkt.signature->version < 4
&& selfsig && !quick)
{
tty_fprintf (fp,
_("The self-signature on \"%s\"\n"
"is a PGP 2.x-style signature.\n"), user);
/* Note that the regular PGP2 warning below
still applies if there are no v4 sigs on
this key at all. */
if (opt.expert)
if (cpr_get_answer_is_yes ("sign_uid.v4_promote_okay",
_("Do you want to promote "
"it to an OpenPGP self-"
"signature? (y/N) ")))
{
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Is the current signature expired? */
if (node->pkt->pkt.signature->flags.expired)
{
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"has expired.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.replace_expired_okay",
_("Do you want to issue a "
"new signature to replace "
"the expired one? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
if (!node->pkt->pkt.signature->flags.exportable && !local)
{
/* It's a local sig, and we want to make a
exportable sig. */
tty_fprintf (fp, _("Your current signature on \"%s\"\n"
"is a local signature.\n"), user);
if (quick || cpr_get_answer_is_yes
("sign_uid.local_promote_okay",
_("Do you want to promote "
"it to a full exportable " "signature? (y/N) ")))
{
/* Mark these for later deletion. We
don't want to delete them here, just in
case the replacement signature doesn't
happen for some reason. We only delete
these after the replacement is already
in place. */
node->flag |= NODFLG_DELSIG;
xfree (user);
continue;
}
}
/* Fixme: see whether there is a revocation in which
* case we should allow signing it again. */
if (!node->pkt->pkt.signature->flags.exportable && local)
tty_fprintf ( fp,
_("\"%s\" was already locally signed by key %s\n"),
user, keystr_from_pk (pk));
else
tty_fprintf (fp,
_("\"%s\" was already signed by key %s\n"),
user, keystr_from_pk (pk));
if (opt.flags.force_sign_key
|| (opt.expert && !quick
&& cpr_get_answer_is_yes ("sign_uid.dupe_okay",
_("Do you want to sign it "
"again anyway? (y/N) "))))
{
/* Don't delete the old sig here since this is
an --expert thing. */
xfree (user);
continue;
}
snprintf (buf, sizeof buf, "%08lX%08lX",
(ulong) pk->keyid[0], (ulong) pk->keyid[1]);
write_status_text (STATUS_ALREADY_SIGNED, buf);
uidnode->flag &= ~NODFLG_MARK_A; /* remove mark */
xfree (user);
}
}
}
/* Check whether any uids are left for signing. */
if (!count_uids_with_flag (keyblock, NODFLG_MARK_A))
{
tty_fprintf (fp, _("Nothing to sign with key %s\n"),
keystr_from_pk (pk));
continue;
}
/* Ask whether we really should sign these user id(s). */
tty_fprintf (fp, "\n");
show_key_with_all_names (ctrl, fp, keyblock, 1, 0, 1, 0, 0, 0);
tty_fprintf (fp, "\n");
if (primary_pk->expiredate && !selfsig)
{
/* Static analyzer note: A claim that PRIMARY_PK might be
NULL is not correct because it set from the public key
packet which is always the first packet in a keyblock and
parsed in the above loop over the keyblock. In case the
keyblock has no packets at all and thus the loop was not
entered the above count_uids_with_flag would have
detected this case. */
u32 now = make_timestamp ();
if (primary_pk->expiredate <= now)
{
tty_fprintf (fp, _("This key has expired!"));
if (opt.expert && !quick)
{
tty_fprintf (fp, " ");
if (!cpr_get_answer_is_yes ("sign_uid.expired_okay",
_("Are you sure you still "
"want to sign it? (y/N) ")))
continue;
}
else
{
tty_fprintf (fp, _(" Unable to sign.\n"));
continue;
}
}
else
{
tty_fprintf (fp, _("This key is due to expire on %s.\n"),
expirestr_from_pk (primary_pk));
if (opt.ask_cert_expire && !quick)
{
char *answer = cpr_get ("sign_uid.expire",
_("Do you want your signature to "
"expire at the same time? (Y/n) "));
if (answer_is_yes_no_default (answer, 1))
{
/* This fixes the signature timestamp we're
going to make as now. This is so the
expiration date is exactly correct, and not
a few seconds off (due to the time it takes
to answer the questions, enter the
passphrase, etc). */
timestamp = now;
duration = primary_pk->expiredate - now;
}
cpr_kill_prompt ();
xfree (answer);
}
}
}
/* Only ask for duration if we haven't already set it to match
the expiration of the pk */
if (!duration && !selfsig)
{
if (opt.ask_cert_expire && !quick)
duration = ask_expire_interval (1, opt.def_cert_expire);
else
duration = parse_expire_string (opt.def_cert_expire);
}
if (selfsig)
;
else
{
if (opt.batch || !opt.ask_cert_level || quick)
class = 0x10 + opt.def_cert_level;
else
{
char *answer;
tty_fprintf (fp,
_("How carefully have you verified the key you are "
"about to sign actually belongs\nto the person "
"named above? If you don't know what to "
"answer, enter \"0\".\n"));
tty_fprintf (fp, "\n");
tty_fprintf (fp, _(" (0) I will not answer.%s\n"),
opt.def_cert_level == 0 ? " (default)" : "");
tty_fprintf (fp, _(" (1) I have not checked at all.%s\n"),
opt.def_cert_level == 1 ? " (default)" : "");
tty_fprintf (fp, _(" (2) I have done casual checking.%s\n"),
opt.def_cert_level == 2 ? " (default)" : "");
tty_fprintf (fp,
_(" (3) I have done very careful checking.%s\n"),
opt.def_cert_level == 3 ? " (default)" : "");
tty_fprintf (fp, "\n");
while (class == 0)
{
answer = cpr_get ("sign_uid.class",
_("Your selection? "
"(enter '?' for more information): "));
if (answer[0] == '\0')
class = 0x10 + opt.def_cert_level; /* Default */
else if (ascii_strcasecmp (answer, "0") == 0)
class = 0x10; /* Generic */
else if (ascii_strcasecmp (answer, "1") == 0)
class = 0x11; /* Persona */
else if (ascii_strcasecmp (answer, "2") == 0)
class = 0x12; /* Casual */
else if (ascii_strcasecmp (answer, "3") == 0)
class = 0x13; /* Positive */
else
tty_fprintf (fp, _("Invalid selection.\n"));
xfree (answer);
}
}
if (trust && !quick)
trustsig_prompt (&trust_value, &trust_depth, &trust_regexp);
}
if (!quick)
{
p = get_user_id_native (ctrl, sk_keyid);
tty_fprintf (fp,
_("Are you sure that you want to sign this key with your\n"
"key \"%s\" (%s)\n"), p, keystr_from_pk (pk));
xfree (p);
}
if (selfsig)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("This will be a self-signature.\n"));
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("WARNING: the signature will not be marked "
"as non-revocable.\n"));
}
}
else
{
if (local)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-exportable.\n"));
}
if (nonrevocable)
{
tty_fprintf (fp, "\n");
tty_fprintf (fp,
_("The signature will be marked as non-revocable.\n"));
}
switch (class)
{
case 0x11:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have not checked this key at all.\n"));
break;
case 0x12:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key casually.\n"));
break;
case 0x13:
tty_fprintf (fp, "\n");
tty_fprintf (fp, _("I have checked this key very carefully.\n"));
break;
}
}
tty_fprintf (fp, "\n");
if (opt.batch && opt.answer_yes)
;
else if (quick)
;
else if (!cpr_get_answer_is_yes ("sign_uid.okay",
_("Really sign? (y/N) ")))
continue;
/* Now we can sign the user ids. */
reloop: /* (Must use this, because we are modifying the list.) */
primary_pk = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
primary_pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID
&& (node->flag & NODFLG_MARK_A))
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
log_assert (primary_pk);
memset (&attrib, 0, sizeof attrib);
attrib.non_exportable = local;
attrib.non_revocable = nonrevocable;
attrib.trust_depth = trust_depth;
attrib.trust_value = trust_value;
attrib.trust_regexp = trust_regexp;
node->flag &= ~NODFLG_MARK_A;
/* We force creation of a v4 signature for local
* signatures, otherwise we would not generate the
* subpacket with v3 keys and the signature becomes
* exportable. */
if (selfsig)
rc = make_keysig_packet (ctrl, &sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
0x13,
0, 0,
keygen_add_std_prefs, primary_pk,
NULL);
else
rc = make_keysig_packet (ctrl, &sig, primary_pk,
node->pkt->pkt.user_id,
NULL,
pk,
class,
timestamp, duration,
sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto leave;
}
*ret_modified = 1; /* We changed the keyblock. */
update_trust = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), PKT_SIGNATURE);
goto reloop;
}
}
/* Delete any sigs that got promoted */
for (node = keyblock; node; node = node->next)
if (node->flag & NODFLG_DELSIG)
delete_kbnode (node);
} /* End loop over signators. */
leave:
release_sk_list (sk_list);
return rc;
}
/*
* Change the passphrase of the primary and all secondary keys. Note
* that it is common to use only one passphrase for the primary and
* all subkeys. However, this is now (since GnuPG 2.1) all up to the
* gpg-agent. Returns 0 on success or an error code.
*/
static gpg_error_t
change_passphrase (ctrl_t ctrl, kbnode_t keyblock)
{
gpg_error_t err;
kbnode_t node;
PKT_public_key *pk;
int any;
u32 keyid[2], subid[2];
char *hexgrip = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key missing!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, keyid);
/* Check whether it is likely that we will be able to change the
passphrase for any subkey. */
for (any = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *serialno;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
/* FIXME: Handle dual keys. */
err = agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
if (!err && serialno)
; /* Key on card. */
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Maybe stub key. */
else if (!err)
any = 1; /* Key is known. */
else
log_error ("key %s: error getting keyinfo from agent: %s\n",
keystr_with_sub (keyid, subid), gpg_strerror (err));
xfree (serialno);
}
}
err = 0;
if (!any)
{
tty_printf (_("Key has only stub or on-card key items - "
"no passphrase to change.\n"));
goto leave;
}
/* Change the passphrase for all keys. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
char *desc;
pk = node->pkt->pkt.public_key;
keyid_from_pk (pk, subid);
xfree (hexgrip);
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
goto leave;
/* Note that when using --dry-run we don't change the
* passphrase but merely verify the current passphrase. */
desc = gpg_format_keydesc (ctrl, pk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, !!opt.dry_run,
&cache_nonce, &passwd_nonce);
xfree (desc);
if (err)
log_log ((gpg_err_code (err) == GPG_ERR_CANCELED
|| gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
? GPGRT_LOGLVL_INFO : GPGRT_LOGLVL_ERROR,
_("key %s: error changing passphrase: %s\n"),
keystr_with_sub (keyid, subid),
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_FULLY_CANCELED)
break;
}
}
leave:
xfree (hexgrip);
xfree (cache_nonce);
xfree (passwd_nonce);
return err;
}
/* Fix various problems in the keyblock. Returns true if the keyblock
was changed. Note that a pointer to the keyblock must be given and
the function may change it (i.e. replacing the first node). */
static int
fix_keyblock (ctrl_t ctrl, kbnode_t *keyblockp)
{
int changed = 0;
if (collapse_uids (keyblockp))
changed++;
if (collapse_subkeys (keyblockp))
changed++;
if (key_check_all_keysigs (ctrl, 1, *keyblockp, 0, 1))
changed++;
reorder_keyblock (*keyblockp);
/* If we modified the keyblock, make sure the flags are right. */
if (changed)
merge_keys_and_selfsig (ctrl, *keyblockp);
return changed;
}
static int
parse_sign_type (const char *str, int *localsig, int *nonrevokesig,
int *trustsig)
{
const char *p = str;
while (*p)
{
if (ascii_strncasecmp (p, "l", 1) == 0)
{
*localsig = 1;
p++;
}
else if (ascii_strncasecmp (p, "nr", 2) == 0)
{
*nonrevokesig = 1;
p += 2;
}
else if (ascii_strncasecmp (p, "t", 1) == 0)
{
*trustsig = 1;
p++;
}
else
return 0;
}
return 1;
}
/*
* Menu driven key editor. If seckey_check is true, then a secret key
* that matches username will be looked for. If it is false, not all
* commands will be available.
*
* Note: to keep track of certain selections we use node->mark MARKBIT_xxxx.
*/
/* Need an SK for this command */
#define KEYEDIT_NEED_SK 1
/* Need an SUB KEY for this command */
#define KEYEDIT_NEED_SUBSK 2
/* Match the tail of the string */
#define KEYEDIT_TAIL_MATCH 8
enum cmdids
{
cmdNONE = 0,
cmdQUIT, cmdHELP, cmdFPR, cmdLIST, cmdSELUID, cmdCHECK, cmdSIGN,
cmdREVSIG, cmdREVKEY, cmdREVUID, cmdDELSIG, cmdPRIMARY, cmdDEBUG,
cmdSAVE, cmdADDUID, cmdADDPHOTO, cmdDELUID, cmdADDKEY, cmdDELKEY,
cmdADDREVOKER, cmdTOGGLE, cmdSELKEY, cmdPASSWD, cmdTRUST, cmdPREF,
cmdEXPIRE, cmdCHANGEUSAGE, cmdBACKSIGN, cmdADDADSK,
#ifndef NO_TRUST_MODELS
cmdENABLEKEY, cmdDISABLEKEY,
#endif /*!NO_TRUST_MODELS*/
cmdSHOWPREF,
cmdSETPREF, cmdPREFKS, cmdNOTATION, cmdINVCMD, cmdSHOWPHOTO, cmdUPDTRUST,
cmdCHKTRUST, cmdADDCARDKEY, cmdKEYTOCARD, cmdKEYTOTPM, cmdBKUPTOCARD,
cmdCLEAN, cmdMINIMIZE, cmdGRIP, cmdNOP
};
static struct
{
const char *name;
enum cmdids id;
int flags;
const char *desc;
} cmds[] =
{
{ "quit", cmdQUIT, 0, N_("quit this menu")},
{ "q", cmdQUIT, 0, NULL},
{ "save", cmdSAVE, 0, N_("save and quit")},
{ "help", cmdHELP, 0, N_("show this help")},
{ "?", cmdHELP, 0, NULL},
{ "fpr", cmdFPR, 0, N_("show key fingerprint")},
{ "grip", cmdGRIP, 0, N_("show the keygrip")},
{ "list", cmdLIST, 0, N_("list key and user IDs")},
{ "l", cmdLIST, 0, NULL},
{ "uid", cmdSELUID, 0, N_("select user ID N")},
{ "key", cmdSELKEY, 0, N_("select subkey N")},
{ "check", cmdCHECK, 0, N_("check signatures")},
{ "c", cmdCHECK, 0, NULL},
{ "change-usage", cmdCHANGEUSAGE, KEYEDIT_NEED_SK, NULL},
{ "cross-certify", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL},
{ "backsign", cmdBACKSIGN, KEYEDIT_NEED_SK, NULL},
{ "sign", cmdSIGN, KEYEDIT_TAIL_MATCH,
N_("sign selected user IDs [* see below for related commands]")},
{ "s", cmdSIGN, 0, NULL},
/* "lsign" and friends will never match since "sign" comes first
and it is a tail match. They are just here so they show up in
the help menu. */
{ "lsign", cmdNOP, 0, N_("sign selected user IDs locally")},
{ "tsign", cmdNOP, 0, N_("sign selected user IDs with a trust signature")},
{ "nrsign", cmdNOP, 0,
N_("sign selected user IDs with a non-revocable signature")},
{ "debug", cmdDEBUG, 0, NULL},
{ "adduid", cmdADDUID, KEYEDIT_NEED_SK, N_("add a user ID")},
{ "addphoto", cmdADDPHOTO, KEYEDIT_NEED_SK,
N_("add a photo ID")},
{ "deluid", cmdDELUID, 0, N_("delete selected user IDs")},
/* delphoto is really deluid in disguise */
{ "delphoto", cmdDELUID, 0, NULL},
{ "addkey", cmdADDKEY, KEYEDIT_NEED_SK, N_("add a subkey")},
#ifdef ENABLE_CARD_SUPPORT
{ "addcardkey", cmdADDCARDKEY, KEYEDIT_NEED_SK,
N_("add a key to a smartcard")},
{ "keytocard", cmdKEYTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("move a key to a smartcard")},
{ "keytotpm", cmdKEYTOTPM, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("convert a key to TPM form using the local TPM")},
{ "bkuptocard", cmdBKUPTOCARD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("move a backup key to a smartcard")},
#endif /*ENABLE_CARD_SUPPORT */
{ "delkey", cmdDELKEY, 0, N_("delete selected subkeys")},
{ "addrevoker", cmdADDREVOKER, KEYEDIT_NEED_SK,
N_("add a revocation key")},
{ "addadsk", cmdADDADSK, KEYEDIT_NEED_SK,
N_("add an additional decryption subkey")},
{ "delsig", cmdDELSIG, 0,
N_("delete signatures from the selected user IDs")},
{ "expire", cmdEXPIRE, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("change the expiration date for the key or selected subkeys")},
{ "primary", cmdPRIMARY, KEYEDIT_NEED_SK,
N_("flag the selected user ID as primary")},
{ "toggle", cmdTOGGLE, KEYEDIT_NEED_SK, NULL}, /* Dummy command. */
{ "t", cmdTOGGLE, KEYEDIT_NEED_SK, NULL},
{ "pref", cmdPREF, 0, N_("list preferences (expert)")},
{ "showpref", cmdSHOWPREF, 0, N_("list preferences (verbose)")},
{ "setpref", cmdSETPREF, KEYEDIT_NEED_SK,
N_("set preference list for the selected user IDs")},
{ "updpref", cmdSETPREF, KEYEDIT_NEED_SK, NULL},
{ "keyserver", cmdPREFKS, KEYEDIT_NEED_SK,
N_("set the preferred keyserver URL for the selected user IDs")},
{ "notation", cmdNOTATION, KEYEDIT_NEED_SK,
N_("set a notation for the selected user IDs")},
{ "passwd", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK,
N_("change the passphrase")},
{ "password", cmdPASSWD, KEYEDIT_NEED_SK | KEYEDIT_NEED_SUBSK, NULL},
#ifndef NO_TRUST_MODELS
{ "trust", cmdTRUST, 0, N_("change the ownertrust")},
#endif /*!NO_TRUST_MODELS*/
{ "revsig", cmdREVSIG, 0,
N_("revoke signatures on the selected user IDs")},
{ "revuid", cmdREVUID, KEYEDIT_NEED_SK,
N_("revoke selected user IDs")},
{ "revphoto", cmdREVUID, KEYEDIT_NEED_SK, NULL},
{ "revkey", cmdREVKEY, KEYEDIT_NEED_SK,
N_("revoke key or selected subkeys")},
#ifndef NO_TRUST_MODELS
{ "enable", cmdENABLEKEY, 0, N_("enable key")},
{ "disable", cmdDISABLEKEY, 0, N_("disable key")},
#endif /*!NO_TRUST_MODELS*/
{ "showphoto", cmdSHOWPHOTO, 0, N_("show selected photo IDs")},
{ "clean", cmdCLEAN, 0,
N_("compact unusable user IDs and remove unusable signatures from key")},
{ "minimize", cmdMINIMIZE, 0,
N_("compact unusable user IDs and remove all signatures from key")},
{ NULL, cmdNONE, 0, NULL}
};
#ifdef HAVE_LIBREADLINE
/*
These two functions are used by readline for command completion.
*/
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen (text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text */
if (cmds[list_index++].desc && strncmp (name, text, len) == 0)
return strdup (name);
}
return NULL;
}
static char **
keyedit_completion (const char *text, int start, int end)
{
/* If we are at the start of a line, we try and command-complete.
If not, just do nothing for now. */
(void) end;
if (start == 0)
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /* HAVE_LIBREADLINE */
/* Main function of the menu driven key editor. */
void
keyedit_menu (ctrl_t ctrl, const char *username, strlist_t locusr,
strlist_t commands, int quiet, int seckey_check)
{
enum cmdids cmd = 0;
gpg_error_t err = 0;
KBNODE keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int have_seckey = 0;
int have_anyseckey = 0;
char *answer = NULL;
int redisplay = 1;
int modified = 0;
int sec_shadowing = 0;
int run_subkey_warnings = 0;
int have_commands = !!commands;
strlist_t delseckey_list = NULL;
int delseckey_list_warn = 0;
if (opt.command_fd != -1)
;
else if (opt.batch && !have_commands)
{
log_error (_("can't do this in batch mode\n"));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* Due to Windows peculiarities we need to make sure that the
trustdb stale check is done before we open another file
(i.e. by searching for a key). In theory we could make sure
that the files are closed after use but the open/close caches
inhibits that and flushing the cache right before the stale
check is not easy to implement. Thus we take the easy way out
and run the stale check as early as possible. Note, that for
- non- W32 platforms it is run indirectly trough a call to
+ non- W32 platforms it is run indirectly through a call to
get_validity (). */
check_trustdb_stale (ctrl);
#endif
/* Get the public key */
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, NULL, username, &keyblock, &kdbhd, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), username, gpg_strerror (err));
goto leave;
}
if (fix_keyblock (ctrl, &keyblock))
modified++;
/* See whether we have a matching secret key. */
if (seckey_check)
{
have_anyseckey = !agent_probe_any_secret_key (ctrl, keyblock);
if (have_anyseckey
&& agent_probe_secret_key (ctrl, keyblock->pkt->pkt.public_key))
{
/* The primary key is also available. */
have_seckey = 1;
}
if (have_seckey && !quiet)
tty_printf (_("Secret key is available.\n"));
else if (have_anyseckey && !quiet)
tty_printf (_("Secret subkeys are available.\n"));
}
/* Main command loop. */
for (;;)
{
int i, arg_number, photo;
const char *arg_string = "";
char *p;
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
tty_printf ("\n");
if (redisplay && !quiet)
{
/* Show using flags: with_revoker, with_subkeys. */
show_key_with_all_names (ctrl, NULL, keyblock, 0, 1, 0, 1, 0, 0);
tty_printf ("\n");
redisplay = 0;
}
if (run_subkey_warnings)
{
run_subkey_warnings = 0;
if (!count_selected_keys (keyblock))
subkey_expire_warning (keyblock);
no_usable_encr_subkeys_warning (keyblock);
}
if (delseckey_list_warn)
{
delseckey_list_warn = 0;
tty_printf
(_("Note: the local copy of the secret key"
" will only be deleted with \"save\".\n"));
}
do
{
xfree (answer);
if (have_commands)
{
if (commands)
{
answer = xstrdup (commands->d);
commands = commands->next;
}
else if (opt.batch)
{
answer = xstrdup ("quit");
}
else
have_commands = 0;
}
if (!have_commands)
{
#ifdef HAVE_LIBREADLINE
tty_enable_completion (keyedit_completion);
#endif
answer = cpr_get_no_help ("keyedit.prompt", GPG_NAME "> ");
cpr_kill_prompt ();
tty_disable_completion ();
}
trim_spaces (answer);
}
while (*answer == '#');
arg_number = 0; /* Here is the init which egcc complains about. */
photo = 0; /* Same here. */
if (!*answer)
cmd = cmdLIST;
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else if (digitp (answer))
{
cmd = cmdSELUID;
arg_number = atoi (answer);
}
else
{
if ((p = strchr (answer, ' ')))
{
*p++ = 0;
trim_spaces (answer);
trim_spaces (p);
arg_number = atoi (p);
arg_string = p;
}
for (i = 0; cmds[i].name; i++)
{
if (cmds[i].flags & KEYEDIT_TAIL_MATCH)
{
size_t l = strlen (cmds[i].name);
size_t a = strlen (answer);
if (a >= l)
{
if (!ascii_strcasecmp (&answer[a - l], cmds[i].name))
{
answer[a - l] = '\0';
break;
}
}
}
else if (!ascii_strcasecmp (answer, cmds[i].name))
break;
}
if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK))
&& !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey)
|| ((cmds[i].flags & KEYEDIT_NEED_SUBSK) && have_anyseckey)))
{
tty_printf (_("Need the secret key to do this.\n"));
cmd = cmdNOP;
}
else
cmd = cmds[i].id;
}
/* Dispatch the command. */
switch (cmd)
{
case cmdHELP:
for (i = 0; cmds[i].name; i++)
{
if ((cmds[i].flags & (KEYEDIT_NEED_SK|KEYEDIT_NEED_SUBSK))
&& !(((cmds[i].flags & KEYEDIT_NEED_SK) && have_seckey)
||((cmds[i].flags&KEYEDIT_NEED_SUBSK)&&have_anyseckey)))
; /* Skip those item if we do not have the secret key. */
else if (cmds[i].desc)
tty_printf ("%-11s %s\n", cmds[i].name, _(cmds[i].desc));
}
tty_printf ("\n");
tty_printf
(_("* The 'sign' command may be prefixed with an 'l' for local "
"signatures (lsign),\n"
" a 't' for trust signatures (tsign), an 'nr' for "
"non-revocable signatures\n"
" (nrsign), or any combination thereof (ltsign, "
"tnrsign, etc.).\n"));
break;
case cmdLIST:
redisplay = 1;
break;
case cmdFPR:
show_key_and_fingerprint
(ctrl,
keyblock, (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1))));
break;
case cmdGRIP:
show_key_and_grip (keyblock);
break;
case cmdSELUID:
if (strlen (arg_string) == NAMEHASH_LEN * 2)
redisplay = menu_select_uid_namehash (keyblock, arg_string);
else
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
redisplay = menu_select_uid (keyblock, arg_number);
}
break;
case cmdSELKEY:
{
if (*arg_string == '*'
&& (!arg_string[1] || spacep (arg_string + 1)))
arg_number = -1; /* Select all. */
if (menu_select_key (keyblock, arg_number, p))
redisplay = 1;
}
break;
case cmdCHECK:
if (key_check_all_keysigs (ctrl, -1, keyblock,
count_selected_uids (keyblock),
!strcmp (arg_string, "selfsig")))
modified = 1;
break;
case cmdSIGN:
{
int localsig = 0, nonrevokesig = 0, trustsig = 0, interactive = 0;
if (pk->flags.revoked)
{
tty_printf (_("Key is revoked."));
if (opt.expert)
{
tty_printf (" ");
if (!cpr_get_answer_is_yes
("keyedit.sign_revoked.okay",
_("Are you sure you still want to sign it? (y/N) ")))
break;
}
else
{
tty_printf (_(" Unable to sign.\n"));
break;
}
}
if (count_uids (keyblock) > 1 && !count_selected_uids (keyblock))
{
int result;
if (opt.only_sign_text_ids)
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all text user IDs? (y/N) "));
else
result = cpr_get_answer_is_yes
("keyedit.sign_all.okay",
_("Really sign all user IDs? (y/N) "));
if (! result)
{
if (opt.interactive)
interactive = 1;
else
{
tty_printf (_("Hint: Select the user IDs to sign\n"));
have_commands = 0;
break;
}
}
}
/* What sort of signing are we doing? */
if (!parse_sign_type
(answer, &localsig, &nonrevokesig, &trustsig))
{
tty_printf (_("Unknown signature type '%s'\n"), answer);
break;
}
sign_uids (ctrl, NULL, keyblock, locusr, &modified,
localsig, nonrevokesig, trustsig, interactive, 0);
}
break;
case cmdDEBUG:
dump_kbnode (keyblock);
break;
case cmdTOGGLE:
/* The toggle command is a leftover from old gpg versions
where we worked with a secret and a public keyring. It
is not necessary anymore but we keep this command for the
sake of scripts using it. */
redisplay = 1;
break;
case cmdADDPHOTO:
if (RFC2440)
{
tty_printf (_("This command is not allowed while in %s mode.\n"),
gnupg_compliance_option_string (opt.compliance));
break;
}
photo = 1;
/* fall through */
case cmdADDUID:
if (menu_adduid (ctrl, keyblock, photo, arg_string, NULL))
{
update_trust = 1;
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdDELUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (real_uids_left (keyblock) < 1)
tty_printf (_("You can't delete the last user ID!\n"));
else if (cpr_get_answer_is_yes
("keyedit.remove.uid.okay",
n1 > 1 ? _("Really remove all selected user IDs? (y/N) ")
: _("Really remove this user ID? (y/N) ")))
{
menu_deluid (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdDELSIG:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (menu_delsig (ctrl, keyblock))
{
/* No redisplay here, because it may scroll away some
* of the status output of this command. */
modified = 1;
}
}
break;
case cmdADDKEY:
if (!generate_subkeypair (ctrl, keyblock, NULL, NULL, NULL))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
#ifdef ENABLE_CARD_SUPPORT
case cmdADDCARDKEY:
if (!card_generate_subkey (ctrl, keyblock))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdKEYTOTPM:
/* FIXME need to store the key and not commit until later */
{
kbnode_t node = NULL;
switch (count_selected_keys (keyblock))
{
case 0:
if (cpr_get_answer_is_yes
("keyedit.keytocard.use_primary",
/* TRANSLATORS: Please take care: This is about
moving the key and not about removing it. */
_("Really move the primary key? (y/N) ")))
node = keyblock;
break;
case 1:
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& node->flag & NODFLG_SELKEY)
break;
}
break;
default:
tty_printf (_("You must select exactly one key.\n"));
break;
}
if (node)
{
PKT_public_key *xxpk = node->pkt->pkt.public_key;
char *hexgrip;
hexkeygrip_from_pk (xxpk, &hexgrip);
if (!agent_keytotpm (ctrl, hexgrip))
{
redisplay = 1;
}
xfree (hexgrip);
}
}
break;
case cmdKEYTOCARD:
{
KBNODE node = NULL;
switch (count_selected_keys (keyblock))
{
case 0:
if (cpr_get_answer_is_yes
("keyedit.keytocard.use_primary",
/* TRANSLATORS: Please take care: This is about
moving the key and not about removing it. */
_("Really move the primary key? (y/N) ")))
node = keyblock;
break;
case 1:
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& node->flag & NODFLG_SELKEY)
break;
}
break;
default:
tty_printf (_("You must select exactly one key.\n"));
break;
}
if (node)
{
PKT_public_key *xxpk = node->pkt->pkt.public_key;
if (card_store_subkey (node, xxpk ? xxpk->pubkey_usage : 0,
&delseckey_list))
{
redisplay = 1;
sec_shadowing = 1;
delseckey_list_warn = 1;
}
}
}
break;
case cmdBKUPTOCARD:
{
/* Ask for a filename, check whether this is really a
backup key as generated by the card generation, parse
that key and store it on card. */
KBNODE node;
char *fname;
PACKET *pkt;
IOBUF a;
struct parse_packet_ctx_s parsectx;
int lastmode;
if (!*arg_string)
{
tty_printf (_("Command expects a filename argument\n"));
break;
}
if (*arg_string == DIRSEP_C)
fname = xstrdup (arg_string);
else if (*arg_string == '~')
fname = make_filename (arg_string, NULL);
else
fname = make_filename (gnupg_homedir (), arg_string, NULL);
/* Open that file. */
a = iobuf_open (fname);
if (a && is_secured_file (iobuf_get_fd (a)))
{
iobuf_close (a);
a = NULL;
gpg_err_set_errno (EPERM);
}
if (!a)
{
tty_printf (_("Can't open '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
break;
}
/* Parse and check that file. */
pkt = xmalloc (sizeof *pkt);
init_packet (pkt);
init_parse_packet (&parsectx, a);
err = parse_packet (&parsectx, pkt);
deinit_parse_packet (&parsectx);
iobuf_close (a);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char *) fname);
if (!err && pkt->pkttype != PKT_SECRET_KEY
&& pkt->pkttype != PKT_SECRET_SUBKEY)
err = GPG_ERR_NO_SECKEY;
if (err)
{
tty_printf (_("Error reading backup key from '%s': %s\n"),
fname, gpg_strerror (err));
xfree (fname);
free_packet (pkt, NULL);
xfree (pkt);
break;
}
xfree (fname);
node = new_kbnode (pkt);
err = agent_set_ephemeral_mode (ctrl, 1, &lastmode);
if (err)
log_error ("error switching to ephemeral mode: %s\n",
gpg_strerror (err));
else
{
/* Transfer it to gpg-agent which handles secret keys. */
err = transfer_secret_keys (ctrl, NULL, node, 1, 1, 0);
if (!err)
{
/* Treat the pkt as a public key. */
pkt->pkttype = PKT_PUBLIC_KEY;
/* Ask gpg-agent to store the secret key to card. */
if (card_store_subkey (node, 0, NULL))
{
redisplay = 1;
sec_shadowing = 1;
}
}
if (!lastmode && agent_set_ephemeral_mode (ctrl, 0, NULL))
log_error ("error clearing the ephemeral mode\n");
}
release_kbnode (node);
}
break;
#endif /* ENABLE_CARD_SUPPORT */
case cmdDELKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
tty_printf (_("You must select at least one key.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "key");
}
else if (!cpr_get_answer_is_yes
("keyedit.remove.subkey.okay",
n1 > 1 ? _("Do you really want to delete the "
"selected keys? (y/N) ")
: _("Do you really want to delete this key? (y/N) ")))
;
else
{
menu_delkey (keyblock);
redisplay = 1;
modified = 1;
}
}
break;
case cmdADDREVOKER:
{
int sensitive = 0;
if (ascii_strcasecmp (arg_string, "sensitive") == 0)
sensitive = 1;
if (menu_addrevoker (ctrl, keyblock, sensitive))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
}
break;
case cmdADDADSK:
if (menu_addadsk (ctrl, keyblock, NULL))
{
redisplay = 1;
modified = 1;
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdREVUID:
{
int n1;
if (!(n1 = count_selected_uids (keyblock)))
{
tty_printf (_("You must select at least one user ID.\n"));
if (!opt.expert)
tty_printf (_("(Use the '%s' command.)\n"), "uid");
}
else if (cpr_get_answer_is_yes
("keyedit.revoke.uid.okay",
n1 > 1 ? _("Really revoke all selected user IDs? (y/N) ")
: _("Really revoke this user ID? (y/N) ")))
{
if (menu_revuid (ctrl, keyblock))
{
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdREVKEY:
{
int n1;
if (!(n1 = count_selected_keys (keyblock)))
{
if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
_("Do you really want to revoke"
" the entire key? (y/N) ")))
{
if (menu_revkey (ctrl, keyblock))
modified = 1;
redisplay = 1;
}
}
else if (cpr_get_answer_is_yes ("keyedit.revoke.subkey.okay",
n1 > 1 ?
_("Do you really want to revoke"
" the selected subkeys? (y/N) ")
: _("Do you really want to revoke"
" this subkey? (y/N) ")))
{
if (menu_revsubkey (ctrl, keyblock))
modified = 1;
redisplay = 1;
}
if (modified)
merge_keys_and_selfsig (ctrl, keyblock);
}
break;
case cmdEXPIRE:
if (gpg_err_code (menu_expire (ctrl, keyblock, 0, 0)) == GPG_ERR_TRUE)
{
merge_keys_and_selfsig (ctrl, keyblock);
run_subkey_warnings = 1;
modified = 1;
redisplay = 1;
}
break;
case cmdCHANGEUSAGE:
if (menu_changeusage (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdBACKSIGN:
if (menu_backsign (ctrl, keyblock))
{
modified = 1;
redisplay = 1;
}
break;
case cmdPRIMARY:
if (menu_set_primary_uid (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdPASSWD:
change_passphrase (ctrl, keyblock);
break;
#ifndef NO_TRUST_MODELS
case cmdTRUST:
if (opt.trust_model == TM_EXTERNAL)
{
tty_printf (_("Owner trust may not be set while "
"using a user provided trust database\n"));
break;
}
show_key_with_all_names (ctrl, NULL, keyblock, 0, 0, 0, 1, 0, 0);
tty_printf ("\n");
if (edit_ownertrust (ctrl, find_kbnode (keyblock,
PKT_PUBLIC_KEY)->pkt->pkt.
public_key, 1))
{
redisplay = 1;
/* No real need to set update_trust here as
edit_ownertrust() calls revalidation_mark()
anyway. */
update_trust = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 1);
}
break;
case cmdSHOWPREF:
{
int count = count_selected_uids (keyblock);
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
show_names (ctrl, NULL, keyblock, keyblock->pkt->pkt.public_key,
count ? NODFLG_SELUID : 0, 2);
}
break;
case cmdSETPREF:
{
PKT_user_id *tempuid;
keygen_set_std_prefs (!*arg_string ? "default" : arg_string, 0);
tempuid = keygen_get_std_prefs ();
tty_printf (_("Set preference list to:\n"));
show_prefs (tempuid, NULL, 1);
free_user_id (tempuid);
if (cpr_get_answer_is_yes
("keyedit.setpref.okay",
count_selected_uids (keyblock) ?
_("Really update the preferences"
" for the selected user IDs? (y/N) ")
: _("Really update the preferences? (y/N) ")))
{
if (menu_set_preferences (ctrl, keyblock, 0))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
}
}
break;
case cmdPREFKS:
if (menu_set_keyserver_url (ctrl, *arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOTATION:
if (menu_set_notation (ctrl, *arg_string ? arg_string : NULL,
keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
modified = 1;
redisplay = 1;
}
break;
case cmdNOP:
break;
case cmdREVSIG:
if (menu_revsig (ctrl, keyblock))
{
redisplay = 1;
modified = 1;
}
break;
#ifndef NO_TRUST_MODELS
case cmdENABLEKEY:
case cmdDISABLEKEY:
if (enable_disable_key (ctrl, keyblock, cmd == cmdDISABLEKEY))
{
redisplay = 1;
modified = 1;
}
break;
#endif /*!NO_TRUST_MODELS*/
case cmdSHOWPHOTO:
menu_showphoto (ctrl, keyblock);
break;
case cmdCLEAN:
if (menu_clean (ctrl, keyblock, 0))
redisplay = modified = 1;
break;
case cmdMINIMIZE:
if (menu_clean (ctrl, keyblock, EXPORT_MINIMAL))
redisplay = modified = 1;
break;
case cmdQUIT:
if (have_commands)
goto leave;
if (!modified && !sec_shadowing)
goto leave;
if (!cpr_get_answer_is_yes ("keyedit.save.okay",
_("Save changes? (y/N) ")))
{
if (cpr_enabled ()
|| cpr_get_answer_is_yes ("keyedit.cancel.okay",
_("Quit without saving? (y/N) ")))
goto leave;
break;
}
/* fall through */
case cmdSAVE:
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (delseckey_list)
{
strlist_t sl;
for (err = 0, sl = delseckey_list; sl; sl = sl->next)
{
if (*sl->d)
{
err = agent_delete_key (ctrl, sl->d, NULL, 1/*force*/);
if (err)
break;
*sl->d = 0; /* Mark deleted. */
}
}
if (err)
{
log_error (_("deleting copy of secret key failed: %s\n"),
gpg_strerror (err));
break; /* the "save". */
}
}
if (sec_shadowing)
{
err = agent_scd_learn (NULL, 1);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
break;
}
}
if (!modified && !sec_shadowing)
tty_printf (_("Key not changed so no update needed.\n"));
if (update_trust)
{
revalidation_mark (ctrl);
update_trust = 0;
}
goto leave;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
}
} /* End of the main command loop. */
leave:
free_strlist (delseckey_list);
release_kbnode (keyblock);
keydb_release (kdbhd);
xfree (answer);
}
/* Change the passphrase of the secret key identified by USERNAME. */
void
keyedit_passwd (ctrl_t ctrl, const char *username)
{
gpg_error_t err;
PKT_public_key *pk;
kbnode_t keyblock = NULL;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = getkey_byname (ctrl, NULL, pk, username, 1, &keyblock);
if (err)
goto leave;
err = change_passphrase (ctrl, keyblock);
leave:
release_kbnode (keyblock);
free_public_key (pk);
if (err)
{
log_info ("error changing the passphrase for '%s': %s\n",
username, gpg_strerror (err));
write_status_error ("keyedit.passwd", err);
}
else
write_status_text (STATUS_SUCCESS, "keyedit.passwd");
}
/* Helper for quick commands to find the keyblock for USERNAME.
* Returns on success the key database handle at R_KDBHD and the
* keyblock at R_KEYBLOCK. */
static gpg_error_t
quick_find_keyblock (ctrl_t ctrl, const char *username, int want_secret,
KEYDB_HANDLE *r_kdbhd, kbnode_t *r_keyblock)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
KEYDB_SEARCH_DESC desc;
kbnode_t node;
*r_kdbhd = NULL;
*r_keyblock = NULL;
/* Search the key; we don't want the whole getkey stuff here. */
kdbhd = keydb_new (ctrl);
if (!kdbhd)
{
/* Note that keydb_new has already used log_error. */
err = gpg_error_from_syserror ();
goto leave;
}
err = classify_user_id (username, &desc, 1);
if (!err)
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
{
err = keydb_get_keyblock (kdbhd, &keyblock);
if (err)
{
log_error (_("error reading keyblock: %s\n"), gpg_strerror (err));
goto leave;
}
/* Now with the keyblock retrieved, search again to detect an
ambiguous specification. We need to save the found state so
that we can do an update later. */
keydb_push_found_state (kdbhd);
err = keydb_search (kdbhd, &desc, 1, NULL);
if (!err)
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
keydb_pop_found_state (kdbhd);
if (!err && want_secret)
{
/* We require the secret primary key to set the primary UID. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
log_assert (node);
if (!agent_probe_secret_key (ctrl, node->pkt->pkt.public_key))
err = gpg_error (GPG_ERR_NO_SECKEY);
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_NO_PUBKEY);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"),
username, gpg_strerror (err));
goto leave;
}
fix_keyblock (ctrl, &keyblock);
merge_keys_and_selfsig (ctrl, keyblock);
*r_keyblock = keyblock;
keyblock = NULL;
*r_kdbhd = kdbhd;
kdbhd = NULL;
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
return err;
}
/* Unattended adding of a new keyid. USERNAME specifies the
key. NEWUID is the new user id to add to the key. */
void
keyedit_quick_adduid (ctrl_t ctrl, const char *username, const char *newuid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
char *uidstring = NULL;
uidstring = xstrdup (newuid);
trim_spaces (uidstring);
if (!*uidstring)
{
log_error ("%s\n", gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock);
if (err)
goto leave;
if (menu_adduid (ctrl, keyblock, 0, NULL, uidstring))
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark (ctrl);
}
leave:
xfree (uidstring);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Helper to find the UID node for namehash. On success, returns the UID node.
Otherwise, return NULL. */
kbnode_t
find_userid_by_namehash (kbnode_t keyblock, const char *namehash, int want_valid)
{
byte hash[NAMEHASH_LEN];
kbnode_t node = NULL;
if (!namehash)
goto leave;
if (strlen (namehash) != NAMEHASH_LEN * 2)
goto leave;
if (hex2bin (namehash, hash, NAMEHASH_LEN) < 0)
goto leave;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& (!want_valid || (!node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired)))
{
namehash_from_uid (node->pkt->pkt.user_id);
if (!memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN))
break;
}
}
leave:
return node;
}
/* Helper to find the UID node for uid. On success, returns the UID node.
Otherwise, return NULL. */
kbnode_t
find_userid (kbnode_t keyblock, const char *uid, int want_valid)
{
kbnode_t node = NULL;
size_t uidlen;
if (!keyblock || !uid)
goto leave;
/* First try to find UID by namehash. */
node = find_userid_by_namehash (keyblock, uid, want_valid);
if (node)
goto leave;
uidlen = strlen (uid);
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& (!want_valid || (!node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired))
&& uidlen == node->pkt->pkt.user_id->len
&& !memcmp (node->pkt->pkt.user_id->name, uid, uidlen))
break;
}
leave:
return node;
}
/* Unattended revocation of a keyid. USERNAME specifies the
key. UIDTOREV is the user id revoke from the key. */
void
keyedit_quick_revuid (ctrl_t ctrl, const char *username, const char *uidtorev)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
kbnode_t node;
int modified = 0;
size_t valid_uids;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. */
err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock);
if (err)
goto leave;
/* To make sure that we do not revoke the last valid UID, we first
count how many valid UIDs there are. */
valid_uids = 0;
for (node = keyblock; node; node = node->next)
valid_uids += (node->pkt->pkttype == PKT_USER_ID
&& !node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired);
/* Find the right UID. */
node = find_userid (keyblock, uidtorev, 0);
if (node)
{
struct revocation_reason_info *reason;
/* Make sure that we do not revoke the last valid UID. */
if (valid_uids == 1
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired)
{
log_error (_("cannot revoke the last valid user ID.\n"));
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
reason = get_default_uid_revocation_reason ();
err = core_revuid (ctrl, keyblock, node, reason, &modified);
release_revocation_reason_info (reason);
if (err)
goto leave;
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
revalidation_mark (ctrl);
goto leave;
}
err = gpg_error (GPG_ERR_NO_USER_ID);
leave:
if (err)
{
log_error (_("revoking the user ID failed: %s\n"), gpg_strerror (err));
write_status_error ("keyedit.revoke.uid", err);
}
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended setting of the primary uid. USERNAME specifies the key.
PRIMARYUID is the user id which shall be primary. */
void
keyedit_quick_set_primary (ctrl_t ctrl, const char *username,
const char *primaryuid)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
kbnode_t primarynode;
kbnode_t node;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock);
if (err)
{
write_status_error ("keyedit.primary", err);
goto leave;
}
/* Find the first matching UID that is valid */
primarynode = find_userid (keyblock, primaryuid, 1);
/* and mark it. */
if (primarynode)
for (node = keyblock; node; node = node->next)
{
if (node == primarynode)
node->flag |= NODFLG_SELUID;
else
node->flag &= ~NODFLG_SELUID;
}
if (!primarynode)
err = gpg_error (GPG_ERR_NO_USER_ID);
else if (menu_set_primary_uid (ctrl, keyblock))
{
merge_keys_and_selfsig (ctrl, keyblock);
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
revalidation_mark (ctrl);
}
else
err = gpg_error (GPG_ERR_GENERAL);
if (err)
{
log_error (_("setting the primary user ID failed: %s\n"),
gpg_strerror (err));
write_status_error ("keyedit.primary", err);
}
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended updating of the preference tro the standard preferences.
* USERNAME specifies the key. This is basically the same as
* gpg --edit-key <<userif> updpref save
*/
void
keyedit_quick_update_pref (ctrl_t ctrl, const char *username)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
err = quick_find_keyblock (ctrl, username, 1, &kdbhd, &keyblock);
if (err)
goto leave;
if (menu_set_preferences (ctrl, keyblock, 1))
{
merge_keys_and_selfsig (ctrl, keyblock);
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
leave:
if (err)
write_status_error ("keyedit.updpref", err);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended updating of the ownertrust or disable/enable state of a key
* USERNAME specifies the key. This is somewhat similar to
* gpg --edit-key <userid> trust save
* gpg --edit-key <userid> disable save
*
* VALUE is the new trust value which is one of:
* "undefined" - Ownertrust is set to undefined
* "never" - Ownertrust is set to never trust
* "marginal" - Ownertrust is set to marginal trust
* "full" - Ownertrust is set to full trust
* "ultimate" - Ownertrust is set to ultimate trust
* "enable" - The key is re-enabled.
* "disable" - The key is disabled.
* Trust settings do not change the ebable/disable state.
*/
void
keyedit_quick_set_ownertrust (ctrl_t ctrl, const char *username,
const char *value)
{
gpg_error_t err;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
PKT_public_key *pk;
unsigned int trust, newtrust;
int x;
int maybe_update_trust = 0;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. Note
* that we are looking for the public key here. */
err = quick_find_keyblock (ctrl, username, 0, &kdbhd, &keyblock);
if (err)
goto leave;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
pk = keyblock->pkt->pkt.public_key;
trust = newtrust = get_ownertrust (ctrl, pk);
if (!ascii_strcasecmp (value, "enable"))
newtrust &= ~TRUST_FLAG_DISABLED;
else if (!ascii_strcasecmp (value, "disable"))
newtrust |= TRUST_FLAG_DISABLED;
else if ((x = string_to_trust_value (value)) >= 0)
{
newtrust = x;
newtrust &= TRUST_MASK;
newtrust |= (trust & ~TRUST_MASK);
maybe_update_trust = 1;
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (trust != newtrust)
{
update_ownertrust (ctrl, pk, newtrust);
if (maybe_update_trust)
revalidation_mark (ctrl);
}
else if (opt.verbose)
log_info (_("Key not changed so no update needed.\n"));
leave:
if (err)
{
log_error (_("setting the ownertrust to '%s' failed: %s\n"),
value, gpg_strerror (err));
write_status_error ("keyedit.setownertrust", err);
}
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Find a keyblock by fingerprint because only this uniquely
* identifies a key and may thus be used to select a key for
* unattended subkey creation os key signing. */
static gpg_error_t
find_by_primary_fpr (ctrl_t ctrl, const char *fpr,
kbnode_t *r_keyblock, KEYDB_HANDLE *r_kdbhd)
{
gpg_error_t err;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
KEYDB_SEARCH_DESC desc;
byte fprbin[MAX_FINGERPRINT_LEN];
size_t fprlen;
*r_keyblock = NULL;
*r_kdbhd = NULL;
if (classify_user_id (fpr, &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, NULL, fpr, &keyblock, &kdbhd, 1);
if (err)
{
log_error (_("key \"%s\" not found: %s\n"), fpr, gpg_strerror (err));
goto leave;
}
/* Check that the primary fingerprint has been given. */
fingerprint_from_pk (keyblock->pkt->pkt.public_key, fprbin, &fprlen);
if (desc.mode == KEYDB_SEARCH_MODE_FPR
&& fprlen == desc.fprlen
&& !memcmp (fprbin, desc.u.fpr, fprlen))
;
else
{
log_error (_("\"%s\" is not the primary fingerprint\n"), fpr);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
*r_keyblock = keyblock;
keyblock = NULL;
*r_kdbhd = kdbhd;
kdbhd = NULL;
err = 0;
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
return err;
}
-/* Unattended key signing function. If the key specifified by FPR is
+/* Unattended key signing function. If the key specified by FPR is
available and FPR is the primary fingerprint all user ids of the
key are signed using the default signing key. If UIDS is an empty
list all usable UIDs are signed, if it is not empty, only those
user ids matching one of the entries of the list are signed. With
LOCAL being true the signatures are marked as non-exportable. */
void
keyedit_quick_sign (ctrl_t ctrl, const char *fpr, strlist_t uids,
strlist_t locusr, int local)
{
gpg_error_t err = 0;
kbnode_t keyblock = NULL;
KEYDB_HANDLE kdbhd = NULL;
int modified = 0;
PKT_public_key *pk;
kbnode_t node;
strlist_t sl;
int any;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
key and may thus be used to select a key for unattended key
signing. */
if (find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd))
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
/* Give some info in verbose. */
if (opt.verbose)
{
show_key_with_all_names (ctrl, es_stdout, keyblock, 0,
1/*with_revoker*/, 1/*with_fingerprint*/,
0, 0, 1);
es_fflush (es_stdout);
}
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), _(" Unable to sign.\n"));
err = gpg_error (GPG_ERR_CERT_REVOKED);
goto leave;
}
/* Set the flags according to the UIDS list. Fixme: We may want to
use classify_user_id along with dedicated compare functions so
that we match the same way as in the key lookup. */
any = 0;
menu_select_uid (keyblock, 0); /* Better clear the flags first. */
for (sl=uids; sl; sl = sl->next)
{
const char *name = sl->d;
int count = 0;
sl->flags &= ~(1|2); /* Clear flags used for error reporting. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->attrib_data)
;
else if (*name == '='
&& strlen (name+1) == uid->len
&& !memcmp (uid->name, name + 1, uid->len))
{ /* Exact match - we don't do a check for ambiguity
* in this case. */
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
}
else if (ascii_memistr (uid->name, uid->len,
*name == '*'? name+1:name))
{
node->flag |= NODFLG_SELUID;
if (any != -1)
{
sl->flags |= 1; /* Report as found. */
any = 1;
}
count++;
}
}
}
if (count > 1)
{
any = -1; /* Force failure at end. */
sl->flags |= 2; /* Report as ambiguous. */
}
}
/* Check whether all given user ids were found. */
for (sl=uids; sl; sl = sl->next)
if (!(sl->flags & 1))
any = -1; /* That user id was not found. */
/* Print an error if there was a problem with the user ids. */
if (uids && any < 1)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
es_fflush (es_stdout);
for (sl=uids; sl; sl = sl->next)
{
if ((sl->flags & 2))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_AMBIGUOUS_NAME));
else if (!(sl->flags & 1))
log_info (_("Invalid user ID '%s': %s\n"),
sl->d, gpg_strerror (GPG_ERR_NOT_FOUND));
}
log_error ("%s %s", _("No matching user IDs."), _("Nothing to sign.\n"));
err = gpg_error (GPG_ERR_NO_USER_ID);
goto leave;
}
/* Sign. */
sign_uids (ctrl, es_stdout, keyblock, locusr, &modified, local, 0, 0, 0, 1);
es_fflush (es_stdout);
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
if (update_trust)
revalidation_mark (ctrl);
leave:
if (err)
write_status_error ("keyedit.sign-key", err);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended revocation of a key signatures. USERNAME specifies the
* key; this should best be a fingerprint. SIGTOREV is the user-id of
* the key for which the key signature shall be removed. Only
* non-self-signatures can be removed with this functions. If
* AFFECTED_UIDS is not NULL only the key signatures on these user-ids
* are revoked. */
void
keyedit_quick_revsig (ctrl_t ctrl, const char *username, const char *sigtorev,
strlist_t affected_uids)
{
gpg_error_t err = 0;
int no_signing_key = 0;
KEYDB_HANDLE kdbhd = NULL;
kbnode_t keyblock = NULL;
PKT_public_key *primarypk; /* Points into KEYBLOCK. */
u32 *primarykid;
PKT_public_key *pksigtorev = NULL;
u32 *pksigtorevkid;
kbnode_t node, n;
int skip_remaining;
int consider_sig;
strlist_t sl;
struct sign_attrib attrib = { 0 };
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* Search the key; we don't want the whole getkey stuff here. Note
* that we are looking for the public key here. */
err = quick_find_keyblock (ctrl, username, 0, &kdbhd, &keyblock);
if (err)
goto leave;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY
|| keyblock->pkt->pkttype == PKT_SECRET_KEY);
primarypk = keyblock->pkt->pkt.public_key;
primarykid = pk_keyid (primarypk);
/* Get the signing key we want to revoke. This must be one of our
* signing keys. We will compare only the keyid because we don't
* assume that we have duplicated keyids on our own secret keys. If
* a there is a duplicated one we will notice this when creating the
* revocation. */
pksigtorev = xtrycalloc (1, sizeof *pksigtorev);
if (!pksigtorev)
{
err = gpg_error_from_syserror ();
goto leave;
}
pksigtorev->req_usage = PUBKEY_USAGE_CERT;
err = getkey_byname (ctrl, NULL, pksigtorev, sigtorev, 1, NULL);
if (err)
{
no_signing_key = 1;
goto leave;
}
pksigtorevkid = pk_keyid (pksigtorev);
/* Find the signatures we want to revoke and set a mark. */
skip_remaining = consider_sig = 0;
for (node = keyblock; node; node = node->next)
{
node->flag &= ~NODFLG_MARK_A;
if (skip_remaining)
;
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
skip_remaining = 1;
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
consider_sig = !affected_uids;
for (sl = affected_uids; !consider_sig && sl; sl = sl->next)
{
const char *name = sl->d;
if (uid->attrib_data)
;
else if (*name == '='
&& strlen (name+1) == uid->len
&& !memcmp (uid->name, name + 1, uid->len))
{ /* Exact match. */
consider_sig = 1;
}
else if (ascii_memistr (uid->name, uid->len,
*name == '*'? name+1:name))
{ /* Case-insensitive substring match. */
consider_sig = 1;
}
}
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
/* We need to sort the signatures so that we can figure out
* whether the key signature has been revoked or the
* revocation has been superseded by a new key
* signature. */
PKT_signature *sig;
unsigned int sigcount = 0;
kbnode_t *sigarray;
- /* Allocate an array large enogh for all signatures. */
+ /* Allocate an array large enough for all signatures. */
for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; n = n->next)
sigcount++;
sigarray = xtrycalloc (sigcount, sizeof *sigarray);
if (!sigarray)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Now fill the array with signatures we are interested in.
* We also move NODE forward to the end. */
sigcount = 0;
for (n=node; n && n->pkt->pkttype == PKT_SIGNATURE; node=n, n=n->next)
{
sig = n->pkt->pkt.signature;
if (!keyid_cmp (primarykid, sig->keyid))
continue; /* Ignore self-signatures. */
if (keyid_cmp (pksigtorevkid, sig->keyid))
continue; /* Ignore non-matching signatures. */
n->flag &= ~NODFLG_MARK_B; /* Clear flag used by cm_signode. */
sigarray[sigcount++] = n;
}
if (sigcount)
{
qsort (sigarray, sigcount, sizeof *sigarray, cmp_signodes);
/* log_debug ("Sorted signatures:\n"); */
/* for (idx=0; idx < sigcount; idx++) */
/* { */
/* sig = sigarray[idx]->pkt->pkt.signature; */
/* log_debug ("%s 0x%02x %s\n", keystr (sig->keyid), */
/* sig->sig_class, datestr_from_sig (sig)); */
/* } */
sig = sigarray[sigcount-1]->pkt->pkt.signature;
if ((consider_sig || !affected_uids) && IS_UID_REV (sig))
{
if (!opt.quiet)
log_info ("sig by %s already revoked at %s\n",
keystr (sig->keyid), datestr_from_sig (sig));
}
else if ((consider_sig && IS_UID_SIG (sig))
|| (!affected_uids && IS_KEY_SIG (sig)))
node->flag |= NODFLG_MARK_A; /* Select signature. */
}
xfree (sigarray);
}
}
/* Check whether any signatures were done by the given key. We do
* not return an error if none were found. */
for (node = keyblock; node; node = node->next)
if ((node->flag & NODFLG_MARK_A))
break;
if (!node)
{
if (opt.verbose)
log_info (_("Not signed by you.\n"));
err = 0;
goto leave;
}
/* Revoke all marked signatures. */
attrib.reason = get_default_sig_revocation_reason ();
reloop: /* (we must repeat because we are modifying the list) */
for (node = keyblock; node; node = node->next)
{
kbnode_t unode;
PKT_signature *sig;
PACKET *pkt;
if (!(node->flag & NODFLG_MARK_A))
continue;
node->flag &= ~NODFLG_MARK_A;
if (IS_KEY_SIG (node->pkt->pkt.signature))
unode = NULL;
else
{
unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
log_assert (unode);
}
attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable;
err = make_keysig_packet (ctrl, &sig, primarypk,
unode? unode->pkt->pkt.user_id : NULL,
NULL, pksigtorev, 0x30, 0, 0,
sign_mk_attrib, &attrib, NULL);
if (err)
{
log_error ("signing failed: %s\n", gpg_strerror (err));
goto leave;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
if (unode)
insert_kbnode (unode, new_kbnode (pkt), 0);
goto reloop;
}
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
revalidation_mark (ctrl);
leave:
if (err)
{
log_error (_("revoking the key signature failed: %s\n"),
gpg_strerror (err));
if (no_signing_key)
print_further_info ("error getting key used to make the key signature");
write_status_error ("keyedit.revoke.sig", err);
}
release_revocation_reason_info (attrib.reason);
free_public_key (pksigtorev);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended subkey creation function.
*
*/
void
keyedit_quick_addkey (ctrl_t ctrl, const char *fpr, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
kbnode_t keyblock;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended subkey
* creation. */
if ((err=find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)))
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
err = gpg_error (GPG_ERR_CERT_REVOKED);
goto leave;
}
/* Create the subkey. Note that the called function already prints
* an error message. */
if (!generate_subkeypair (ctrl, keyblock, algostr, usagestr, expirestr))
modified = 1;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
if (err)
write_status_error ("keyedit.addkey", err);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended ADSK setup function.
*
* FPR is the fingerprint of our key. ADSKFPR is the fingerprint of
* another subkey which we want to add as ADSK to our key.
*/
void
keyedit_quick_addadsk (ctrl_t ctrl, const char *fpr, const char *adskfpr)
{
gpg_error_t err;
kbnode_t keyblock;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended adsk
* adding. */
if ((err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd)))
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
err = gpg_error (GPG_ERR_CERT_REVOKED);
goto leave;
}
/* Locate and add the ADSK. Note that the called function already
* prints error messages. */
if (menu_addadsk (ctrl, keyblock, adskfpr))
modified = 1;
else
log_inc_errorcount (); /* (We use log_info in menu_adsk) */
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
}
leave:
if (err)
write_status_error ("keyedit.addadsk", err);
release_kbnode (keyblock);
keydb_release (kdbhd);
}
/* Unattended expiration setting function for the main key. If
* SUBKEYFPRS is not NULL and SUBKEYSFPRS[0] is neither NULL, it is
* expected to be an array of fingerprints for subkeys to change. It
* may also be an array with only the item "*" to indicate that all
* keys shall be set to that expiration date.
*/
void
keyedit_quick_set_expire (ctrl_t ctrl, const char *fpr, const char *expirestr,
char **subkeyfprs)
{
gpg_error_t err;
kbnode_t keyblock, node;
KEYDB_HANDLE kdbhd;
int modified = 0;
PKT_public_key *pk;
u32 expire;
int primary_only = 0;
int idx;
#ifdef HAVE_W32_SYSTEM
/* See keyedit_menu for why we need this. */
check_trustdb_stale (ctrl);
#endif
/* We require a fingerprint because only this uniquely identifies a
* key and may thus be used to select a key for unattended
* expiration setting. */
err = find_by_primary_fpr (ctrl, fpr, &keyblock, &kdbhd);
if (err)
goto leave;
if (fix_keyblock (ctrl, &keyblock))
modified++;
pk = keyblock->pkt->pkt.public_key;
if (pk->flags.revoked)
{
if (!opt.verbose)
show_key_with_all_names (ctrl, es_stdout, keyblock, 0, 0, 0, 0, 0, 1);
log_error ("%s%s", _("Key is revoked."), "\n");
err = gpg_error (GPG_ERR_CERT_REVOKED);
goto leave;
}
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
log_error (_("'%s' is not a valid expiration time\n"), expirestr);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (expire)
expire += make_timestamp ();
/* Check whether a subkey's expiration time shall be changed or the
* expiration time of all keys. */
if (!subkeyfprs || !subkeyfprs[0])
primary_only = 1;
else if ( !strcmp (subkeyfprs[0], "*") && !subkeyfprs[1])
{
/* Change all subkeys keys which have not been revoked and are
* not yet expired. */
merge_keys_and_selfsig (ctrl, keyblock);
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (pk = node->pkt->pkt.public_key)
&& !pk->flags.revoked
&& !pk->has_expired)
node->flag |= NODFLG_SELKEY;
}
}
else
{
/* Change specified subkeys. */
KEYDB_SEARCH_DESC desc;
byte fprbin[MAX_FINGERPRINT_LEN];
size_t fprlen;
err = 0;
merge_keys_and_selfsig (ctrl, keyblock);
for (idx=0; subkeyfprs[idx]; idx++)
{
int any = 0;
/* Parse the fingerprint. */
if (classify_user_id (subkeyfprs[idx], &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_error (_("\"%s\" is not a proper fingerprint\n"),
subkeyfprs[idx] );
if (!err)
err = gpg_error (GPG_ERR_INV_NAME);
continue;
}
/* Set the flag for the matching non revoked subkey. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (pk = node->pkt->pkt.public_key)
&& !pk->flags.revoked )
{
fingerprint_from_pk (pk, fprbin, &fprlen);
if (fprlen == 20 && !memcmp (fprbin, desc.u.fpr, 20))
{
node->flag |= NODFLG_SELKEY;
any = 1;
}
}
}
if (!any)
{
log_error (_("subkey \"%s\" not found\n"), subkeyfprs[idx]);
if (!err)
err = gpg_error (GPG_ERR_NOT_FOUND);
}
}
if (err)
goto leave;
}
/* Set the new expiration date. */
err = menu_expire (ctrl, keyblock, primary_only? 1 : 2, expire);
if (gpg_err_code (err) == GPG_ERR_TRUE)
modified = 1;
else if (err)
goto leave;
es_fflush (es_stdout);
/* Store. */
if (modified)
{
err = keydb_update_keyblock (ctrl, kdbhd, keyblock);
if (err)
{
log_error (_("update failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (update_trust)
revalidation_mark (ctrl);
}
else
log_info (_("Key not changed so no update needed.\n"));
leave:
release_kbnode (keyblock);
keydb_release (kdbhd);
if (err)
write_status_error ("set_expire", err);
}
static void
tty_print_notations (int indent, PKT_signature * sig)
{
int first = 1;
struct notation *notation, *nd;
if (indent < 0)
{
first = 0;
indent = -indent;
}
notation = sig_to_notation (sig);
for (nd = notation; nd; nd = nd->next)
{
if (!first)
tty_printf ("%*s", indent, "");
else
first = 0;
tty_print_utf8_string (nd->name, strlen (nd->name));
tty_printf ("=");
tty_print_utf8_string (nd->value, strlen (nd->value));
tty_printf ("\n");
}
free_notation (notation);
}
/*
* Show preferences of a public keyblock.
*/
static void
show_prefs (PKT_user_id * uid, PKT_signature * selfsig, int verbose)
{
if (!uid)
return;
if (verbose)
{
show_preferences (uid, 4, -1, 1);
if (selfsig)
{
const byte *pref_ks;
size_t pref_ks_len;
pref_ks = parse_sig_subpkt (selfsig, 1,
SIGSUBPKT_PREF_KS, &pref_ks_len);
if (pref_ks && pref_ks_len)
{
tty_printf (" ");
tty_printf (_("Preferred keyserver: "));
tty_print_utf8_string (pref_ks, pref_ks_len);
tty_printf ("\n");
}
if (selfsig->flags.notation)
{
tty_printf (" ");
tty_printf (_("Notations: "));
tty_print_notations (5 + strlen (_("Notations: ")), selfsig);
}
}
}
else
{
show_preferences (uid, 4, -1, 0);
}
}
/* This is the version of show_key_with_all_names used when
opt.with_colons is used. It prints all available data in a easy to
parse format and does not translate utf8 */
static void
show_key_with_all_names_colon (ctrl_t ctrl, estream_t fp, kbnode_t keyblock)
{
KBNODE node;
int i, j, ulti_hack = 0;
byte pk_version = 0;
PKT_public_key *primary = NULL;
int have_seckey;
if (!fp)
fp = es_stdout;
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (node->pkt->pkttype == PKT_PUBLIC_SUBKEY))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
u32 keyid[2];
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk_version = pk->version;
primary = pk;
}
keyid_from_pk (pk, keyid);
have_seckey = agent_probe_secret_key (ctrl, pk);
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
es_fputs (have_seckey? "sec:" : "pub:", fp);
else
es_fputs (have_seckey? "ssb:" : "sub:", fp);
if (!pk->flags.valid)
es_putc ('i', fp);
else if (pk->flags.revoked)
es_putc ('r', fp);
else if (pk->has_expired)
es_putc ('e', fp);
else if (!(opt.fast_list_mode || opt.no_expensive_trust_checks))
{
int trust = get_validity_info (ctrl, keyblock, pk, NULL);
if (trust == 'u')
ulti_hack = 1;
es_putc (trust, fp);
}
es_fprintf (fp, ":%u:%d:%08lX%08lX:%lu:%lu::",
nbits_from_pk (pk),
pk->pubkey_algo,
(ulong) keyid[0], (ulong) keyid[1],
(ulong) pk->timestamp, (ulong) pk->expiredate);
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& !(opt.fast_list_mode || opt.no_expensive_trust_checks))
es_putc (get_ownertrust_info (ctrl, pk, 0), fp);
es_putc (':', fp);
es_putc (':', fp);
es_putc (':', fp);
/* Print capabilities. */
if ((pk->pubkey_usage & PUBKEY_USAGE_ENC))
es_putc ('e', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_SIG))
es_putc ('s', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_CERT))
es_putc ('c', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_AUTH))
es_putc ('a', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_RENC))
es_putc ('r', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_TIME))
es_putc ('t', fp);
if ((pk->pubkey_usage & PUBKEY_USAGE_GROUP))
es_putc ('g', fp);
es_putc ('\n', fp);
print_fingerprint (ctrl, fp, pk, 0);
print_revokers (fp, pk);
}
}
/* the user ids */
i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (uid->attrib_data)
es_fputs ("uat:", fp);
else
es_fputs ("uid:", fp);
if (uid->flags.revoked)
es_fputs ("r::::::::", fp);
else if (uid->flags.expired)
es_fputs ("e::::::::", fp);
else if (opt.fast_list_mode || opt.no_expensive_trust_checks)
es_fputs ("::::::::", fp);
else
{
int uid_validity;
if (primary && !ulti_hack)
uid_validity = get_validity_info (ctrl, keyblock, primary, uid);
else
uid_validity = 'u';
es_fprintf (fp, "%c::::::::", uid_validity);
}
if (uid->attrib_data)
es_fprintf (fp, "%u %lu", uid->numattribs, uid->attrib_len);
else
es_write_sanitized (fp, uid->name, uid->len, ":", NULL);
es_putc (':', fp);
/* signature class */
es_putc (':', fp);
/* capabilities */
es_putc (':', fp);
/* preferences */
if (pk_version > 3 || uid->selfsigversion > 3)
{
const prefitem_t *prefs = uid->prefs;
for (j = 0; prefs && prefs[j].type; j++)
{
if (j)
es_putc (' ', fp);
es_fprintf (fp,
"%c%d", prefs[j].type == PREFTYPE_SYM ? 'S' :
prefs[j].type == PREFTYPE_HASH ? 'H' :
prefs[j].type == PREFTYPE_ZIP ? 'Z' : '?',
prefs[j].value);
}
if (uid->flags.mdc)
es_fputs (",mdc", fp);
if (uid->flags.aead)
es_fputs (",aead", fp);
if (!uid->flags.ks_modify)
es_fputs (",no-ks-modify", fp);
}
es_putc (':', fp);
/* flags */
es_fprintf (fp, "%d,", i);
if (uid->flags.primary)
es_putc ('p', fp);
if (uid->flags.revoked)
es_putc ('r', fp);
if (uid->flags.expired)
es_putc ('e', fp);
if ((node->flag & NODFLG_SELUID))
es_putc ('s', fp);
if ((node->flag & NODFLG_MARK_A))
es_putc ('m', fp);
es_putc (':', fp);
if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
#ifdef USE_TOFU
enum tofu_policy policy;
if (! tofu_get_policy (ctrl, primary, uid, &policy)
&& policy != TOFU_POLICY_NONE)
es_fprintf (fp, "%s", tofu_policy_str (policy));
#endif /*USE_TOFU*/
}
es_putc (':', fp);
es_putc ('\n', fp);
}
}
}
static void
show_names (ctrl_t ctrl, estream_t fp,
kbnode_t keyblock, PKT_public_key * pk, unsigned int flag,
int with_prefs)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID && !is_deleted_kbnode (node))
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
if (!flag || (flag && (node->flag & flag)))
{
if (!(flag & NODFLG_MARK_A) && pk)
tty_fprintf (fp, "%s ", uid_trust_string_fixed (ctrl, pk, uid));
if (flag & NODFLG_MARK_A)
tty_fprintf (fp, " ");
else if (node->flag & NODFLG_SELUID)
tty_fprintf (fp, "(%d)* ", i);
else if (uid->flags.primary)
tty_fprintf (fp, "(%d). ", i);
else
tty_fprintf (fp, "(%d) ", i);
tty_print_utf8_string2 (fp, uid->name, uid->len, 0);
tty_fprintf (fp, "\n");
if (with_prefs && pk)
{
if (pk->version > 3 || uid->selfsigversion > 3)
{
PKT_signature *selfsig = NULL;
KBNODE signode;
for (signode = node->next;
signode && signode->pkt->pkttype == PKT_SIGNATURE;
signode = signode->next)
{
if (signode->pkt->pkt.signature->
flags.chosen_selfsig)
{
selfsig = signode->pkt->pkt.signature;
break;
}
}
show_prefs (uid, selfsig, with_prefs == 2);
}
else
tty_fprintf (fp, _("There are no preferences on a"
" PGP 2.x-style user ID.\n"));
}
}
}
}
}
/*
* Display the key a the user ids, if only_marked is true, do only so
* for user ids with mark A flag set and do not display the index
* number. If FP is not NULL print to the given stream and not to the
* tty (ignored in with-colons mode).
*/
static void
show_key_with_all_names (ctrl_t ctrl, estream_t fp,
KBNODE keyblock, int only_marked, int with_revoker,
int with_fpr, int with_subkeys, int with_prefs,
int nowarn)
{
gpg_error_t err;
kbnode_t node;
int i;
int do_warn = 0;
int have_seckey = 0;
char *serialno = NULL;
PKT_public_key *primary = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
if (opt.with_colons)
{
show_key_with_all_names_colon (ctrl, fp, keyblock);
return;
}
/* the keys */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| (with_subkeys && node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& !is_deleted_kbnode (node)))
{
PKT_public_key *pk = node->pkt->pkt.public_key;
const char *otrust = "err";
const char *trust = "err";
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
/* do it here, so that debug messages don't clutter the
* output */
static int did_warn = 0;
trust = get_validity_string (ctrl, pk, NULL);
otrust = get_ownertrust_string (ctrl, pk, 0);
/* Show a warning once */
if (!did_warn
&& (get_validity (ctrl, keyblock, pk, NULL, NULL, 0)
& TRUST_FLAG_PENDING_CHECK))
{
did_warn = 1;
do_warn = 1;
}
primary = pk;
}
if (pk->flags.revoked)
{
char *user = get_user_id_string_native (ctrl, pk->revoked.keyid);
tty_fprintf (fp,
_("The following key was revoked on"
" %s by %s key %s\n"),
revokestr_from_pk (pk),
gcry_pk_algo_name (pk->revoked.algo), user);
xfree (user);
}
if (with_revoker)
{
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
for (i = 0; i < pk->numrevkeys; i++)
{
u32 r_keyid[2];
char *user;
const char *algo;
algo = gcry_pk_algo_name (pk->revkey[i].algid);
keyid_from_fingerprint (ctrl, pk->revkey[i].fpr,
pk->revkey[i].fprlen, r_keyid);
user = get_user_id_string_native (ctrl, r_keyid);
tty_fprintf (fp,
_("This key may be revoked by %s key %s"),
algo ? algo : "?", user);
if (pk->revkey[i].class & 0x40)
{
tty_fprintf (fp, " ");
tty_fprintf (fp, _("(sensitive)"));
}
tty_fprintf (fp, "\n");
xfree (user);
}
}
keyid_from_pk (pk, NULL);
xfree (serialno);
serialno = NULL;
{
char *hexgrip;
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("error computing a keygrip: %s\n",
gpg_strerror (err));
have_seckey = 0;
}
else
have_seckey = !agent_get_keyinfo (ctrl, hexgrip, &serialno, NULL);
xfree (hexgrip);
}
tty_fprintf
(fp, "%s%c %s/%s",
node->pkt->pkttype == PKT_PUBLIC_KEY && have_seckey? "sec" :
node->pkt->pkttype == PKT_PUBLIC_KEY ? "pub" :
have_seckey ? "ssb" :
"sub",
(node->flag & NODFLG_SELKEY) ? '*' : ' ',
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr (pk->keyid));
if (opt.legacy_list_mode)
tty_fprintf (fp, " ");
else
tty_fprintf (fp, "\n ");
tty_fprintf (fp, _("created: %s"), datestr_from_pk (pk));
tty_fprintf (fp, " ");
if (pk->flags.revoked)
tty_fprintf (fp, _("revoked: %s"), revokestr_from_pk (pk));
else if (pk->has_expired)
tty_fprintf (fp, _("expired: %s"), expirestr_from_pk (pk));
else
tty_fprintf (fp, _("expires: %s"), expirestr_from_pk (pk));
tty_fprintf (fp, " ");
tty_fprintf (fp, _("usage: %s"), usagestr_from_pk (pk, 1));
tty_fprintf (fp, "\n");
if (serialno)
{
/* The agent told us that a secret key is available and
that it has been stored on a card. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (strlen (serialno) == 32
&& !strncmp (serialno, "D27600012401", 12))
{
/* This is an OpenPGP card. Print the relevant part. */
/* Example: D2760001240101010001000003470000 */
/* xxxxyyyyyyyy */
tty_fprintf (fp, "%.*s %.*s\n",
4, serialno+16, 8, serialno+20);
}
else
tty_fprintf (fp, "%s\n", serialno);
}
else if (pk->seckey_info
&& pk->seckey_info->is_protected
&& pk->seckey_info->s2k.mode == 1002)
{
/* FIXME: Check whether this code path is still used. */
tty_fprintf (fp, "%*s%s", opt.legacy_list_mode? 21:5, "",
_("card-no: "));
if (pk->seckey_info->ivlen == 16
&& !memcmp (pk->seckey_info->iv,
"\xD2\x76\x00\x01\x24\x01", 6))
{
/* This is an OpenPGP card. */
for (i = 8; i < 14; i++)
{
if (i == 10)
tty_fprintf (fp, " ");
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
}
else
{
/* Unknown card: Print all. */
for (i = 0; i < pk->seckey_info->ivlen; i++)
tty_fprintf (fp, "%02X", pk->seckey_info->iv[i]);
}
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
if (opt.trust_model != TM_ALWAYS)
{
tty_fprintf (fp, "%*s",
opt.legacy_list_mode?
((int) keystrlen () + 13):5, "");
/* Ownertrust is only meaningful for the PGP or
classic trust models, or PGP combined with TOFU */
if (opt.trust_model == TM_PGP
|| opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP)
{
int width = 14 - strlen (otrust);
if (width <= 0)
width = 1;
tty_fprintf (fp, _("trust: %s"), otrust);
tty_fprintf (fp, "%*s", width, "");
}
tty_fprintf (fp, _("validity: %s"), trust);
tty_fprintf (fp, "\n");
}
if (node->pkt->pkttype == PKT_PUBLIC_KEY
&& (get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED))
{
tty_fprintf (fp, "*** ");
tty_fprintf (fp, _("This key has been disabled"));
tty_fprintf (fp, "\n");
}
}
if ((node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY) && with_fpr)
{
print_fingerprint (ctrl, fp, pk, 2);
tty_fprintf (fp, "\n");
}
}
}
show_names (ctrl, fp,
keyblock, primary, only_marked ? NODFLG_MARK_A : 0, with_prefs);
if (do_warn && !nowarn)
tty_fprintf (fp, _("Please note that the shown key validity"
" is not necessarily correct\n"
"unless you restart the program.\n"));
xfree (serialno);
}
/* Display basic key information. This function is suitable to show
* information on the key without any dependencies on the trustdb or
* any other internal GnuPG stuff. KEYBLOCK may either be a public or
* a secret key. This function may be called with KEYBLOCK containing
* secret keys and thus the printing of "pub" vs. "sec" does only
* depend on the packet type and not by checking with gpg-agent. If
* PRINT_SEC is set "sec" is printed instead of "pub". */
void
show_basic_key_info (ctrl_t ctrl, kbnode_t keyblock, int print_sec)
{
KBNODE node;
int i;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* The primary key */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_SECRET_KEY)
{
PKT_public_key *pk = node->pkt->pkt.public_key;
const char *tag;
if (node->pkt->pkttype == PKT_SECRET_KEY || print_sec)
tag = "sec";
else
tag = "pub";
/* Note, we use the same format string as in other show
functions to make the translation job easier. */
tty_printf ("%s %s/%s ",
tag,
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk (pk));
tty_printf (_("created: %s"), datestr_from_pk (pk));
tty_printf (" ");
tty_printf (_("expires: %s"), expirestr_from_pk (pk));
tty_printf ("\n");
print_fingerprint (ctrl, NULL, pk, 3);
tty_printf ("\n");
}
}
/* The user IDs. */
(void)i; /* Counting User IDs */
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
++i;
tty_printf (" ");
if (uid->flags.revoked)
tty_printf ("[%s] ", _("revoked"));
else if (uid->flags.expired)
tty_printf ("[%s] ", _("expired"));
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
}
}
static void
show_key_and_fingerprint (ctrl_t ctrl, kbnode_t keyblock, int with_subkeys)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("pub %s/%s %s ",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk));
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_print_utf8_string (uid->name, uid->len);
break;
}
}
tty_printf ("\n");
if (pk)
print_fingerprint (ctrl, NULL, pk, 2);
if (with_subkeys)
{
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("sub %s/%s %s [%s]\n",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
print_fingerprint (ctrl, NULL, pk, 4);
}
}
}
}
/* Show a listing of the primary and its subkeys along with their
keygrips. */
static void
show_key_and_grip (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
char *hexgrip;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
tty_printf ("%s %s/%s %s [%s]\n",
node->pkt->pkttype == PKT_PUBLIC_KEY? "pub":"sub",
pubkey_string (pk, pkstrbuf, sizeof pkstrbuf),
keystr_from_pk(pk),
datestr_from_pk (pk),
usagestr_from_pk (pk, 0));
if (!hexkeygrip_from_pk (pk, &hexgrip))
{
tty_printf (" Keygrip: %s\n", hexgrip);
xfree (hexgrip);
}
}
}
}
/* Show a warning if no uids on the key have the primary uid flag
set. */
static void
no_primary_warning (KBNODE keyblock)
{
KBNODE node;
int have_primary = 0, uid_count = 0;
/* TODO: if we ever start behaving differently with a primary or
non-primary attribute ID, we will need to check for attributes
here as well. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID
&& node->pkt->pkt.user_id->attrib_data == NULL)
{
uid_count++;
if (node->pkt->pkt.user_id->flags.primary == 2)
{
have_primary = 1;
break;
}
}
}
if (uid_count > 1 && !have_primary)
log_info (_
("WARNING: no user ID has been marked as primary. This command"
" may\n cause a different user ID to become"
" the assumed primary.\n"));
}
/* Print a warning if the latest encryption subkey expires soon. This
function is called after the expire data of the primary key has
been changed. */
static void
subkey_expire_warning (kbnode_t keyblock)
{
u32 curtime = make_timestamp ();
kbnode_t node;
PKT_public_key *pk;
/* u32 mainexpire = 0; */
u32 subexpire = 0;
u32 latest_date = 0;
for (node = keyblock; node; node = node->next)
{
/* if (node->pkt->pkttype == PKT_PUBLIC_KEY) */
/* { */
/* pk = node->pkt->pkt.public_key; */
/* mainexpire = pk->expiredate; */
/* } */
if (node->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
pk = node->pkt->pkt.public_key;
if (!pk->flags.valid)
continue;
if (pk->flags.revoked)
continue;
if (pk->timestamp > curtime)
continue; /* Ignore future keys. */
if (!(pk->pubkey_usage & PUBKEY_USAGE_ENC))
continue; /* Not an encryption key. */
if (pk->timestamp > latest_date || (!pk->timestamp && !latest_date))
{
latest_date = pk->timestamp;
subexpire = pk->expiredate;
}
}
if (!subexpire)
return; /* No valid subkey with an expiration time. */
if (curtime + (10*86400) > subexpire)
{
log_info (_("WARNING: Your encryption subkey expires soon.\n"));
log_info (_("You may want to change its expiration date too.\n"));
}
}
/* Print a warning if all encryption (sub|primary)keys are expired.
* The warning is not printed if there is no encryption
* (sub|primary)key at all. This function is called after the expire
* data of the primary key has been changed. */
void
no_usable_encr_subkeys_warning (kbnode_t keyblock)
{
kbnode_t node;
PKT_public_key *pk;
int any_encr_key = 0;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
if ((pk->pubkey_usage & PUBKEY_USAGE_ENC))
{
any_encr_key = 1;
if (pk->flags.valid && !pk->has_expired && !pk->flags.revoked
&& !pk->flags.disabled)
{
return; /* Key is usable for encryption */
}
}
}
}
if (any_encr_key && !opt.quiet)
log_info (_("WARNING: No valid encryption subkey left over.\n"));
}
/*
* Ask for a new user id, add the self-signature, and update the
* keyblock. If UIDSTRING is not NULL the user ID is generated
* unattended using that string. UIDSTRING is expected to be utf-8
* encoded and white space trimmed. Returns true if there is a new
* user id.
*/
static int
menu_adduid (ctrl_t ctrl, kbnode_t pub_keyblock,
int photo, const char *photo_name, const char *uidstring)
{
PKT_user_id *uid;
PKT_public_key *pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
KBNODE node;
KBNODE pub_where = NULL;
gpg_error_t err;
if (photo && uidstring)
return 0; /* Not allowed. */
for (node = pub_keyblock; node; pub_where = node, node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break;
}
if (!node) /* No subkey. */
pub_where = NULL;
log_assert (pk);
if (photo)
{
int hasattrib = 0;
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->attrib_data != NULL)
{
hasattrib = 1;
break;
}
/* It is legal but bad for compatibility to add a photo ID to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a photo on a v3 key.
Don't bother to ask this if the key already has a photo - any
damage has already been done at that point. -dms */
if (pk->version == 3 && !hasattrib)
{
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP2-style key. "
"Adding a photo ID may cause some versions\n"
" of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_photo.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a photo ID to "
"a PGP2-style key.\n"));
return 0;
}
}
uid = generate_photo_id (ctrl, pk, photo_name);
}
else
uid = generate_user_id (pub_keyblock, uidstring);
if (!uid)
{
if (uidstring)
{
write_status_error ("adduid", gpg_error (304));
log_error ("%s\n", _("Such a user ID already exists on this key!"));
}
return 0;
}
err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x13, 0, 0,
keygen_add_std_prefs, pk, NULL);
if (err)
{
write_status_error ("keysig", err);
log_error ("signing failed: %s\n", gpg_strerror (err));
free_user_id (uid);
return 0;
}
/* Insert/append to public keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_USER_ID;
pkt->pkt.user_id = uid;
node = new_kbnode (pkt);
if (pub_where)
insert_kbnode (pub_where, node, 0);
else
add_kbnode (pub_keyblock, node);
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
if (pub_where)
insert_kbnode (node, new_kbnode (pkt), 0);
else
add_kbnode (pub_keyblock, new_kbnode (pkt));
return 1;
}
/*
* Remove all selected userids from the keyring
*/
static void
menu_deluid (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
selected = node->flag & NODFLG_SELUID;
if (selected)
{
/* Only cause a trust update if we delete a
non-revoked user id */
if (!node->pkt->pkt.user_id->flags.revoked)
update_trust = 1;
delete_kbnode (node);
}
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
selected = 0;
}
commit_kbnode (&pub_keyblock);
}
static int
menu_delsig (ctrl_t ctrl, kbnode_t pub_keyblock)
{
KBNODE node;
PKT_user_id *uid = NULL;
int changed = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
uid = (node->flag & NODFLG_SELUID) ? node->pkt->pkt.user_id : NULL;
}
else if (uid && node->pkt->pkttype == PKT_SIGNATURE)
{
int okay, valid, selfsig, inv_sig, no_key, other_err;
tty_printf ("uid ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
okay = inv_sig = no_key = other_err = 0;
if (opt.with_colons)
valid = print_and_check_one_sig_colon (ctrl, pub_keyblock, node,
&inv_sig, &no_key,
&other_err, &selfsig, 1);
else
valid = print_and_check_one_sig (ctrl, pub_keyblock, node,
&inv_sig, &no_key, &other_err,
&selfsig, 1, 0);
if (valid)
{
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.valid",
_("Delete this good signature? (y/N/q)"));
/* Only update trust if we delete a good signature.
The other two cases do not affect trust. */
if (okay)
update_trust = 1;
}
else if (inv_sig || other_err)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.invalid",
_("Delete this invalid signature? (y/N/q)"));
else if (no_key)
okay = cpr_get_answer_yes_no_quit
("keyedit.delsig.unknown",
_("Delete this unknown signature? (y/N/q)"));
if (okay == -1)
break;
if (okay && selfsig
&& !cpr_get_answer_is_yes
("keyedit.delsig.selfsig",
_("Really delete this self-signature? (y/N)")))
okay = 0;
if (okay)
{
delete_kbnode (node);
changed++;
}
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
uid = NULL;
}
if (changed)
{
commit_kbnode (&pub_keyblock);
tty_printf (ngettext("Deleted %d signature.\n",
"Deleted %d signatures.\n", changed), changed);
}
else
tty_printf (_("Nothing deleted.\n"));
return changed;
}
/* Note: OPTIONS are from the EXPORT_* set. */
static int
menu_clean (ctrl_t ctrl, kbnode_t keyblock, unsigned int options)
{
KBNODE uidnode;
int modified = 0;
int select_all = !count_selected_uids (keyblock);
for (uidnode = keyblock->next;
uidnode && uidnode->pkt->pkttype != PKT_PUBLIC_SUBKEY;
uidnode = uidnode->next)
{
if (uidnode->pkt->pkttype == PKT_USER_ID
&& (uidnode->flag & NODFLG_SELUID || select_all))
{
int uids = 0, sigs = 0;
char *user = utf8_to_native (uidnode->pkt->pkt.user_id->name,
uidnode->pkt->pkt.user_id->len,
0);
clean_one_uid (ctrl, keyblock, uidnode, opt.verbose, options,
&uids, &sigs);
if (uids)
{
const char *reason;
if (uidnode->pkt->pkt.user_id->flags.revoked)
reason = _("revoked");
else if (uidnode->pkt->pkt.user_id->flags.expired)
reason = _("expired");
else
reason = _("invalid");
tty_printf (_("User ID \"%s\" compacted: %s\n"), user, reason);
modified = 1;
}
else if (sigs)
{
tty_printf (ngettext("User ID \"%s\": %d signature removed\n",
"User ID \"%s\": %d signatures removed\n",
sigs), user, sigs);
modified = 1;
}
else
{
tty_printf ((options & EXPORT_MINIMAL)?
_("User ID \"%s\": already minimized\n") :
_("User ID \"%s\": already clean\n"), user);
}
xfree (user);
}
}
return modified;
}
/*
* Remove some of the secondary keys
*/
static void
menu_delkey (KBNODE pub_keyblock)
{
KBNODE node;
int selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
selected = node->flag & NODFLG_SELKEY;
if (selected)
delete_kbnode (node);
}
else if (selected && node->pkt->pkttype == PKT_SIGNATURE)
delete_kbnode (node);
else
selected = 0;
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys are no
longer used to certify other keys, so there is no change in
trust when revoking/removing them. */
}
/*
* Ask for a new revoker, create the self-signature and put it into
* the keyblock. Returns true if there is a new revoker.
*/
static int
menu_addrevoker (ctrl_t ctrl, kbnode_t pub_keyblock, int sensitive)
{
PKT_public_key *pk = NULL;
PKT_public_key *revoker_pk = NULL;
PKT_signature *sig = NULL;
PACKET *pkt;
struct revocation_key revkey;
size_t fprlen;
int rc;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
pk = pub_keyblock->pkt->pkt.public_key;
if (pk->numrevkeys == 0 && pk->version == 3)
{
/* It is legal but bad for compatibility to add a revoker to a
v3 key as it means that PGP2 will not be able to use that key
anymore. Also, PGP may not expect a revoker on a v3 key.
Don't bother to ask this if the key already has a revoker -
any damage has already been done at that point. -dms */
if (opt.expert)
{
tty_printf (_("WARNING: This is a PGP 2.x-style key. "
"Adding a designated revoker may cause\n"
" some versions of PGP to reject this key.\n"));
if (!cpr_get_answer_is_yes ("keyedit.v3_revoker.okay",
_("Are you sure you still want "
"to add it? (y/N) ")))
return 0;
}
else
{
tty_printf (_("You may not add a designated revoker to "
"a PGP 2.x-style key.\n"));
return 0;
}
}
for (;;)
{
char *answer;
free_public_key (revoker_pk);
revoker_pk = xmalloc_clear (sizeof (*revoker_pk));
tty_printf ("\n");
answer = cpr_get_utf8
("keyedit.add_revoker",
_("Enter the user ID of the designated revoker: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
goto fail;
}
/* Note that I'm requesting CERT here, which usually implies
primary keys only, but some casual testing shows that PGP and
GnuPG both can handle a designated revocation from a subkey. */
revoker_pk->req_usage = PUBKEY_USAGE_CERT;
rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, revoker_pk, answer, NULL, NULL, 1);
if (rc)
{
log_error (_("key \"%s\" not found: %s\n"), answer,
gpg_strerror (rc));
xfree (answer);
continue;
}
xfree (answer);
fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen);
if (fprlen != 20 && fprlen != 32)
{
log_error (_("cannot appoint a PGP 2.x style key as a "
"designated revoker\n"));
continue;
}
revkey.fprlen = fprlen;
revkey.class = 0x80;
if (sensitive)
revkey.class |= 0x40;
revkey.algid = revoker_pk->pubkey_algo;
if (cmp_public_keys (revoker_pk, pk) == 0)
{
/* This actually causes no harm (after all, a key that
designates itself as a revoker is the same as a
regular key), but it's easy enough to check. */
log_error (_("you cannot appoint a key as its own "
"designated revoker\n"));
continue;
}
keyid_from_pk (pk, NULL);
/* Does this revkey already exist? */
if (!pk->revkey && pk->numrevkeys)
BUG ();
else
{
int i;
for (i = 0; i < pk->numrevkeys; i++)
{
if (memcmp (&pk->revkey[i], &revkey,
sizeof (struct revocation_key)) == 0)
{
char buf[50];
log_error (_("this key has already been designated "
"as a revoker\n"));
format_keyid (pk_keyid (pk), KF_LONG, buf, sizeof (buf));
write_status_text (STATUS_ALREADY_SIGNED, buf);
break;
}
}
if (i < pk->numrevkeys)
continue;
}
print_key_info (ctrl, NULL, 0, revoker_pk, 0);
print_fingerprint (ctrl, NULL, revoker_pk, 2);
tty_printf ("\n");
tty_printf (_("WARNING: appointing a key as a designated revoker "
"cannot be undone!\n"));
tty_printf ("\n");
if (!cpr_get_answer_is_yes ("keyedit.add_revoker.okay",
_("Are you sure you want to appoint this "
"key as a designated revoker? (y/N) ")))
continue;
free_public_key (revoker_pk);
revoker_pk = NULL;
break;
}
rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk, 0x1F, 0, 0,
keygen_add_revkey, &revkey, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error ("signing failed: %s\n", gpg_strerror (rc));
goto fail;
}
/* Insert into public keyblock. */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), PKT_SIGNATURE);
return 1;
fail:
if (sig)
free_seckey_enc (sig);
free_public_key (revoker_pk);
return 0;
}
/*
* Ask for a new additional decryption subkey and add it to the key
* block. Returns true if the keyblock was changed and false
* otherwise. If ADSKFPR is not NULL, this function has been called
* by quick_addadsk and gives the fingerprint of the to be added key.
*/
static int
menu_addadsk (ctrl_t ctrl, kbnode_t pub_keyblock, const char *adskfpr)
{
PKT_public_key *pk;
PKT_public_key *sub_pk;
PKT_public_key *main_pk;
PKT_public_key *adsk_pk = NULL;
kbnode_t adsk_keyblock = NULL;
PKT_signature *sig = NULL;
char *answer = NULL;
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
byte fpr[MAX_FINGERPRINT_LEN];
size_t fprlen;
kbnode_t node, node2;
kbnode_t subkeynode = NULL;
PACKET *pkt; /* (temp. use; will be put into a kbnode_t) */
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
main_pk = pub_keyblock->pkt->pkt.public_key;
for (;;)
{
xfree (answer);
if (adskfpr)
answer = xstrdup (adskfpr);
else
{
answer = cpr_get_utf8
("keyedit.addadsk",
_("Enter the fingerprint of the additional decryption subkey: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
if (classify_user_id (answer, &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_info (_("\"%s\" is not a fingerprint\n"), answer);
err = gpg_error (GPG_ERR_INV_USER_ID);
if (adskfpr)
goto leave;
continue;
}
/* Force searching for that exact fingerprint and for any key
* which has a key with that fingerprint. */
if (!strchr (answer, '!'))
{
char *tmpstr = xstrconcat (answer, "!", NULL);
xfree (answer);
answer = tmpstr;
}
free_public_key (adsk_pk);
adsk_pk = xcalloc (1, sizeof *adsk_pk);
adsk_pk->req_usage = PUBKEY_USAGE_ENC;
release_kbnode (adsk_keyblock);
adsk_keyblock = NULL;
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, adsk_pk, answer, &adsk_keyblock, NULL, 1);
if (err)
{
log_info (_("key \"%s\" not found: %s\n"), answer,
gpg_strerror (err));
if ((!opt.batch || adskfpr) && !opt.quiet
&& gpg_err_code (err) == GPG_ERR_UNUSABLE_PUBKEY)
log_info (_("Did you specify the fingerprint of a subkey?\n"));
if (adskfpr)
goto leave;
continue;
}
for (node = adsk_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fprlen);
if (fprlen == desc.fprlen
&& !memcmp (fpr, desc.u.fpr, fprlen)
&& (pk->pubkey_usage & PUBKEY_USAGE_ENC))
break;
}
}
if (!node)
{
err = gpg_error (GPG_ERR_WRONG_KEY_USAGE);
log_info (_("key \"%s\" not found: %s\n"), answer,
gpg_strerror (err));
if ((!opt.batch || adskfpr) && !opt.quiet)
log_info (_("Did you specify the fingerprint of a subkey?\n"));
if (adskfpr)
goto leave;
continue;
}
/* Check that the selected subkey is not yet on our keyblock. */
for (node2 = pub_keyblock; node2; node2 = node2->next)
{
if (node2->pkt->pkttype == PKT_PUBLIC_KEY
|| node2->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
pk = node2->pkt->pkt.public_key;
fingerprint_from_pk (pk, fpr, &fprlen);
if (fprlen == desc.fprlen
&& !memcmp (fpr, desc.u.fpr, fprlen))
break;
}
}
if (node2)
{
log_info (_("key \"%s\" is already on this keyblock\n"), answer);
err = gpg_error (GPG_ERR_DUP_KEY);
if (adskfpr)
goto leave;
continue;
}
break;
}
/* Append the subkey.
* Note that we don't use the ADSK_PK directly because this is the
* primary key and in general we use a subkey to which NODE points.
* ADSK_PK has only been used to pass the requested key usage to
* get_pubkey_byname. SUB_PK will point to the actual adsk. */
log_assert (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY);
sub_pk = copy_public_key_basics (NULL, node->pkt->pkt.public_key);
keyid_from_pk (main_pk, sub_pk->main_keyid); /* Fixup main keyid. */
log_assert ((sub_pk->pubkey_usage & PUBKEY_USAGE_ENC));
sub_pk->pubkey_usage = PUBKEY_USAGE_RENC; /* 'e' -> 'r' */
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_PUBLIC_SUBKEY; /* Make sure it is a subkey. */
pkt->pkt.public_key = sub_pk;
subkeynode = new_kbnode (pkt);
/* Make the signature. */
err = make_keysig_packet (ctrl, &sig, main_pk, NULL, sub_pk, main_pk, 0x18,
sub_pk->timestamp, 0,
keygen_add_key_flags_and_expire, sub_pk, NULL);
if (err)
{
write_status_error ("keysig", err);
log_error ("creating key binding failed: %s\n", gpg_strerror (err));
goto leave;
}
/* Append the subkey packet and the binding signature. */
add_kbnode (pub_keyblock, subkeynode);
subkeynode = NULL;
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (pub_keyblock, new_kbnode (pkt));
leave:
xfree (answer);
free_public_key (adsk_pk);
release_kbnode (adsk_keyblock);
release_kbnode (subkeynode);
if (!err)
return 1; /* The keyblock was modified. */
else
return 0; /* Not modified. */
}
/* With FORCE_MAINKEY cleared this function handles the interactive
* menu option "expire". With UNATTENDED set to 1 this function only
* sets the expiration date of the primary key to NEWEXPIRATION and
* avoid all interactivity; with a value of 2 only the flagged subkeys
* are set to NEWEXPIRATION. Returns 0 if nothing was done,
* GPG_ERR_TRUE if the key was modified, or any other error code. */
static gpg_error_t
menu_expire (ctrl_t ctrl, kbnode_t pub_keyblock,
int unattended, u32 newexpiration)
{
int signumber, rc;
u32 expiredate;
int only_mainkey; /* Set if only the mainkey is to be updated. */
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
(void)signumber;
if (unattended)
{
only_mainkey = (unattended == 1);
expiredate = newexpiration;
}
else
{
int n1;
only_mainkey = 0;
n1 = count_selected_keys (pub_keyblock);
if (n1 > 1)
{
if (!cpr_get_answer_is_yes
("keyedit.expire_multiple_subkeys.okay",
_("Are you sure you want to change the"
" expiration time for multiple subkeys? (y/N) ")))
return gpg_error (GPG_ERR_CANCELED);;
}
else if (n1)
tty_printf (_("Changing expiration time for a subkey.\n"));
else
{
tty_printf (_("Changing expiration time for the primary key.\n"));
only_mainkey = 1;
no_primary_warning (pub_keyblock);
}
expiredate = ask_expiredate ();
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
signumber = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
main_pk->expiredate = expiredate;
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if ((node->flag & NODFLG_SELKEY) && unattended != 1)
{
/* The flag is set and we do not want to set the
* expiration date only for the main key. */
sub_pk = node->pkt->pkt.public_key;
sub_pk->expiredate = expiredate;
}
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (only_mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((only_mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!only_mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is a self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
signumber++;
if ((only_mainkey && main_pk->version < 4)
|| (!only_mainkey && sub_pk->version < 4))
{
log_info
(_("You can't change the expiration date of a v3 key\n"));
return gpg_error (GPG_ERR_LEGACY_KEY);
}
if (only_mainkey)
rc = update_keysig_packet (ctrl,
&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_expire,
main_pk);
else
rc =
update_keysig_packet (ctrl,
&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_expire, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_TRUE)
rc = GPG_ERR_GENERAL;
return rc;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
}
}
}
update_trust = 1;
return gpg_error (GPG_ERR_TRUE);
}
/* Change the capability of a selected key. This command should only
* be used to rectify badly created keys and as such is not suggested
* for general use. */
static int
menu_changeusage (ctrl_t ctrl, kbnode_t keyblock)
{
int n1, rc;
int mainkey = 0;
PKT_public_key *main_pk, *sub_pk;
PKT_user_id *uid;
kbnode_t node;
u32 keyid[2];
n1 = count_selected_keys (keyblock);
if (n1 > 1)
{
tty_printf (_("You must select exactly one key.\n"));
return 0;
}
else if (n1)
tty_printf (_("Changing usage of a subkey.\n"));
else
{
tty_printf (_("Changing usage of the primary key.\n"));
mainkey = 1;
}
/* Now we can actually change the self-signature(s) */
main_pk = sub_pk = NULL;
uid = NULL;
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->flag & NODFLG_SELKEY)
sub_pk = node->pkt->pkt.public_key;
else
sub_pk = NULL;
}
else if (node->pkt->pkttype == PKT_USER_ID)
uid = node->pkt->pkt.user_id;
else if (main_pk && node->pkt->pkttype == PKT_SIGNATURE
&& (mainkey || sub_pk))
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& ((mainkey && uid
&& uid->created && (sig->sig_class & ~3) == 0x10)
|| (!mainkey && sig->sig_class == 0x18))
&& sig->flags.chosen_selfsig)
{
/* This is the self-signature which is to be replaced. */
PKT_signature *newsig;
PACKET *newpkt;
if ((mainkey && main_pk->version < 4)
|| (!mainkey && sub_pk->version < 4))
{
/* Note: This won't happen because we don't support
* v3 keys anymore. */
log_info ("You can't change the capabilities of a v3 key\n");
return 0;
}
if (mainkey)
main_pk->pubkey_usage = ask_key_flags (main_pk->pubkey_algo, 0,
main_pk->pubkey_usage);
else
sub_pk->pubkey_usage = ask_key_flags (sub_pk->pubkey_algo, 1,
sub_pk->pubkey_usage);
if (mainkey)
rc = update_keysig_packet (ctrl,
&newsig, sig, main_pk, uid, NULL,
main_pk, keygen_add_key_flags,
main_pk);
else
rc =
update_keysig_packet (ctrl,
&newsig, sig, main_pk, NULL, sub_pk,
main_pk, keygen_add_key_flags, sub_pk);
if (rc)
{
log_error ("make_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* Replace the packet. */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
sub_pk = NULL;
break;
}
}
}
return 1;
}
static int
menu_backsign (ctrl_t ctrl, kbnode_t pub_keyblock)
{
int rc, modified = 0;
PKT_public_key *main_pk;
KBNODE node;
u32 timestamp;
log_assert (pub_keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
merge_keys_and_selfsig (ctrl, pub_keyblock);
main_pk = pub_keyblock->pkt->pkt.public_key;
keyid_from_pk (main_pk, NULL);
/* We use the same timestamp for all backsigs so that we don't
reveal information about the used machine. */
timestamp = make_timestamp ();
for (node = pub_keyblock; node; node = node->next)
{
PKT_public_key *sub_pk = NULL;
KBNODE node2, sig_pk = NULL /*,sig_sk = NULL*/;
/* char *passphrase; */
/* Find a signing subkey with no backsig */
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
if (node->pkt->pkt.public_key->pubkey_usage & PUBKEY_USAGE_SIG)
{
if (node->pkt->pkt.public_key->flags.backsig)
tty_printf (_
("signing subkey %s is already cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
else
sub_pk = node->pkt->pkt.public_key;
}
else
tty_printf (_("subkey %s does not sign and so does"
" not need to be cross-certified\n"),
keystr_from_pk (node->pkt->pkt.public_key));
}
if (!sub_pk)
continue;
/* Find the selected selfsig on this subkey */
for (node2 = node->next;
node2 && node2->pkt->pkttype == PKT_SIGNATURE; node2 = node2->next)
if (node2->pkt->pkt.signature->version >= 4
&& node2->pkt->pkt.signature->flags.chosen_selfsig)
{
sig_pk = node2;
break;
}
if (!sig_pk)
continue;
/* Find the secret subkey that matches the public subkey */
log_debug ("FIXME: Check whether a secret subkey is available.\n");
/* if (!sub_sk) */
/* { */
/* tty_printf (_("no secret subkey for public subkey %s - ignoring\n"), */
/* keystr_from_pk (sub_pk)); */
/* continue; */
/* } */
/* Now we can get to work. */
rc = make_backsig (ctrl,
sig_pk->pkt->pkt.signature, main_pk, sub_pk, sub_pk,
timestamp, NULL);
if (!rc)
{
PKT_signature *newsig;
PACKET *newpkt;
rc = update_keysig_packet (ctrl,
&newsig, sig_pk->pkt->pkt.signature,
main_pk, NULL, sub_pk, main_pk,
NULL, NULL);
if (!rc)
{
/* Put the new sig into place on the pubkey */
newpkt = xmalloc_clear (sizeof (*newpkt));
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (sig_pk->pkt, NULL);
xfree (sig_pk->pkt);
sig_pk->pkt = newpkt;
modified = 1;
}
else
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
break;
}
}
else
{
log_error ("make_backsig failed: %s\n", gpg_strerror (rc));
break;
}
}
return modified;
}
static int
change_primary_uid_cb (PKT_signature * sig, void *opaque)
{
byte buf[1];
/* first clear all primary uid flags so that we are sure none are
* lingering around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PRIMARY_UID);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PRIMARY_UID);
/* if opaque is set,we want to set the primary id */
if (opaque)
{
buf[0] = 1;
build_sig_subpkt (sig, SIGSUBPKT_PRIMARY_UID, buf, 1);
}
return 0;
}
/*
* Set the primary uid flag for the selected UID. We will also reset
* all other primary uid flags. For this to work we have to update
* all the signature timestamps. If we would do this with the current
* time, we lose quite a lot of information, so we use a kludge to
* do this: Just increment the timestamp by one second which is
* sufficient to updated a signature during import.
*/
static int
menu_set_primary_uid (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected;
int attribute = 0;
int modified = 0;
if (count_selected_uids (pub_keyblock) != 1)
{
tty_printf (_("Please select exactly one user ID.\n"));
return 0;
}
main_pk = NULL;
uid = NULL;
selected = 0;
/* Is our selected uid an attribute packet? */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && node->flag & NODFLG_SELUID)
attribute = (node->pkt->pkt.user_id->attrib_data != NULL);
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = node->flag & NODFLG_SELUID;
}
else if (main_pk && uid && node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& attribute == (uid->attrib_data != NULL)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced.
We can just ignore v3 signatures because they are
not able to carry the primary ID flag. We also
ignore self-sigs on user IDs that are not of the
same type that we are making primary. That is, if
we are making a user ID primary, we alter user IDs.
If we are making an attribute packet primary, we
alter attribute packets. */
/* FIXME: We must make sure that we only have one
self-signature per user ID here (not counting
revocations) */
PKT_signature *newsig;
PACKET *newpkt;
const byte *p;
int action;
/* See whether this signature has the primary UID flag. */
p = parse_sig_subpkt (sig, 1,
SIGSUBPKT_PRIMARY_UID, NULL);
if (!p)
p = parse_sig_subpkt (sig, 0,
SIGSUBPKT_PRIMARY_UID, NULL);
if (p && *p) /* yes */
action = selected ? 0 : -1;
else /* no */
action = selected ? 1 : 0;
if (action)
{
int rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
change_primary_uid_cb,
action > 0 ? "x" : NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
}
return modified;
}
/*
* Set preferences to new values for the selected user IDs.
* --quick-update-pred calls this with UNATTENDED set.
*/
static int
menu_set_preferences (ctrl_t ctrl, kbnode_t pub_keyblock, int unattended)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
if (!unattended)
no_primary_warning (pub_keyblock);
select_all = unattended? 1 : !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* No more user-ids expected - ready. */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
if (sig->version < 4)
{
char *user =
utf8_to_native (uid->name, strlen (uid->name), 0);
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
xfree (user);
}
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the preferences. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL, main_pk,
keygen_upd_std_prefs, NULL);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
}
}
}
return modified;
}
static int
menu_set_keyserver_url (ctrl_t ctrl, const char *url, kbnode_t pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer, *uri;
no_primary_warning (pub_keyblock);
if (url)
answer = xstrdup (url);
else
{
answer = cpr_get_utf8 ("keyedit.add_keyserver",
_("Enter your preferred keyserver URL: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (!ascii_strcasecmp (answer, "none"))
{
xfree (answer);
uri = NULL;
}
else
{
struct keyserver_spec *keyserver = NULL;
/* Sanity check the format */
keyserver = parse_keyserver_uri (answer, 1);
xfree (answer);
if (!keyserver)
{
log_info (_("could not parse keyserver URL\n"));
return 0;
}
uri = xstrdup (keyserver->uri);
free_keyserver_spec (keyserver);
}
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
/* This is a selfsignature which is to be replaced
* We have to ignore v3 signatures because they are
* not able to carry the subpacket. */
PKT_signature *newsig;
PACKET *newpkt;
int rc;
const byte *p;
size_t plen;
p = parse_sig_subpkt (sig, 1, SIGSUBPKT_PREF_KS, &plen);
if (p && plen)
{
tty_printf ("Current preferred keyserver for user"
" ID \"%s\": ", user);
tty_print_utf8_string (p, plen);
tty_printf ("\n");
if (!cpr_get_answer_is_yes
("keyedit.confirm_keyserver",
uri
? _("Are you sure you want to replace it? (y/N) ")
: _("Are you sure you want to delete it? (y/N) ")))
{
xfree (user);
continue;
}
}
else if (uri == NULL)
{
/* There is no current keyserver URL, so there
is no point in trying to un-set it. */
xfree (user);
continue;
}
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_keyserver_url, uri);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
xfree (uri);
xfree (user);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
}
xfree (user);
}
}
}
xfree (uri);
return modified;
}
static int
menu_set_notation (ctrl_t ctrl, const char *string, KBNODE pub_keyblock)
{
PKT_public_key *main_pk;
PKT_user_id *uid;
KBNODE node;
u32 keyid[2];
int selected, select_all;
int modified = 0;
char *answer;
struct notation *notation;
no_primary_warning (pub_keyblock);
if (string)
answer = xstrdup (string);
else
{
answer = cpr_get_utf8 ("keyedit.add_notation",
_("Enter the notation: "));
if (answer[0] == '\0' || answer[0] == CONTROL_D)
{
xfree (answer);
return 0;
}
}
if (!ascii_strcasecmp (answer, "none")
|| !ascii_strcasecmp (answer, "-"))
notation = NULL; /* Delete them all. */
else
{
notation = string_to_notation (answer, 0);
if (!notation)
{
xfree (answer);
return 0;
}
}
xfree (answer);
select_all = !count_selected_uids (pub_keyblock);
/* Now we can actually change the self signature(s) */
main_pk = NULL;
uid = NULL;
selected = 0;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
break; /* ready */
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
{
main_pk = node->pkt->pkt.public_key;
keyid_from_pk (main_pk, keyid);
}
else if (node->pkt->pkttype == PKT_USER_ID)
{
uid = node->pkt->pkt.user_id;
selected = select_all || (node->flag & NODFLG_SELUID);
}
else if (main_pk && uid && selected
&& node->pkt->pkttype == PKT_SIGNATURE)
{
PKT_signature *sig = node->pkt->pkt.signature;
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1]
&& (uid && (sig->sig_class & ~3) == 0x10)
&& sig->flags.chosen_selfsig)
{
char *user = utf8_to_native (uid->name, strlen (uid->name), 0);
if (sig->version < 4)
log_info (_("skipping v3 self-signature on user ID \"%s\"\n"),
user);
else
{
PKT_signature *newsig;
PACKET *newpkt;
int rc, skip = 0, addonly = 1;
if (sig->flags.notation)
{
tty_printf ("Current notations for user ID \"%s\":\n",
user);
tty_print_notations (-9, sig);
}
else
{
tty_printf ("No notations on user ID \"%s\"\n", user);
if (notation == NULL)
{
/* There are no current notations, so there
is no point in trying to un-set them. */
continue;
}
}
if (notation)
{
struct notation *n;
int deleting = 0;
notation->next = sig_to_notation (sig);
for (n = notation->next; n; n = n->next)
if (strcmp (n->name, notation->name) == 0)
{
if (notation->value)
{
if (strcmp (n->value, notation->value) == 0)
{
if (notation->flags.ignore)
{
/* Value match with a delete
flag. */
n->flags.ignore = 1;
deleting = 1;
}
else
{
/* Adding the same notation
twice, so don't add it at
all. */
skip = 1;
tty_printf ("Skipping notation:"
" %s=%s\n",
notation->name,
notation->value);
break;
}
}
}
else
{
/* No value, so it means delete. */
n->flags.ignore = 1;
deleting = 1;
}
if (n->flags.ignore)
{
tty_printf ("Removing notation: %s=%s\n",
n->name, n->value);
addonly = 0;
}
}
if (!notation->flags.ignore && !skip)
tty_printf ("Adding notation: %s=%s\n",
notation->name, notation->value);
/* We tried to delete, but had no matches. */
if (notation->flags.ignore && !deleting)
continue;
}
else
{
tty_printf ("Removing all notations\n");
addonly = 0;
}
if (skip
|| (!addonly
&&
!cpr_get_answer_is_yes ("keyedit.confirm_notation",
_("Proceed? (y/N) "))))
continue;
rc = update_keysig_packet (ctrl, &newsig, sig,
main_pk, uid, NULL,
main_pk,
keygen_add_notations, notation);
if (rc)
{
log_error ("update_keysig_packet failed: %s\n",
gpg_strerror (rc));
free_notation (notation);
xfree (user);
return 0;
}
/* replace the packet */
newpkt = xmalloc_clear (sizeof *newpkt);
newpkt->pkttype = PKT_SIGNATURE;
newpkt->pkt.signature = newsig;
free_packet (node->pkt, NULL);
xfree (node->pkt);
node->pkt = newpkt;
modified = 1;
if (notation)
{
/* Snip off the notation list from the sig */
free_notation (notation->next);
notation->next = NULL;
}
xfree (user);
}
}
}
}
free_notation (notation);
return modified;
}
/*
* Select one user id or remove all selection if IDX is 0 or select
* all if IDX is -1. Returns: True if the selection changed.
*/
static int
menu_select_uid (KBNODE keyblock, int idx)
{
KBNODE node;
int i;
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag |= NODFLG_SELUID;
return 1;
}
else if (idx) /* Toggle. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No user ID with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
if (++i == idx)
{
if ((node->flag & NODFLG_SELUID))
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
}
}
}
}
else /* Unselect all */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
node->flag &= ~NODFLG_SELUID;
}
return 1;
}
/* Search in the keyblock for a uid that matches namehash */
static int
menu_select_uid_namehash (KBNODE keyblock, const char *namehash)
{
byte hash[NAMEHASH_LEN];
KBNODE node;
int i;
log_assert (strlen (namehash) == NAMEHASH_LEN * 2);
for (i = 0; i < NAMEHASH_LEN; i++)
hash[i] = hextobyte (&namehash[i * 2]);
for (node = keyblock->next; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
namehash_from_uid (node->pkt->pkt.user_id);
if (memcmp (node->pkt->pkt.user_id->namehash, hash, NAMEHASH_LEN) ==
0)
{
if (node->flag & NODFLG_SELUID)
node->flag &= ~NODFLG_SELUID;
else
node->flag |= NODFLG_SELUID;
break;
}
}
}
if (!node)
{
tty_printf (_("No user ID with hash %s\n"), namehash);
return 0;
}
return 1;
}
/*
* Select secondary keys
* Returns: True if the selection changed.
*/
static int
menu_select_key (KBNODE keyblock, int idx, char *p)
{
KBNODE node;
int i, j;
int is_hex_digits;
is_hex_digits = p && strlen (p) >= 8;
if (is_hex_digits)
{
/* Skip initial spaces. */
while (spacep (p))
p ++;
/* If the id starts with 0x accept and ignore it. */
if (p[0] == '0' && p[1] == 'x')
p += 2;
for (i = 0, j = 0; p[i]; i ++)
if (hexdigitp (&p[i]))
{
p[j] = toupper (p[i]);
j ++;
}
else if (spacep (&p[i]))
/* Skip spaces. */
{
}
else
{
is_hex_digits = 0;
break;
}
if (is_hex_digits)
/* In case we skipped some spaces, add a new NUL terminator. */
{
p[j] = 0;
/* If we skipped some spaces, make sure that we still have
at least 8 characters. */
is_hex_digits = (/* Short keyid. */
strlen (p) == 8
/* Long keyid. */
|| strlen (p) == 16
/* Fingerprints are (currently) 32 or 40
characters. */
|| strlen (p) >= 32);
}
}
if (is_hex_digits)
{
int found_one = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
{
int match = 0;
if (strlen (p) == 8 || strlen (p) == 16)
{
u32 kid[2];
char kid_str[17];
keyid_from_pk (node->pkt->pkt.public_key, kid);
format_keyid (kid, strlen (p) == 8 ? KF_SHORT : KF_LONG,
kid_str, sizeof (kid_str));
if (strcmp (p, kid_str) == 0)
match = 1;
}
else
{
char fp[2*MAX_FINGERPRINT_LEN + 1];
hexfingerprint (node->pkt->pkt.public_key, fp, sizeof (fp));
if (strcmp (fp, p) == 0)
match = 1;
}
if (match)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
found_one = 1;
}
}
if (found_one)
return 1;
tty_printf (_("No subkey with key ID '%s'.\n"), p);
return 0;
}
if (idx == -1) /* Select all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag |= NODFLG_SELKEY;
}
else if (idx) /* Toggle selection. */
{
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
break;
}
if (!node)
{
tty_printf (_("No subkey with index %d\n"), idx);
return 0;
}
for (i = 0, node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
if (++i == idx)
{
if ((node->flag & NODFLG_SELKEY))
node->flag &= ~NODFLG_SELKEY;
else
node->flag |= NODFLG_SELKEY;
}
}
}
else /* Unselect all. */
{
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY)
node->flag &= ~NODFLG_SELKEY;
}
return 1;
}
static int
count_uids_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & flag))
i++;
return i;
}
static int
count_keys_with_flag (KBNODE keyblock, unsigned flag)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if ((node->pkt->pkttype == PKT_PUBLIC_SUBKEY
|| node->pkt->pkttype == PKT_SECRET_SUBKEY) && (node->flag & flag))
i++;
return i;
}
static int
count_uids (KBNODE keyblock)
{
KBNODE node;
int i = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID)
i++;
return i;
}
/*
* Returns true if there is at least one selected user id
*/
static int
count_selected_uids (KBNODE keyblock)
{
return count_uids_with_flag (keyblock, NODFLG_SELUID);
}
static int
count_selected_keys (KBNODE keyblock)
{
return count_keys_with_flag (keyblock, NODFLG_SELKEY);
}
/* Returns how many real (i.e. not attribute) uids are unmarked. */
static int
real_uids_left (KBNODE keyblock)
{
KBNODE node;
int real = 0;
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & NODFLG_SELUID) &&
!node->pkt->pkt.user_id->attrib_data)
real++;
return real;
}
/*
* Ask whether the signature should be revoked. If the user commits this,
* flag bit MARK_A is set on the signature and the user ID.
*/
static void
ask_revoke_sig (ctrl_t ctrl, kbnode_t keyblock, kbnode_t node)
{
int doit = 0;
PKT_user_id *uid;
PKT_signature *sig = node->pkt->pkt.signature;
KBNODE unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
if (!unode)
{
log_error ("Oops: no user ID for signature\n");
return;
}
uid = unode->pkt->pkt.user_id;
if (opt.with_colons)
{
if (uid->attrib_data)
printf ("uat:::::::::%u %lu", uid->numattribs, uid->attrib_len);
else
{
es_printf ("uid:::::::::");
es_write_sanitized (es_stdout, uid->name, uid->len, ":", NULL);
}
es_printf ("\n");
print_and_check_one_sig_colon (ctrl, keyblock, node,
NULL, NULL, NULL, NULL, 1);
}
else
{
char *p = utf8_to_native (unode->pkt->pkt.user_id->name,
unode->pkt->pkt.user_id->len, 0);
tty_printf (_("user ID: \"%s\"\n"), p);
xfree (p);
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"), "");
}
if (sig->flags.expired)
{
tty_printf (_("This signature expired on %s.\n"),
expirestr_from_sig (sig));
/* Use a different question so we can have different help text */
doit = cpr_get_answer_is_yes
("ask_revoke_sig.expired",
_("Are you sure you still want to revoke it? (y/N) "));
}
else
doit = cpr_get_answer_is_yes
("ask_revoke_sig.one",
_("Create a revocation certificate for this signature? (y/N) "));
if (doit)
{
node->flag |= NODFLG_MARK_A;
unode->flag |= NODFLG_MARK_A;
}
}
/*
* Display all user ids of the current public key together with signatures
* done by one of our keys. Then walk over all this sigs and ask the user
* whether he wants to revoke this signature.
* Return: True when the keyblock has changed.
*/
static int
menu_revsig (ctrl_t ctrl, kbnode_t keyblock)
{
PKT_signature *sig;
PKT_public_key *primary_pk;
KBNODE node;
int changed = 0;
int rc, any, skip = 1, all = !count_selected_uids (keyblock);
struct revocation_reason_info *reason = NULL;
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
/* First check whether we have any signatures at all. */
any = 0;
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
skip = 0;
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (ctrl, sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
any = 1;
break;
}
}
}
if (!any)
{
tty_printf (_("Not signed by you.\n"));
return 0;
}
/* FIXME: detect duplicates here */
tty_printf (_("You have signed these user IDs on key %s:\n"),
keystr_from_pk (keyblock->pkt->pkt.public_key));
for (node = keyblock; node; node = node->next)
{
node->flag &= ~(NODFLG_SELSIG | NODFLG_MARK_A);
if (node->pkt->pkttype == PKT_USER_ID)
{
if (node->flag & NODFLG_SELUID || all)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
/* Hmmm: Should we show only UIDs with a signature? */
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
skip = 0;
}
else
skip = 1;
}
else if (!skip && node->pkt->pkttype == PKT_SIGNATURE
&& ((sig = node->pkt->pkt.signature),
have_secret_key_with_kid (ctrl, sig->keyid)))
{
if ((sig->sig_class & ~3) == 0x10)
{
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig),
sig->flags.exportable ? "" : _(" (non-exportable)"),
sig->flags.revocable ? "" : _(" (non-revocable)"));
if (sig->flags.revocable)
node->flag |= NODFLG_SELSIG;
}
else if (sig->sig_class == 0x30)
{
tty_printf (" ");
tty_printf (_("revoked by your key %s on %s\n"),
keystr (sig->keyid), datestr_from_sig (sig));
}
}
}
tty_printf ("\n");
/* ask */
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_SELSIG))
continue;
ask_revoke_sig (ctrl, keyblock, node);
}
/* present selected */
any = 0;
for (node = keyblock; node; node = node->next)
{
if (!(node->flag & NODFLG_MARK_A))
continue;
if (!any)
{
any = 1;
tty_printf (_("You are about to revoke these signatures:\n"));
}
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
tty_printf (" ");
tty_print_utf8_string (uid->name, uid->len);
tty_printf ("\n");
}
else if (node->pkt->pkttype == PKT_SIGNATURE)
{
sig = node->pkt->pkt.signature;
tty_printf (" ");
tty_printf (_("signed by your key %s on %s%s%s\n"),
keystr (sig->keyid), datestr_from_sig (sig), "",
sig->flags.exportable ? "" : _(" (non-exportable)"));
}
}
if (!any)
return 0; /* none selected */
if (!cpr_get_answer_is_yes
("ask_revoke_sig.okay",
_("Really create the revocation certificates? (y/N) ")))
return 0; /* forget it */
reason = ask_revocation_reason (0, 1, 0);
if (!reason)
{ /* user decided to cancel */
return 0;
}
/* now we can sign the user ids */
reloop: /* (must use this, because we are modifying the list) */
primary_pk = keyblock->pkt->pkt.public_key;
for (node = keyblock; node; node = node->next)
{
KBNODE unode;
PACKET *pkt;
struct sign_attrib attrib;
PKT_public_key *signerkey;
if (!(node->flag & NODFLG_MARK_A)
|| node->pkt->pkttype != PKT_SIGNATURE)
continue;
unode = find_prev_kbnode (keyblock, node, PKT_USER_ID);
log_assert (unode); /* we already checked this */
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
attrib.non_exportable = !node->pkt->pkt.signature->flags.exportable;
node->flag &= ~NODFLG_MARK_A;
signerkey = xmalloc_secure_clear (sizeof *signerkey);
if (get_seckey (ctrl, signerkey, node->pkt->pkt.signature->keyid))
{
log_info (_("no secret key\n"));
free_public_key (signerkey);
continue;
}
rc = make_keysig_packet (ctrl, &sig, primary_pk,
unode->pkt->pkt.user_id,
NULL, signerkey, 0x30, 0, 0,
sign_mk_attrib, &attrib, NULL);
free_public_key (signerkey);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
update_trust = 1;
/* Are we revoking our own uid? */
if (primary_pk->keyid[0] == sig->keyid[0] &&
primary_pk->keyid[1] == sig->keyid[1])
unode->pkt->pkt.user_id->flags.revoked = 1;
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (unode, new_kbnode (pkt), 0);
goto reloop;
}
release_revocation_reason_info (reason);
return changed;
}
/* return 0 if revocation of NODE (which must be a User ID) was
successful, non-zero if there was an error. *modified will be set
to 1 if a change was made. */
static int
core_revuid (ctrl_t ctrl, kbnode_t keyblock, KBNODE node,
const struct revocation_reason_info *reason, int *modified)
{
PKT_public_key *pk = keyblock->pkt->pkt.public_key;
gpg_error_t rc;
if (node->pkt->pkttype != PKT_USER_ID)
{
rc = gpg_error (GPG_ERR_NO_USER_ID);
write_status_error ("keysig", rc);
log_error (_("tried to revoke a non-user ID: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (uid->flags.revoked)
{
char *user = utf8_to_native (uid->name, uid->len, 0);
log_info (_("user ID \"%s\" is already revoked\n"), user);
xfree (user);
}
else
{
PACKET *pkt;
PKT_signature *sig;
struct sign_attrib attrib;
u32 timestamp = make_timestamp ();
if (uid->created >= timestamp)
{
/* Okay, this is a problem. The user ID selfsig was
created in the future, so we need to warn the user and
set our revocation timestamp one second after that so
everything comes out clean. */
log_info (_("WARNING: a user ID signature is dated %d"
" seconds in the future\n"),
uid->created - timestamp);
timestamp = uid->created + 1;
}
memset (&attrib, 0, sizeof attrib);
/* should not need to cast away const here; but
revocation_reason_build_cb needs to take a non-const
void* in order to meet the function signature for the
mksubpkt argument to make_keysig_packet */
attrib.reason = (struct revocation_reason_info *)reason;
rc = make_keysig_packet (ctrl, &sig, pk, uid, NULL, pk, 0x30,
timestamp, 0,
sign_mk_attrib, &attrib, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
return 1;
}
else
{
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
#ifndef NO_TRUST_MODELS
/* If the trustdb has an entry for this key+uid then the
trustdb needs an update. */
if (!update_trust
&& ((get_validity (ctrl, keyblock, pk, uid, NULL, 0)
& TRUST_MASK)
>= TRUST_UNDEFINED))
update_trust = 1;
#endif /*!NO_TRUST_MODELS*/
node->pkt->pkt.user_id->flags.revoked = 1;
if (modified)
*modified = 1;
}
}
return 0;
}
}
/* Revoke a user ID (i.e. revoke a user ID selfsig). Return true if
keyblock changed. */
static int
menu_revuid (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
size_t valid_uids;
/* Note that this is correct as per the RFCs, but nevertheless
somewhat meaningless in the real world. 1991 did define the 0x30
sig class, but PGP 2.x did not actually implement it, so it would
probably be safe to use v4 revocations everywhere. -ds */
for (node = pub_keyblock; node; node = node->next)
if (pk->version > 3 || (node->pkt->pkttype == PKT_USER_ID &&
node->pkt->pkt.user_id->selfsigversion > 3))
{
if ((reason = ask_revocation_reason (0, 1, 4)))
break;
else
goto leave;
}
/* Too make sure that we do not revoke the last valid UID, we first
count how many valid UIDs there are. */
valid_uids = 0;
for (node = pub_keyblock; node; node = node->next)
valid_uids +=
node->pkt->pkttype == PKT_USER_ID
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired;
reloop: /* (better this way because we are modifying the keyring) */
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & NODFLG_SELUID))
{
int modified = 0;
/* Make sure that we do not revoke the last valid UID. */
if (valid_uids == 1
&& ! node->pkt->pkt.user_id->flags.revoked
&& ! node->pkt->pkt.user_id->flags.expired)
{
log_error (_("Cannot revoke the last valid user ID.\n"));
goto leave;
}
rc = core_revuid (ctrl, pub_keyblock, node, reason, &modified);
if (rc)
goto leave;
if (modified)
{
node->flag &= ~NODFLG_SELUID;
changed = 1;
goto reloop;
}
}
if (changed)
commit_kbnode (&pub_keyblock);
leave:
release_revocation_reason_info (reason);
return changed;
}
/*
* Revoke the whole key.
*/
static int
menu_revkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *pk = pub_keyblock->pkt->pkt.public_key;
int rc, changed = 0;
struct revocation_reason_info *reason;
PACKET *pkt;
PKT_signature *sig;
if (pk->flags.revoked)
{
tty_printf (_("Key %s is already revoked.\n"), keystr_from_pk (pk));
return 0;
}
reason = ask_revocation_reason (1, 0, 0);
/* user decided to cancel */
if (!reason)
return 0;
rc = make_keysig_packet (ctrl, &sig, pk, NULL, NULL, pk,
0x20, 0, 0,
revocation_reason_build_cb, reason, NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
goto scram;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (pub_keyblock, new_kbnode (pkt), 0);
commit_kbnode (&pub_keyblock);
update_trust = 1;
scram:
release_revocation_reason_info (reason);
return changed;
}
static int
menu_revsubkey (ctrl_t ctrl, kbnode_t pub_keyblock)
{
PKT_public_key *mainpk;
KBNODE node;
int changed = 0;
int rc;
struct revocation_reason_info *reason = NULL;
reason = ask_revocation_reason (1, 0, 0);
if (!reason)
return 0; /* User decided to cancel. */
reloop: /* (better this way because we are modifying the keyring) */
mainpk = pub_keyblock->pkt->pkt.public_key;
for (node = pub_keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& (node->flag & NODFLG_SELKEY))
{
PACKET *pkt;
PKT_signature *sig;
PKT_public_key *subpk = node->pkt->pkt.public_key;
struct sign_attrib attrib;
if (subpk->flags.revoked)
{
tty_printf (_("Subkey %s is already revoked.\n"),
keystr_from_pk (subpk));
continue;
}
memset (&attrib, 0, sizeof attrib);
attrib.reason = reason;
node->flag &= ~NODFLG_SELKEY;
rc = make_keysig_packet (ctrl, &sig, mainpk, NULL, subpk, mainpk,
0x28, 0, 0, sign_mk_attrib, &attrib,
NULL);
if (rc)
{
write_status_error ("keysig", rc);
log_error (_("signing failed: %s\n"), gpg_strerror (rc));
release_revocation_reason_info (reason);
return changed;
}
changed = 1; /* we changed the keyblock */
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
insert_kbnode (node, new_kbnode (pkt), 0);
goto reloop;
}
}
commit_kbnode (&pub_keyblock);
/* No need to set update_trust here since signing keys no longer
are used to certify other keys, so there is no change in trust
when revoking/removing them */
release_revocation_reason_info (reason);
return changed;
}
/* Note that update_ownertrust is going to mark the trustdb dirty when
enabling or disabling a key. This is arguably sub-optimal as
disabled keys are still counted in the web of trust, but perhaps
not worth adding extra complexity to change. -ds */
#ifndef NO_TRUST_MODELS
static int
enable_disable_key (ctrl_t ctrl, kbnode_t keyblock, int disable)
{
PKT_public_key *pk =
find_kbnode (keyblock, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
unsigned int trust, newtrust;
trust = newtrust = get_ownertrust (ctrl, pk);
newtrust &= ~TRUST_FLAG_DISABLED;
if (disable)
newtrust |= TRUST_FLAG_DISABLED;
if (trust == newtrust)
return 0; /* already in that state */
update_ownertrust (ctrl, pk, newtrust);
return 0;
}
#endif /*!NO_TRUST_MODELS*/
static void
menu_showphoto (ctrl_t ctrl, kbnode_t keyblock)
{
KBNODE node;
int select_all = !count_selected_uids (keyblock);
int count = 0;
PKT_public_key *pk = NULL;
/* Look for the public key first. We have to be really, really,
explicit as to which photo this is, and what key it is a UID on
since people may want to sign it. */
for (node = keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_PUBLIC_KEY)
pk = node->pkt->pkt.public_key;
else if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
count++;
if ((select_all || (node->flag & NODFLG_SELUID)) &&
uid->attribs != NULL)
{
int i;
for (i = 0; i < uid->numattribs; i++)
{
byte type;
u32 size;
if (uid->attribs[i].type == ATTRIB_IMAGE &&
parse_image_header (&uid->attribs[i], &type, &size))
{
tty_printf (_("Displaying %s photo ID of size %ld for "
"key %s (uid %d)\n"),
image_type_to_string (type, 1),
(ulong) size, keystr_from_pk (pk), count);
show_photos (ctrl, &uid->attribs[i], 1, pk, uid);
}
}
}
}
}
}
diff --git a/g10/keygen.c b/g10/keygen.c
index 0c37f27f8..f6498e9fc 100644
--- a/g10/keygen.c
+++ b/g10/keygen.c
@@ -1,6882 +1,6882 @@
/* keygen.c - Generate a key pair
* Copyright (C) 1998-2007, 2009-2011 Free Software Foundation, Inc.
* Copyright (C) 2014, 2015, 2016, 2017, 2018 Werner Koch
* Copyright (C) 2020, 2024 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpg.h"
#include "../common/util.h"
#include "main.h"
#include "packet.h"
#include "../common/ttyio.h"
#include "options.h"
#include "keydb.h"
#include "trustdb.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "keyserver-internal.h"
#include "call-agent.h"
#include "pkglue.h"
#include "../common/shareddefs.h"
#include "../common/host2net.h"
#include "../common/mbox-util.h"
/* The default algorithms. If you change them, you should ensure the
value is inside the bounds enforced by ask_keysize and gen_xxx.
See also get_keysize_range which encodes the allowed ranges. The
default answer in ask_algo also needs to be adjusted. */
#define DEFAULT_STD_KEY_PARAM "ed25519/cert,sign+cv25519/encr"
#define FUTURE_STD_KEY_PARAM "ed25519/cert,sign+cv25519/encr"
/* When generating keys using the streamlined key generation dialog,
use this as a default expiration interval. */
const char *default_expiration_interval = "3y";
/* Flag bits used during key generation. */
#define KEYGEN_FLAG_NO_PROTECTION 1
#define KEYGEN_FLAG_TRANSIENT_KEY 2
#define KEYGEN_FLAG_CREATE_V5_KEY 4
/* Maximum number of supported algorithm preferences. */
#define MAX_PREFS 30
enum para_name {
pKEYTYPE,
pKEYLENGTH,
pKEYCURVE,
pKEYUSAGE,
pSUBKEYTYPE,
pSUBKEYLENGTH,
pSUBKEYCURVE,
pSUBKEYUSAGE,
pAUTHKEYTYPE,
pNAMEREAL,
pNAMEEMAIL,
pNAMECOMMENT,
pPREFERENCES,
pREVOKER,
pUSERID,
pCREATIONDATE,
pKEYCREATIONDATE, /* Same in seconds since epoch. */
pEXPIREDATE,
pKEYEXPIRE, /* in n seconds */
pSUBKEYCREATIONDATE,
pSUBKEYEXPIREDATE,
pSUBKEYEXPIRE, /* in n seconds */
pAUTHKEYCREATIONDATE, /* Not yet used. */
pPASSPHRASE,
pSERIALNO,
pCARDBACKUPKEY,
pHANDLE,
pKEYSERVER,
pKEYGRIP,
pSUBKEYGRIP,
pVERSION, /* Desired version of the key packet. */
pSUBVERSION, /* Ditto for the subpacket. */
pCARDKEY /* The keygrips have been taken from active card (bool). */
};
struct para_data_s {
struct para_data_s *next;
int lnr;
enum para_name key;
union {
u32 expire;
u32 creation;
int abool;
unsigned int usage;
struct revocation_key revkey;
char value[1];
} u;
};
struct output_control_s
{
int lnr;
int dryrun;
unsigned int keygen_flags;
int use_files;
struct {
char *fname;
char *newfname;
IOBUF stream;
armor_filter_context_t *afx;
} pub;
};
/* An object to help communicating with the actual key generation
* code. */
struct common_gen_cb_parm_s
{
/* This variable set to the result of agent_genkey. The callback
* may take a copy of this so that the result can be used after we
* are back from the deep key generation call stack. */
gcry_sexp_t genkey_result;
/* For a dual algorithms the result of the second algorithm
* (e.g. Kyber). */
gcry_sexp_t genkey_result2;
};
typedef struct common_gen_cb_parm_s *common_gen_cb_parm_t;
/* A communication object to help adding certain notations to a key
* binding signature. */
struct opaque_data_usage_and_pk
{
unsigned int usage;
const char *cpl_notation;
PKT_public_key *pk;
};
/* FIXME: These globals vars are ugly. And using MAX_PREFS even for
* aeads is useless, given that we don't expects more than a very few
* algorithms. */
static int prefs_initialized = 0;
static byte sym_prefs[MAX_PREFS];
static int nsym_prefs;
static byte hash_prefs[MAX_PREFS];
static int nhash_prefs;
static byte zip_prefs[MAX_PREFS];
static int nzip_prefs;
static byte aead_prefs[MAX_PREFS];
static int naead_prefs;
static int mdc_available;
static int ks_modify;
static int aead_available;
static gpg_error_t parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage,
u32 *r_expire, unsigned int *r_nbits,
const char **r_curve, int *r_version,
char **r_keygrip, u32 *r_keytime);
static void do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card );
static int write_keyblock (iobuf_t out, kbnode_t node);
static gpg_error_t gen_card_key (int keyno, int algo, int is_primary,
kbnode_t pub_root, u32 *timestamp,
u32 expireval, int *keygen_flags);
static unsigned int get_keysize_range (int algo,
unsigned int *min, unsigned int *max);
static void do_add_notation (PKT_signature *sig,
const char *name, const char *value,
int critical);
/* Return the algo string for a default new key. */
const char *
get_default_pubkey_algo (void)
{
if (opt.def_new_key_algo)
{
if (*opt.def_new_key_algo && !strchr (opt.def_new_key_algo, ':'))
return opt.def_new_key_algo;
/* To avoid checking that option every time we delay that until
* here. The only thing we really need to make sure is that
* there is no colon in the string so that the --gpgconf-list
* command won't mess up its output. */
log_info (_("invalid value for option '%s'\n"), "--default-new-key-algo");
}
return DEFAULT_STD_KEY_PARAM;
}
static void
print_status_key_created (int letter, PKT_public_key *pk, const char *handle)
{
byte array[MAX_FINGERPRINT_LEN], *s;
char *buf, *p;
size_t i, n;
if (!handle)
handle = "";
buf = xmalloc (MAX_FINGERPRINT_LEN*2+31 + strlen (handle) + 1);
p = buf;
if (letter || pk)
{
*p++ = letter;
if (pk)
{
*p++ = ' ';
fingerprint_from_pk (pk, array, &n);
s = array;
/* Fixme: Use bin2hex */
for (i=0; i < n ; i++, s++, p += 2)
snprintf (p, 3, "%02X", *s);
}
}
if (*handle)
{
*p++ = ' ';
for (i=0; handle[i] && i < 100; i++)
*p++ = isspace ((unsigned int)handle[i])? '_':handle[i];
}
*p = 0;
write_status_text ((letter || pk)?STATUS_KEY_CREATED:STATUS_KEY_NOT_CREATED,
buf);
xfree (buf);
}
static void
print_status_key_not_created (const char *handle)
{
print_status_key_created (0, NULL, handle);
}
static gpg_error_t
write_uid (kbnode_t root, const char *s)
{
PACKET *pkt = NULL;
size_t n = strlen (s);
if (n > MAX_UID_PACKET_LENGTH - 10)
return gpg_error (GPG_ERR_INV_USER_ID);
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_USER_ID;
pkt->pkt.user_id = xmalloc_clear (sizeof *pkt->pkt.user_id + n);
pkt->pkt.user_id->len = n;
pkt->pkt.user_id->ref = 1;
strcpy (pkt->pkt.user_id->name, s);
add_kbnode (root, new_kbnode (pkt));
return 0;
}
static void
do_add_key_flags (PKT_signature *sig, unsigned int use)
{
byte buf[2] = { 0, 0 };
/* The spec says that all primary keys MUST be able to certify. */
if ( sig->sig_class != 0x18 )
buf[0] |= 0x01;
if (use & PUBKEY_USAGE_SIG)
buf[0] |= 0x02;
if (use & PUBKEY_USAGE_ENC)
buf[0] |= 0x04 | 0x08;
if (use & PUBKEY_USAGE_AUTH)
buf[0] |= 0x20;
if (use & PUBKEY_USAGE_GROUP)
buf[0] |= 0x80;
if (use & PUBKEY_USAGE_RENC)
buf[1] |= 0x04;
if (use & PUBKEY_USAGE_TIME)
buf[1] |= 0x08;
build_sig_subpkt (sig, SIGSUBPKT_KEY_FLAGS, buf, buf[1]? 2:1);
}
int
keygen_add_key_expire (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
byte buf[8];
u32 u;
if (pk->expiredate)
{
if (pk->expiredate > pk->timestamp)
u = pk->expiredate - pk->timestamp;
else
u = 1;
buf[0] = (u >> 24) & 0xff;
buf[1] = (u >> 16) & 0xff;
buf[2] = (u >> 8) & 0xff;
buf[3] = u & 0xff;
build_sig_subpkt (sig, SIGSUBPKT_KEY_EXPIRE, buf, 4);
}
else
{
/* Make sure we don't leave a key expiration subpacket lying
around */
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KEY_EXPIRE);
}
return 0;
}
/* Add the key usage (i.e. key flags) in SIG from the public keys
* pubkey_usage field. OPAQUE has the public key. */
int
keygen_add_key_flags (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
return 0;
}
int
keygen_add_key_flags_and_expire (PKT_signature *sig, void *opaque)
{
keygen_add_key_flags (sig, opaque);
return keygen_add_key_expire (sig, opaque);
}
/* This is only used to write the key binding signature. It is not
* used for the primary key. */
static int
keygen_add_key_flags_from_oduap (PKT_signature *sig, void *opaque)
{
struct opaque_data_usage_and_pk *oduap = opaque;
do_add_key_flags (sig, oduap->usage);
if (oduap->cpl_notation)
do_add_notation (sig, "cpl@gnupg.org", oduap->cpl_notation, 0);
return keygen_add_key_expire (sig, oduap->pk);
}
static int
set_one_pref (int val, int type, const char *item, byte *buf, int *nbuf)
{
int i;
for (i=0; i < *nbuf; i++ )
if (buf[i] == val)
{
log_info (_("preference '%s' duplicated\n"), item);
return -1;
}
if (*nbuf >= MAX_PREFS)
{
if(type==1)
log_info(_("too many cipher preferences\n"));
else if(type==2)
log_info(_("too many digest preferences\n"));
else if(type==3)
log_info(_("too many compression preferences\n"));
else if(type==4)
log_info(_("too many AEAD preferences\n"));
else
BUG();
return -1;
}
buf[(*nbuf)++] = val;
return 0;
}
/*
* Parse the supplied string and use it to set the standard
* preferences. The string may be in a form like the one printed by
* "pref" (something like: "S10 S3 H3 H2 Z2 Z1") or the actual
* cipher/hash/compress names. Use NULL to set the default
* preferences. Returns: 0 = okay
*/
int
keygen_set_std_prefs (const char *string,int personal)
{
byte sym[MAX_PREFS], hash[MAX_PREFS], zip[MAX_PREFS], aead[MAX_PREFS];
int nsym=0, nhash=0, nzip=0, naead=0, val, rc=0;
int mdc=1, modify=0; /* mdc defaults on, modify defaults off. */
char dummy_string[25*4+1]; /* Enough for 25 items. */
if (!string || !ascii_strcasecmp (string, "default"))
{
if (opt.def_preference_list)
string=opt.def_preference_list;
else
{
int any_compress = 0;
dummy_string[0]='\0';
/* The rationale why we use the order AES256,192,128 is
for compatibility reasons with PGP. If gpg would
define AES128 first, we would get the somewhat
confusing situation:
gpg -r pgpkey -r gpgkey ---gives--> AES256
gpg -r gpgkey -r pgpkey ---gives--> AES
Note that by using --personal-cipher-preferences it is
possible to prefer AES128.
*/
/* Make sure we do not add more than 15 items here, as we
could overflow the size of dummy_string. We currently
have at most 12. */
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES256) )
strcat(dummy_string,"S9 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES192) )
strcat(dummy_string,"S8 ");
if ( !openpgp_cipher_test_algo (CIPHER_ALGO_AES) )
strcat(dummy_string,"S7 ");
strcat(dummy_string,"S2 "); /* 3DES */
if (!openpgp_aead_test_algo (AEAD_ALGO_OCB))
strcat(dummy_string,"A2 ");
if (personal)
{
/* The default internal hash algo order is:
* SHA-256, SHA-384, SHA-512, SHA-224, SHA-1.
*/
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256))
strcat (dummy_string, "H8 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384))
strcat (dummy_string, "H9 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512))
strcat (dummy_string, "H10 ");
}
else
{
/* The default advertised hash algo order is:
* SHA-512, SHA-384, SHA-256, SHA-224, SHA-1.
*/
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA512))
strcat (dummy_string, "H10 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA384))
strcat (dummy_string, "H9 ");
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA256))
strcat (dummy_string, "H8 ");
}
if (!openpgp_md_test_algo (DIGEST_ALGO_SHA224))
strcat (dummy_string, "H11 ");
strcat (dummy_string, "H2 "); /* SHA-1 */
if(!check_compress_algo(COMPRESS_ALGO_ZLIB))
{
strcat(dummy_string,"Z2 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_BZIP2))
{
strcat(dummy_string,"Z3 ");
any_compress = 1;
}
if(!check_compress_algo(COMPRESS_ALGO_ZIP))
{
strcat(dummy_string,"Z1 ");
any_compress = 1;
}
/* In case we have no compress algo at all, declare that
we prefer no compression. */
if (!any_compress)
strcat(dummy_string,"Z0 ");
/* Remove the trailing space. */
if (*dummy_string && dummy_string[strlen (dummy_string)-1] == ' ')
dummy_string[strlen (dummy_string)-1] = 0;
string=dummy_string;
}
}
else if (!ascii_strcasecmp (string, "none"))
string = "";
if(strlen(string))
{
char *prefstringbuf;
char *tok, *prefstring;
/* We need a writable string. */
prefstring = prefstringbuf = xstrdup (string);
while((tok=strsep(&prefstring," ,")))
{
if (!*tok)
;
else if((val=string_to_cipher_algo (tok)))
{
if(set_one_pref(val,1,tok,sym,&nsym))
rc=-1;
}
else if((val=string_to_digest_algo (tok)))
{
if(set_one_pref(val,2,tok,hash,&nhash))
rc=-1;
}
else if((val=string_to_compress_algo(tok))>-1)
{
if(set_one_pref(val,3,tok,zip,&nzip))
rc=-1;
}
else if ((val=string_to_aead_algo (tok)))
{
if (set_one_pref (val, 4, tok, aead, &naead))
rc = -1;
}
else if (!ascii_strcasecmp(tok, "mdc")
|| !ascii_strcasecmp(tok, "[mdc]"))
mdc=1;
else if (!ascii_strcasecmp(tok, "no-mdc")
|| !ascii_strcasecmp(tok, "[no-mdc]"))
mdc=0;
else if (!ascii_strcasecmp(tok, "ks-modify")
|| !ascii_strcasecmp(tok, "[ks-modify]"))
modify=1;
else if (!ascii_strcasecmp(tok,"no-ks-modify")
|| !ascii_strcasecmp(tok,"[no-ks-modify]"))
modify=0;
else if (!ascii_strcasecmp(tok,"aead")
|| !ascii_strcasecmp(tok,"[aead]"))
{
/* Ignore because this is set from the preferences but
* shown in the in the preferences/features list. */
}
else
{
log_info (_("invalid item '%s' in preference string\n"),tok);
rc=-1;
}
}
xfree (prefstringbuf);
}
if(!rc)
{
if(personal)
{
if(personal==PREFTYPE_SYM)
{
xfree(opt.personal_cipher_prefs);
if(nsym==0)
opt.personal_cipher_prefs=NULL;
else
{
int i;
opt.personal_cipher_prefs=
xmalloc(sizeof(prefitem_t *)*(nsym+1));
for (i=0; i<nsym; i++)
{
opt.personal_cipher_prefs[i].type = PREFTYPE_SYM;
opt.personal_cipher_prefs[i].value = sym[i];
}
opt.personal_cipher_prefs[i].type = PREFTYPE_NONE;
opt.personal_cipher_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_HASH)
{
xfree(opt.personal_digest_prefs);
if(nhash==0)
opt.personal_digest_prefs=NULL;
else
{
int i;
opt.personal_digest_prefs=
xmalloc(sizeof(prefitem_t *)*(nhash+1));
for (i=0; i<nhash; i++)
{
opt.personal_digest_prefs[i].type = PREFTYPE_HASH;
opt.personal_digest_prefs[i].value = hash[i];
}
opt.personal_digest_prefs[i].type = PREFTYPE_NONE;
opt.personal_digest_prefs[i].value = 0;
}
}
else if(personal==PREFTYPE_ZIP)
{
xfree(opt.personal_compress_prefs);
if(nzip==0)
opt.personal_compress_prefs=NULL;
else
{
int i;
opt.personal_compress_prefs=
xmalloc(sizeof(prefitem_t *)*(nzip+1));
for (i=0; i<nzip; i++)
{
opt.personal_compress_prefs[i].type = PREFTYPE_ZIP;
opt.personal_compress_prefs[i].value = zip[i];
}
opt.personal_compress_prefs[i].type = PREFTYPE_NONE;
opt.personal_compress_prefs[i].value = 0;
}
}
}
else
{
memcpy (sym_prefs, sym, (nsym_prefs=nsym));
memcpy (hash_prefs, hash, (nhash_prefs=nhash));
memcpy (zip_prefs, zip, (nzip_prefs=nzip));
memcpy (aead_prefs, aead, (naead_prefs=naead));
mdc_available = mdc;
aead_available = !!naead;
ks_modify = modify;
prefs_initialized = 1;
}
}
return rc;
}
/* Return a fake user ID containing the preferences. Caller must
free. */
PKT_user_id *
keygen_get_std_prefs(void)
{
int i,j=0;
PKT_user_id *uid=xmalloc_clear(sizeof(PKT_user_id));
if(!prefs_initialized)
keygen_set_std_prefs(NULL,0);
uid->ref=1;
uid->prefs = xmalloc ((sizeof(prefitem_t *)*
(nsym_prefs+naead_prefs+nhash_prefs+nzip_prefs+1)));
for(i=0;i<nsym_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_SYM;
uid->prefs[j].value=sym_prefs[i];
}
for (i=0; i < naead_prefs; i++, j++)
{
uid->prefs[j].type = PREFTYPE_AEAD;
uid->prefs[j].value = aead_prefs[i];
}
for(i=0;i<nhash_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_HASH;
uid->prefs[j].value=hash_prefs[i];
}
for(i=0;i<nzip_prefs;i++,j++)
{
uid->prefs[j].type=PREFTYPE_ZIP;
uid->prefs[j].value=zip_prefs[i];
}
uid->prefs[j].type=PREFTYPE_NONE;
uid->prefs[j].value=0;
uid->flags.mdc = mdc_available;
uid->flags.aead = aead_available;
uid->flags.ks_modify = ks_modify;
return uid;
}
static void
add_feature_mdc (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x01)) || (!enabled && !(s[0] & 0x01))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x01; /* MDC feature */
else
buf[0] &= ~0x01;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_feature_aead (PKT_signature *sig, int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n );
if (s && n && ((enabled && (s[0] & 0x02)) || (!enabled && !(s[0] & 0x02))))
return; /* Already set or cleared */
if (!s || !n)
{ /* Create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else
{
buf = xmalloc (n);
memcpy (buf, s, n);
}
if (enabled)
buf[0] |= 0x02; /* AEAD supported */
else
buf[0] &= ~0x02;
/* Are there any bits set? */
for (i=0; i < n; i++)
if (buf[i])
break;
if (i == n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_feature_v5 (PKT_signature *sig, int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_FEATURES, &n );
if (s && n && ((enabled && (s[0] & 0x04)) || (!enabled && !(s[0] & 0x04))))
return; /* Already set or cleared */
if (!s || !n)
{ /* Create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else
{
buf = xmalloc (n);
memcpy (buf, s, n);
}
if (enabled)
buf[0] |= 0x04; /* v5 key supported */
else
buf[0] &= ~0x04;
/* Are there any bits set? */
for (i=0; i < n; i++)
if (buf[i])
break;
if (i == n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_FEATURES);
else
build_sig_subpkt (sig, SIGSUBPKT_FEATURES, buf, n);
xfree (buf);
}
static void
add_keyserver_modify (PKT_signature *sig,int enabled)
{
const byte *s;
size_t n;
int i;
char *buf;
/* The keyserver modify flag is a negative flag (i.e. no-modify) */
enabled=!enabled;
s = parse_sig_subpkt (sig, 1, SIGSUBPKT_KS_FLAGS, &n );
/* Already set or cleared */
if (s && n &&
((enabled && (s[0] & 0x80)) || (!enabled && !(s[0] & 0x80))))
return;
if (!s || !n) { /* create a new one */
n = 1;
buf = xmalloc_clear (n);
}
else {
buf = xmalloc (n);
memcpy (buf, s, n);
}
if(enabled)
buf[0] |= 0x80; /* no-modify flag */
else
buf[0] &= ~0x80;
/* Are there any bits set? */
for(i=0;i<n;i++)
if(buf[i]!=0)
break;
if(i==n)
delete_sig_subpkt (sig->hashed, SIGSUBPKT_KS_FLAGS);
else
build_sig_subpkt (sig, SIGSUBPKT_KS_FLAGS, buf, n);
xfree (buf);
}
int
keygen_upd_std_prefs (PKT_signature *sig, void *opaque)
{
(void)opaque;
if (!prefs_initialized)
keygen_set_std_prefs (NULL, 0);
if (nsym_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_SYM, sym_prefs, nsym_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_SYM);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_SYM);
}
if (naead_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_AEAD, aead_prefs, naead_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_AEAD);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_AEAD);
}
if (nhash_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_HASH, hash_prefs, nhash_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_HASH);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_HASH);
}
if (nzip_prefs)
build_sig_subpkt (sig, SIGSUBPKT_PREF_COMPR, zip_prefs, nzip_prefs);
else
{
delete_sig_subpkt (sig->hashed, SIGSUBPKT_PREF_COMPR);
delete_sig_subpkt (sig->unhashed, SIGSUBPKT_PREF_COMPR);
}
/* Make sure that the MDC feature flag is set if needed. */
add_feature_mdc (sig,mdc_available);
add_feature_aead (sig, aead_available);
add_feature_v5 (sig, 1);
add_keyserver_modify (sig,ks_modify);
keygen_add_keyserver_url(sig,NULL);
return 0;
}
/****************
* Add preference to the self signature packet.
* This is only called for packets with version > 3.
*/
int
keygen_add_std_prefs (PKT_signature *sig, void *opaque)
{
PKT_public_key *pk = opaque;
do_add_key_flags (sig, pk->pubkey_usage);
keygen_add_key_expire (sig, opaque );
keygen_upd_std_prefs (sig, opaque);
keygen_add_keyserver_url (sig,NULL);
return 0;
}
int
keygen_add_keyserver_url(PKT_signature *sig, void *opaque)
{
const char *url=opaque;
if(!url)
url=opt.def_keyserver_url;
if(url)
build_sig_subpkt(sig,SIGSUBPKT_PREF_KS,url,strlen(url));
else
delete_sig_subpkt (sig->hashed,SIGSUBPKT_PREF_KS);
return 0;
}
/* This function is used to add a notations to a signature. In
* general the caller should have cleared exiting notations before
* adding new ones. For example by calling:
*
* delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION);
* delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION);
*
* Only human readable notaions may be added. NAME and value are
* expected to be UTF-* strings.
*/
static void
do_add_notation (PKT_signature *sig, const char *name, const char *value,
int critical)
{
unsigned char *buf;
unsigned int n1,n2;
n1 = strlen (name);
n2 = strlen (value);
buf = xmalloc (8 + n1 + n2);
buf[0] = 0x80; /* human readable. */
buf[1] = buf[2] = buf[3] = 0;
buf[4] = n1 >> 8;
buf[5] = n1;
buf[6] = n2 >> 8;
buf[7] = n2;
memcpy (buf+8, name, n1);
memcpy (buf+8+n1, value, n2);
build_sig_subpkt (sig,
(SIGSUBPKT_NOTATION|(critical?SIGSUBPKT_FLAG_CRITICAL:0)),
buf, 8+n1+n2 );
xfree (buf);
}
int
keygen_add_notations(PKT_signature *sig,void *opaque)
{
struct notation *notation;
/* We always start clean */
delete_sig_subpkt(sig->hashed,SIGSUBPKT_NOTATION);
delete_sig_subpkt(sig->unhashed,SIGSUBPKT_NOTATION);
sig->flags.notation=0;
for(notation=opaque;notation;notation=notation->next)
if(!notation->flags.ignore)
{
unsigned char *buf;
unsigned int n1,n2;
n1=strlen(notation->name);
if(notation->altvalue)
n2=strlen(notation->altvalue);
else if(notation->bdat)
n2=notation->blen;
else
n2=strlen(notation->value);
buf = xmalloc( 8 + n1 + n2 );
/* human readable or not */
buf[0] = notation->bdat?0:0x80;
buf[1] = buf[2] = buf[3] = 0;
buf[4] = n1 >> 8;
buf[5] = n1;
buf[6] = n2 >> 8;
buf[7] = n2;
memcpy(buf+8, notation->name, n1 );
if(notation->altvalue)
memcpy(buf+8+n1, notation->altvalue, n2 );
else if(notation->bdat)
memcpy(buf+8+n1, notation->bdat, n2 );
else
memcpy(buf+8+n1, notation->value, n2 );
build_sig_subpkt( sig, SIGSUBPKT_NOTATION |
(notation->flags.critical?SIGSUBPKT_FLAG_CRITICAL:0),
buf, 8+n1+n2 );
xfree(buf);
}
return 0;
}
int
keygen_add_revkey (PKT_signature *sig, void *opaque)
{
struct revocation_key *revkey = opaque;
byte buf[2+MAX_FINGERPRINT_LEN];
log_assert (revkey->fprlen <= MAX_FINGERPRINT_LEN);
buf[0] = revkey->class;
buf[1] = revkey->algid;
memcpy (buf + 2, revkey->fpr, revkey->fprlen);
memset (buf + 2 + revkey->fprlen, 0, sizeof (revkey->fpr) - revkey->fprlen);
build_sig_subpkt (sig, SIGSUBPKT_REV_KEY, buf, 2+revkey->fprlen);
/* All sigs with revocation keys set are nonrevocable. */
sig->flags.revocable = 0;
buf[0] = 0;
build_sig_subpkt (sig, SIGSUBPKT_REVOCABLE, buf, 1);
parse_revkeys (sig);
return 0;
}
/* Create a back-signature. If TIMESTAMP is not NULL, use it for the
signature creation time. */
gpg_error_t
make_backsig (ctrl_t ctrl, PKT_signature *sig, PKT_public_key *pk,
PKT_public_key *sub_pk, PKT_public_key *sub_psk,
u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PKT_signature *backsig;
cache_public_key (sub_pk);
err = make_keysig_packet (ctrl, &backsig, pk, NULL, sub_pk, sub_psk, 0x19,
timestamp, 0, NULL, NULL, cache_nonce);
if (err)
log_error ("make_keysig_packet failed for backsig: %s\n",
gpg_strerror (err));
else
{
/* Get it into a binary packed form. */
IOBUF backsig_out = iobuf_temp();
PACKET backsig_pkt;
init_packet (&backsig_pkt);
backsig_pkt.pkttype = PKT_SIGNATURE;
backsig_pkt.pkt.signature = backsig;
err = build_packet (backsig_out, &backsig_pkt);
free_packet (&backsig_pkt, NULL);
if (err)
log_error ("build_packet failed for backsig: %s\n", gpg_strerror (err));
else
{
size_t pktlen = 0;
byte *buf = iobuf_get_temp_buffer (backsig_out);
/* Remove the packet header. */
if(buf[0]&0x40)
{
if (buf[1] < 192)
{
pktlen = buf[1];
buf += 2;
}
else if(buf[1] < 224)
{
pktlen = (buf[1]-192)*256;
pktlen += buf[2]+192;
buf += 3;
}
else if (buf[1] == 255)
{
pktlen = buf32_to_size_t (buf+2);
buf += 6;
}
else
BUG ();
}
else
{
int mark = 1;
switch (buf[0]&3)
{
case 3:
BUG ();
break;
case 2:
pktlen = (size_t)buf[mark++] << 24;
pktlen |= buf[mark++] << 16;
/* fall through */
case 1:
pktlen |= buf[mark++] << 8;
/* fall through */
case 0:
pktlen |= buf[mark++];
}
buf += mark;
}
/* Now make the binary blob into a subpacket. */
build_sig_subpkt (sig, SIGSUBPKT_SIGNATURE, buf, pktlen);
iobuf_close (backsig_out);
}
}
return err;
}
/* Write a direct key signature to the first key in ROOT using the key
PSK. REVKEY is describes the direct key signature and TIMESTAMP is
the timestamp to set on the signature. */
static gpg_error_t
write_direct_sig (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk,
struct revocation_key *revkey, u32 timestamp,
const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing direct signature\n"));
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG ();
pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (ctrl, &sig, pk, NULL,NULL, psk, 0x1F,
timestamp, 0,
keygen_add_revkey, revkey, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err) );
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write a self-signature to the first user id in ROOT using the key
PSK. USE and TIMESTAMP give the extra data we need for the
signature. */
static gpg_error_t
write_selfsigs (ctrl_t ctrl, kbnode_t root, PKT_public_key *psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
PKT_user_id *uid;
KBNODE node;
PKT_public_key *pk;
if (opt.verbose)
log_info (_("writing self signature\n"));
/* Get the uid packet from the list. */
node = find_kbnode (root, PKT_USER_ID);
if (!node)
BUG(); /* No user id packet in tree. */
uid = node->pkt->pkt.user_id;
/* Get the pk packet from the pub_tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pk = node->pkt->pkt.public_key;
/* The usage has not yet been set - do it now. */
pk->pubkey_usage = use;
/* We have to cache the key, so that the verification of the
signature creation is able to retrieve the public key. */
cache_public_key (pk);
/* Make the signature. */
err = make_keysig_packet (ctrl, &sig, pk, uid, NULL, psk, 0x13,
timestamp, 0,
keygen_add_std_prefs, pk, cache_nonce);
if (err)
{
log_error ("make_keysig_packet failed: %s\n", gpg_strerror (err));
return err;
}
pkt = xmalloc_clear (sizeof *pkt);
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt));
return err;
}
/* Write the key binding signature. If TIMESTAMP is not NULL use the
signature creation time. PRI_PSK is the key use for signing.
SUB_PSK is a key used to create a back-signature; that one is only
used if USE has the PUBKEY_USAGE_SIG capability. */
static int
write_keybinding (ctrl_t ctrl, kbnode_t root,
PKT_public_key *pri_psk, PKT_public_key *sub_psk,
unsigned int use, u32 timestamp, const char *cache_nonce)
{
gpg_error_t err;
PACKET *pkt;
PKT_signature *sig;
KBNODE node;
PKT_public_key *pri_pk, *sub_pk;
struct opaque_data_usage_and_pk oduap;
if (opt.verbose)
log_info(_("writing key binding signature\n"));
/* Get the primary pk packet from the tree. */
node = find_kbnode (root, PKT_PUBLIC_KEY);
if (!node)
BUG();
pri_pk = node->pkt->pkt.public_key;
/* We have to cache the key, so that the verification of the
* signature creation is able to retrieve the public key. */
cache_public_key (pri_pk);
/* Find the last subkey. */
sub_pk = NULL;
for (node = root; node; node = node->next )
{
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
}
if (!sub_pk)
BUG();
/* Make the signature. */
oduap.usage = use;
if ((use & PUBKEY_USAGE_ENC)
&& opt.compliance == CO_DE_VS
/* The required libgcrypt 1.11 won't yet claim a compliant RNG. */
&& gnupg_rng_is_compliant (CO_DE_VS))
oduap.cpl_notation = "de-vs";
else if ((use & PUBKEY_USAGE_ENC)
&& sub_pk->pubkey_algo == PUBKEY_ALGO_KYBER)
oduap.cpl_notation = "fips203.ipd.2023-08-24";
else
oduap.cpl_notation = NULL;
oduap.pk = sub_pk;
err = make_keysig_packet (ctrl, &sig, pri_pk, NULL, sub_pk, pri_psk, 0x18,
timestamp, 0,
keygen_add_key_flags_from_oduap, &oduap,
cache_nonce);
if (err)
{
log_error ("make_keysig_packeto failed: %s\n", gpg_strerror (err));
return err;
}
/* Make a backsig. */
if (use & PUBKEY_USAGE_SIG)
{
err = make_backsig (ctrl,
sig, pri_pk, sub_pk, sub_psk, timestamp, cache_nonce);
if (err)
return err;
}
pkt = xmalloc_clear ( sizeof *pkt );
pkt->pkttype = PKT_SIGNATURE;
pkt->pkt.signature = sig;
add_kbnode (root, new_kbnode (pkt) );
return err;
}
/* Returns true if SEXP specified the curve ED448 or X448. */
static int
curve_is_448 (gcry_sexp_t sexp)
{
gcry_sexp_t list, l2;
char *curve;
int result;
list = gcry_sexp_find_token (sexp, "public-key", 0);
if (!list)
return 0; /* Not a public key. */
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return 0; /* Bad public key. */
l2 = gcry_sexp_find_token (list, "curve", 0);
gcry_sexp_release (list);
if (!l2)
return 0; /* No curve parameter. */
curve = gcry_sexp_nth_string (l2, 1);
gcry_sexp_release (l2);
if (!curve)
return 0; /* Bad curve parameter. */
result = (!ascii_strcasecmp (curve, "X448")
|| !ascii_strcasecmp (curve, "Ed448")
|| !ascii_strcasecmp (curve, "cv448"));
xfree (curve);
return result;
}
/* Extract the parameters in OpenPGP format from SEXP and put them
* into the caller provided ARRAY. SEXP2 is used to provide the
* parameters for dual algorithm (e.g. Kyber). */
static gpg_error_t
ecckey_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp,
gcry_sexp_t sexp2, int algo, int pkversion)
{
gpg_error_t err;
gcry_sexp_t list, l2;
char *curve = NULL;
int i;
const char *oidstr;
unsigned int nbits;
array[0] = NULL;
array[1] = NULL;
array[2] = NULL;
list = gcry_sexp_find_token (sexp, "public-key", 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
l2 = gcry_sexp_find_token (list, "curve", 0);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
curve = gcry_sexp_nth_string (l2, 1);
if (!curve)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
gcry_sexp_release (l2);
oidstr = openpgp_curve_to_oid (curve, &nbits, NULL);
if (!oidstr)
{
/* That can't happen because we used one of the curves
gpg_curve_to_oid knows about. */
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* For v5 keys we prefer the modern OID for cv25519. */
if (pkversion > 4 && !strcmp (oidstr, "1.3.6.1.4.1.3029.1.5.1"))
oidstr = "1.3.101.110";
err = openpgp_oid_from_str (oidstr, &array[0]);
if (err)
goto leave;
err = sexp_extract_param_sos (list, "q", &array[1]);
if (err)
goto leave;
gcry_sexp_release (list);
list = NULL;
if (algo == PUBKEY_ALGO_KYBER)
{
if (!sexp2)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
list = gcry_sexp_find_token (sexp2, "public-key", 0);
if (!list)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
{
err = gpg_error (GPG_ERR_NO_OBJ);
goto leave;
}
l2 = gcry_sexp_find_token (list, "p", 1);
if (!l2)
{
err = gpg_error (GPG_ERR_NO_OBJ); /* required parameter not found */
goto leave;
}
array[2] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_OPAQUE);
gcry_sexp_release (l2);
if (!array[2])
{
err = gpg_error (GPG_ERR_INV_OBJ); /* required parameter invalid */
goto leave;
}
}
else if (algo == PUBKEY_ALGO_ECDH)
{
array[2] = pk_ecdh_default_params (nbits);
if (!array[2])
{
err = gpg_error_from_syserror ();
goto leave;
}
}
leave:
xfree (curve);
gcry_sexp_release (list);
if (err)
{
for (i=0; i < 3; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
}
return err;
}
/* Extract key parameters from SEXP and store them in ARRAY. ELEMS is
a string where each character denotes a parameter name. TOPNAME is
the name of the top element above the elements. */
static int
key_from_sexp (gcry_mpi_t *array, gcry_sexp_t sexp,
const char *topname, const char *elems)
{
gcry_sexp_t list, l2;
const char *s;
int i, idx;
int rc = 0;
list = gcry_sexp_find_token (sexp, topname, 0);
if (!list)
return gpg_error (GPG_ERR_INV_OBJ);
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
if (!list)
return gpg_error (GPG_ERR_NO_OBJ);
for (idx=0,s=elems; *s; s++, idx++)
{
l2 = gcry_sexp_find_token (list, s, 1);
if (!l2)
{
rc = gpg_error (GPG_ERR_NO_OBJ); /* required parameter not found */
goto leave;
}
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
if (!array[idx])
{
rc = gpg_error (GPG_ERR_INV_OBJ); /* required parameter invalid */
goto leave;
}
}
gcry_sexp_release (list);
leave:
if (rc)
{
for (i=0; i<idx; i++)
{
gcry_mpi_release (array[i]);
array[i] = NULL;
}
gcry_sexp_release (list);
}
return rc;
}
/* Create a keyblock using the given KEYGRIP. ALGO is the OpenPGP
* algorithm of that keygrip. If CARDKEY is true the key is expected
* to already live on the active card. */
static int
do_create_from_keygrip (ctrl_t ctrl, int algo,
const char *hexkeygrip, int cardkey,
kbnode_t pub_root, u32 timestamp, u32 expireval,
int is_subkey, int *keygen_flags)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
gcry_sexp_t s_key2 = NULL;
const char *algoelem;
char *hexkeygrip_buffer = NULL;
char *hexkeygrip2 = NULL;
if (hexkeygrip[0] == '&')
hexkeygrip++;
if (strchr (hexkeygrip, ','))
{
hexkeygrip_buffer = xtrystrdup (hexkeygrip);
if (!hexkeygrip_buffer)
return gpg_error_from_syserror ();
hexkeygrip = hexkeygrip_buffer;
hexkeygrip2 = strchr (hexkeygrip_buffer, ',');
if (hexkeygrip2)
*hexkeygrip2++ = 0;
}
switch (algo)
{
case PUBKEY_ALGO_RSA: algoelem = "ne"; break;
case PUBKEY_ALGO_DSA: algoelem = "pqgy"; break;
case PUBKEY_ALGO_ELGAMAL_E: algoelem = "pgy"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA: algoelem = ""; break;
case PUBKEY_ALGO_EDDSA: algoelem = ""; break;
case PUBKEY_ALGO_KYBER: algoelem = ""; break;
default:
xfree (hexkeygrip_buffer);
return gpg_error (GPG_ERR_INTERNAL);
}
/* Ask the agent for the public key matching HEXKEYGRIP. */
if (cardkey)
{
err = agent_scd_readkey (ctrl, hexkeygrip, &s_key, NULL);
if (err)
{
xfree (hexkeygrip_buffer);
return err;
}
}
else
{
unsigned char *public;
err = agent_readkey (ctrl, 0, hexkeygrip, &public);
if (err)
{
xfree (hexkeygrip_buffer);
return err;
}
err = gcry_sexp_sscan (&s_key, NULL, public,
gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
{
xfree (hexkeygrip_buffer);
return err;
}
if (hexkeygrip2)
{
err = agent_readkey (ctrl, 0, hexkeygrip2, &public);
if (err)
{
gcry_sexp_release (s_key);
xfree (hexkeygrip_buffer);
return err;
}
err = gcry_sexp_sscan (&s_key2, NULL, public,
gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
{
gcry_sexp_release (s_key);
xfree (hexkeygrip_buffer);
return err;
}
}
}
/* For X448 and Kyber we force the use of v5 packets. */
if (curve_is_448 (s_key) || algo == PUBKEY_ALGO_KYBER)
*keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
/* Build a public key packet. */
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
gcry_sexp_release (s_key2);
xfree (hexkeygrip_buffer);
return err;
}
pk->timestamp = timestamp;
pk->version = (*keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_KYBER)
err = ecckey_from_sexp (pk->pkey, s_key, s_key2, algo, pk->version);
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, NULL, algo, pk->version);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
gcry_sexp_release (s_key2);
free_public_key (pk);
xfree (hexkeygrip_buffer);
return err;
}
gcry_sexp_release (s_key);
gcry_sexp_release (s_key2);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
xfree (hexkeygrip_buffer);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
xfree (hexkeygrip_buffer);
return 0;
}
/* Common code for the key generation function gen_xxx. The optional
* (COMMON_GEN_CB,COMMON_GEN_CB_PARM) can be used as communication
* object. A KEYPARMS2 forces the use of a dual key (e.g. Kyber+ECC).
*/
static int
common_gen (const char *keyparms, const char *keyparms2,
int algo, const char *algoelem,
kbnode_t pub_root, u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr,
gpg_error_t (*common_gen_cb)(common_gen_cb_parm_t),
common_gen_cb_parm_t common_gen_cb_parm)
{
int err;
PACKET *pkt;
PKT_public_key *pk;
gcry_sexp_t s_key;
gcry_sexp_t s_key2 = NULL;
err = agent_genkey (NULL, cache_nonce_addr, passwd_nonce_addr, keyparms,
!!(keygen_flags & KEYGEN_FLAG_NO_PROTECTION),
passphrase, timestamp,
&s_key);
if (err)
{
log_error ("agent_genkey failed: %s\n", gpg_strerror (err) );
return err;
}
if (keyparms2)
{
err = agent_genkey (NULL, NULL, NULL, keyparms2,
1 /* No protection */,
NULL, timestamp,
&s_key2);
if (err)
{
log_error ("agent_genkey failed for second algo: %s\n",
gpg_strerror (err) );
gcry_sexp_release (s_key);
return err;
}
}
if (common_gen_cb && common_gen_cb_parm)
{
common_gen_cb_parm->genkey_result = s_key;
common_gen_cb_parm->genkey_result2 = s_key2;
err = common_gen_cb (common_gen_cb_parm);
common_gen_cb_parm->genkey_result = NULL;
common_gen_cb_parm->genkey_result2 = NULL;
if (err)
{
gcry_sexp_release (s_key);
gcry_sexp_release (s_key2);
return err;
}
}
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
return err;
}
pk->timestamp = timestamp;
pk->version = (keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
if (algo == PUBKEY_ALGO_KYBER)
err = ecckey_from_sexp (pk->pkey, s_key, s_key2, algo, pk->version);
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, NULL, algo, pk->version);
else
err = key_from_sexp (pk->pkey, s_key, "public-key", algoelem);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
gcry_sexp_release (s_key);
free_public_key (pk);
return err;
}
gcry_sexp_release (s_key);
gcry_sexp_release (s_key2);
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
err = gpg_error_from_syserror ();
free_public_key (pk);
return err;
}
pkt->pkttype = is_subkey ? PKT_PUBLIC_SUBKEY : PKT_PUBLIC_KEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
}
/*
* Generate an Elgamal key.
*/
static int
gen_elg (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr,
gpg_error_t (*common_gen_cb)(common_gen_cb_parm_t),
common_gen_cb_parm_t common_gen_cb_parm)
{
int err;
char *keyparms;
char nbitsstr[35];
log_assert (is_ELGAMAL (algo));
if (nbits < 1024)
{
nbits = 2048;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > 4096)
{
nbits = 4096;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
/* Note that we use transient-key only if no-protection has also
been enabled. */
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(%s(nbits %zu:%s)%s))",
algo == GCRY_PK_ELG_E ? "openpgp-elg" :
algo == GCRY_PK_ELG ? "elg" : "x-oops" ,
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, NULL, algo, "pgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
xfree (keyparms);
}
return err;
}
/*
* Generate an DSA key
*/
static gpg_error_t
gen_dsa (unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr,
gpg_error_t (*common_gen_cb)(common_gen_cb_parm_t),
common_gen_cb_parm_t common_gen_cb_parm)
{
int err;
unsigned int qbits;
char *keyparms;
char nbitsstr[35];
char qbitsstr[35];
if (nbits < 768)
{
nbits = 2048;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
else if ( nbits > 3072 )
{
nbits = 3072;
log_info(_("keysize invalid; using %u bits\n"), nbits );
}
if( (nbits % 64) )
{
nbits = ((nbits + 63) / 64) * 64;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/* To comply with FIPS rules we round up to the next value unless in
expert mode. */
if (!opt.expert && nbits > 1024 && (nbits % 1024))
{
nbits = ((nbits + 1023) / 1024) * 1024;
log_info(_("keysize rounded up to %u bits\n"), nbits );
}
/*
Figure out a q size based on the key size. FIPS 180-3 says:
L = 1024, N = 160
L = 2048, N = 224
L = 2048, N = 256
L = 3072, N = 256
2048/256 is an odd pair since there is also a 2048/224 and
3072/256. Matching sizes is not a very exact science.
We'll do 256 qbits for nbits over 2047, 224 for nbits over 1024
but less than 2048, and 160 for 1024 (DSA1).
*/
if (nbits > 2047)
qbits = 256;
else if ( nbits > 1024)
qbits = 224;
else
qbits = 160;
if (qbits != 160 )
log_info (_("WARNING: some OpenPGP programs can't"
" handle a DSA key with this digest size\n"));
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
snprintf (qbitsstr, sizeof qbitsstr, "%u", qbits);
keyparms = xtryasprintf ("(genkey(dsa(nbits %zu:%s)(qbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
strlen (qbitsstr), qbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, NULL, PUBKEY_ALGO_DSA, "pqgy",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
xfree (keyparms);
}
return err;
}
/*
* Generate an ECC key.
* Note that KEYGEN_FLAGS might be updated by this function to
* indicate the forced creation of a v5 key.
*/
static gpg_error_t
gen_ecc (int algo, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int *keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr,
gpg_error_t (*common_gen_cb)(common_gen_cb_parm_t),
common_gen_cb_parm_t common_gen_cb_parm)
{
gpg_error_t err;
char *keyparms;
log_assert (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH);
if (!curve || !*curve)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Map the displayed short forms of some curves to their canonical
* names. */
if (!ascii_strcasecmp (curve, "cv25519"))
curve = "Curve25519";
else if (!ascii_strcasecmp (curve, "ed25519"))
curve = "Ed25519";
else if (!ascii_strcasecmp (curve, "cv448"))
curve = "X448";
else if (!ascii_strcasecmp (curve, "ed448"))
curve = "Ed448";
/* Note that we use the "comp" flag with EdDSA to request the use of
a 0x40 compression prefix octet. */
if (algo == PUBKEY_ALGO_EDDSA && !strcmp (curve, "Ed25519"))
{
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags eddsa comp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
else if (algo == PUBKEY_ALGO_EDDSA && !strcmp (curve, "Ed448"))
{
*keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags comp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
else if (algo == PUBKEY_ALGO_ECDH && !strcmp (curve, "Curve25519"))
{
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags djb-tweak comp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
else if (algo == PUBKEY_ALGO_ECDH && !strcmp (curve, "X448"))
{
*keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags comp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
else
{
keyparms = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags nocomp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, NULL, algo, "",
pub_root, timestamp, expireval, is_subkey,
*keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
xfree (keyparms);
}
return err;
}
/* Generate a dual ECC+Kyber key. Note that KEYGEN_FLAGS will be
* updated by this function to indicate the forced creation of a v5
* key. */
static gpg_error_t
gen_kyber (int algo, unsigned int nbits, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int *keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr,
gpg_error_t (*common_gen_cb)(common_gen_cb_parm_t),
common_gen_cb_parm_t common_gen_cb_parm)
{
gpg_error_t err;
char *keyparms1;
const char *keyparms2;
log_assert (algo == PUBKEY_ALGO_KYBER);
if (nbits == 768)
keyparms2 = "(genkey(kyber768))";
else if (nbits == 1024)
keyparms2 = "(genkey(kyber1024))";
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
if (!curve || !*curve)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
*keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
if (!strcmp (curve, "Curve25519"))
{
keyparms1 = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags djb-tweak comp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
else if (!strcmp (curve, "X448"))
{
keyparms1 = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags comp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
else /* Should we use the compressed format? Check smartcard support. */
{
keyparms1 = xtryasprintf
("(genkey(ecc(curve %zu:%s)(flags nocomp%s)))",
strlen (curve), curve,
(((*keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (*keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
" transient-key" : ""));
}
if (!keyparms1)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms1, keyparms2, algo, "",
pub_root, timestamp, expireval, is_subkey,
*keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
xfree (keyparms1);
}
return err;
}
/*
* Generate an RSA key.
*/
static int
gen_rsa (int algo, unsigned int nbits, KBNODE pub_root,
u32 timestamp, u32 expireval, int is_subkey,
int keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr,
gpg_error_t (*common_gen_cb)(common_gen_cb_parm_t),
common_gen_cb_parm_t common_gen_cb_parm)
{
int err;
char *keyparms;
char nbitsstr[35];
const unsigned maxsize = (opt.flags.large_rsa ? 8192 : 4096);
log_assert (is_RSA(algo));
if (!nbits)
nbits = get_keysize_range (algo, NULL, NULL);
if (nbits < 1024)
{
nbits = 3072;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
else if (nbits > maxsize)
{
nbits = maxsize;
log_info (_("keysize invalid; using %u bits\n"), nbits );
}
if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
log_info (_("keysize rounded up to %u bits\n"), nbits );
}
snprintf (nbitsstr, sizeof nbitsstr, "%u", nbits);
keyparms = xtryasprintf ("(genkey(rsa(nbits %zu:%s)%s))",
strlen (nbitsstr), nbitsstr,
((keygen_flags & KEYGEN_FLAG_TRANSIENT_KEY)
&& (keygen_flags & KEYGEN_FLAG_NO_PROTECTION))?
"(transient-key)" : "" );
if (!keyparms)
err = gpg_error_from_syserror ();
else
{
err = common_gen (keyparms, NULL, algo, "ne",
pub_root, timestamp, expireval, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
xfree (keyparms);
}
return err;
}
/****************
* check valid days:
* return 0 on error or the multiplier
*/
static int
check_valid_days( const char *s )
{
if( !digitp(s) )
return 0;
for( s++; *s; s++)
if( !digitp(s) )
break;
if( !*s )
return 1;
if( s[1] )
return 0; /* e.g. "2323wc" */
if( *s == 'd' || *s == 'D' )
return 1;
if( *s == 'w' || *s == 'W' )
return 7;
if( *s == 'm' || *s == 'M' )
return 30;
if( *s == 'y' || *s == 'Y' )
return 365;
return 0;
}
static void
print_key_flags(int flags)
{
if(flags&PUBKEY_USAGE_SIG)
tty_printf("%s ",_("Sign"));
if(flags&PUBKEY_USAGE_CERT)
tty_printf("%s ",_("Certify"));
if(flags&PUBKEY_USAGE_ENC)
tty_printf("%s ",_("Encrypt"));
if(flags&PUBKEY_USAGE_AUTH)
tty_printf("%s ",_("Authenticate"));
if(flags&PUBKEY_USAGE_RENC)
tty_printf("%s ", "RENC");
}
/* Ask for the key flags and return them. CURRENT gives the current
* usage which should normally be given as 0. MASK gives the allowed
* flags. */
unsigned int
ask_key_flags_with_mask (int algo, int subkey, unsigned int current,
unsigned int mask)
{
/* TRANSLATORS: Please use only plain ASCII characters for the
* translation. If this is not possible use single digits. The
* string needs to 8 bytes long. Here is a description of the
* functions:
*
* s = Toggle signing capability
* e = Toggle encryption capability
* a = Toggle authentication capability
* q = Finish
*/
const char *togglers = _("SsEeAaQq");
char *answer = NULL;
const char *s;
unsigned int possible;
if ( strlen(togglers) != 8 )
{
tty_printf ("NOTE: Bad translation at %s:%d. "
"Please report.\n", __FILE__, __LINE__);
togglers = "11223300";
}
/* restrict the mask to the actual useful bits. */
/* Mask the possible usage flags. This is for example used for a
* card based key. For ECDH we need to allows additional usages if
* they are provided. RENC is not directly poissible here but see
* below for a workaround. */
possible = (openpgp_pk_algo_usage (algo) & mask);
possible &= ~PUBKEY_USAGE_RENC;
if (algo == PUBKEY_ALGO_ECDH)
possible |= (current & (PUBKEY_USAGE_ENC
|PUBKEY_USAGE_CERT
|PUBKEY_USAGE_SIG
|PUBKEY_USAGE_AUTH));
/* However, only primary keys may certify. */
if (subkey)
possible &= ~PUBKEY_USAGE_CERT;
/* Preload the current set with the possible set, without
* authentication if CURRENT is 0. If CURRENT is non-zero we mask
* with all possible usages. */
if (current)
current &= possible;
else
current = (possible&~PUBKEY_USAGE_AUTH);
for (;;)
{
tty_printf("\n");
tty_printf(_("Possible actions for this %s key: "),
(algo == PUBKEY_ALGO_ECDH
|| algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
? "ECC" : openpgp_pk_algo_name (algo));
print_key_flags(possible);
tty_printf("\n");
tty_printf(_("Current allowed actions: "));
print_key_flags(current);
tty_printf("\n\n");
if(possible&PUBKEY_USAGE_SIG)
tty_printf(_(" (%c) Toggle the sign capability\n"),
togglers[0]);
if(possible&PUBKEY_USAGE_ENC)
tty_printf(_(" (%c) Toggle the encrypt capability\n"),
togglers[2]);
if(possible&PUBKEY_USAGE_AUTH)
tty_printf(_(" (%c) Toggle the authenticate capability\n"),
togglers[4]);
tty_printf(_(" (%c) Finished\n"),togglers[6]);
tty_printf("\n");
xfree(answer);
answer = cpr_get("keygen.flags",_("Your selection? "));
cpr_kill_prompt();
if (*answer == '=')
{
/* Hack to allow direct entry of the capabilities. */
current = 0;
for (s=answer+1; *s; s++)
{
if ((*s == 's' || *s == 'S') && (possible&PUBKEY_USAGE_SIG))
current |= PUBKEY_USAGE_SIG;
else if ((*s == 'e' || *s == 'E') && (possible&PUBKEY_USAGE_ENC))
current |= PUBKEY_USAGE_ENC;
else if ((*s == 'a' || *s == 'A') && (possible&PUBKEY_USAGE_AUTH))
current |= PUBKEY_USAGE_AUTH;
else if (!subkey && *s == 'c')
{
/* Accept 'c' for the primary key because USAGE_CERT
will be set anyway. This is for folks who
want to experiment with a cert-only primary key. */
current |= PUBKEY_USAGE_CERT;
}
else if ((*s == 'r' || *s == 'R') && (possible&PUBKEY_USAGE_ENC))
{
/* Allow to set RENC or an encryption capable key.
* This is on purpose not shown in the menu. */
current |= PUBKEY_USAGE_RENC;
}
}
break;
}
else if (strlen(answer)>1)
tty_printf(_("Invalid selection.\n"));
else if(*answer=='\0' || *answer==togglers[6] || *answer==togglers[7])
break;
else if((*answer==togglers[0] || *answer==togglers[1])
&& possible&PUBKEY_USAGE_SIG)
{
if(current&PUBKEY_USAGE_SIG)
current&=~PUBKEY_USAGE_SIG;
else
current|=PUBKEY_USAGE_SIG;
}
else if((*answer==togglers[2] || *answer==togglers[3])
&& possible&PUBKEY_USAGE_ENC)
{
if(current&PUBKEY_USAGE_ENC)
current&=~PUBKEY_USAGE_ENC;
else
current|=PUBKEY_USAGE_ENC;
}
else if((*answer==togglers[4] || *answer==togglers[5])
&& possible&PUBKEY_USAGE_AUTH)
{
if(current&PUBKEY_USAGE_AUTH)
current&=~PUBKEY_USAGE_AUTH;
else
current|=PUBKEY_USAGE_AUTH;
}
else
tty_printf(_("Invalid selection.\n"));
}
xfree(answer);
return current;
}
unsigned int
ask_key_flags (int algo, int subkey, unsigned int current)
{
return ask_key_flags_with_mask (algo, subkey, current, ~0);
}
/* Check whether we have a key for the key with HEXGRIP. Returns 0 if
there is no such key or the OpenPGP algo number for the key. */
static int
check_keygrip (ctrl_t ctrl, const char *hexgrip)
{
gpg_error_t err;
unsigned char *public;
size_t publiclen;
int algo;
if (hexgrip[0] == '&')
hexgrip++;
err = agent_readkey (ctrl, 0, hexgrip, &public);
if (err)
return 0;
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
algo = get_pk_algo_from_canon_sexp (public, publiclen);
xfree (public);
return map_gcry_pk_to_openpgp (algo);
}
/* Ask for an algorithm. The function returns the algorithm id to
* create. If ADDMODE is false the function won't show an option to
* create the primary and subkey combined and won't set R_USAGE
* either. If a combined algorithm has been selected, the subkey
* algorithm is stored at R_SUBKEY_ALGO. If R_KEYGRIP is given, the
* user has the choice to enter the keygrip of an existing key. That
* keygrip is then stored at this address. The caller needs to free
* it. If R_CARDKEY is not NULL and the keygrip has been taken from
* an active card, true is stored there; if R_KEYTIME is not NULL the
* creation time of that key is then stored there. */
static int
ask_algo (ctrl_t ctrl, int addmode, int *r_subkey_algo, unsigned int *r_usage,
char **r_keygrip, int *r_cardkey, u32 *r_keytime)
{
gpg_error_t err;
char *keygrip = NULL;
u32 keytime = 0;
char *answer = NULL;
int cardkey = 0;
int algo;
int dummy_algo;
if (!r_subkey_algo)
r_subkey_algo = &dummy_algo;
tty_printf (_("Please select what kind of key you want:\n"));
#if GPG_USE_RSA
if (!addmode)
tty_printf (_(" (%d) RSA and RSA%s\n"), 1, "");
#endif
if (!addmode && opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA and Elgamal%s\n"), 2, "");
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (sign only)%s\n"), 3, "");
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (sign only)%s\n"), 4, "");
#endif
if (addmode)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) Elgamal (encrypt only)%s\n"), 5, "");
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (encrypt only)%s\n"), 6, "");
#endif
}
if (opt.expert)
{
if (opt.compliance != CO_DE_VS)
tty_printf (_(" (%d) DSA (set your own capabilities)%s\n"), 7, "");
#if GPG_USE_RSA
tty_printf (_(" (%d) RSA (set your own capabilities)%s\n"), 8, "");
#endif
}
#if GPG_USE_ECDSA || GPG_USE_ECDH || GPG_USE_EDDSA
if (!addmode)
tty_printf (_(" (%d) ECC (sign and encrypt)%s\n"), 9, _(" *default*") );
tty_printf (_(" (%d) ECC (sign only)\n"), 10 );
if (opt.expert)
tty_printf (_(" (%d) ECC (set your own capabilities)%s\n"), 11, "");
if (addmode)
tty_printf (_(" (%d) ECC (encrypt only)%s\n"), 12, "");
#endif
if (opt.expert && r_keygrip)
tty_printf (_(" (%d) Existing key%s\n"), 13, "");
if (r_keygrip)
tty_printf (_(" (%d) Existing key from card%s\n"), 14, "");
for (;;)
{
*r_usage = 0;
*r_subkey_algo = 0;
xfree (answer);
answer = cpr_get ("keygen.algo", _("Your selection? "));
cpr_kill_prompt ();
algo = *answer? atoi (answer) : 9; /* Default algo is 9 */
if (opt.compliance == CO_DE_VS
&& (algo == 2 || algo == 3 || algo == 5 || algo == 7))
{
tty_printf (_("Invalid selection.\n"));
}
else if ((algo == 1 || !strcmp (answer, "rsa+rsa")) && !addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_subkey_algo = PUBKEY_ALGO_RSA;
break;
}
else if ((algo == 2 || !strcmp (answer, "dsa+elg")) && !addmode)
{
algo = PUBKEY_ALGO_DSA;
*r_subkey_algo = PUBKEY_ALGO_ELGAMAL_E;
break;
}
else if (algo == 3 || !strcmp (answer, "dsa"))
{
algo = PUBKEY_ALGO_DSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if (algo == 4 || !strcmp (answer, "rsa/s"))
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 5 || !strcmp (answer, "elg")) && addmode)
{
algo = PUBKEY_ALGO_ELGAMAL_E;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 6 || !strcmp (answer, "rsa/e")) && addmode)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 7 || !strcmp (answer, "dsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_DSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 8 || !strcmp (answer, "rsa/*")) && opt.expert)
{
algo = PUBKEY_ALGO_RSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 9 || !strcmp (answer, "ecc+ecc"))
&& !addmode)
{
algo = PUBKEY_ALGO_ECDSA;
*r_subkey_algo = PUBKEY_ALGO_ECDH;
break;
}
else if ((algo == 10 || !strcmp (answer, "ecc/s")))
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = PUBKEY_USAGE_SIG;
break;
}
else if ((algo == 11 || !strcmp (answer, "ecc/*")) && opt.expert)
{
algo = PUBKEY_ALGO_ECDSA;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 12 || !strcmp (answer, "ecc/e"))
&& addmode)
{
algo = PUBKEY_ALGO_ECDH;
*r_usage = PUBKEY_USAGE_ENC;
break;
}
else if ((algo == 13 || !strcmp (answer, "keygrip"))
&& opt.expert && r_keygrip)
{
for (;;)
{
xfree (answer);
answer = cpr_get ("keygen.keygrip", _("Enter the keygrip: "));
cpr_kill_prompt ();
trim_spaces (answer);
if (!*answer)
{
xfree (answer);
answer = NULL;
continue;
}
if (strlen (answer) == 40+1+40 && answer[40]==',')
{
int algo1, algo2;
answer[40] = 0;
algo1 = check_keygrip (ctrl, answer);
algo2 = check_keygrip (ctrl, answer+41);
answer[40] = ',';
if (algo1 == PUBKEY_ALGO_ECDH && algo2 == PUBKEY_ALGO_KYBER)
{
algo = PUBKEY_ALGO_KYBER;
break;
}
else if (!algo1 || !algo2)
tty_printf (_("No key with this keygrip\n"));
else
tty_printf ("Invalid combination for dual algo (%d,%d)\n",
algo1, algo2);
}
else if (strlen (answer) != 40 &&
!(answer[0] == '&' && strlen (answer+1) == 40))
tty_printf
(_("Not a valid keygrip (expecting 40 hex digits)\n"));
else if (!(algo = check_keygrip (ctrl, answer)) )
tty_printf (_("No key with this keygrip\n"));
else
break; /* Okay. */
}
xfree (keygrip);
keygrip = answer;
answer = NULL;
*r_usage = ask_key_flags (algo, addmode, 0);
break;
}
else if ((algo == 14 || !strcmp (answer, "cardkey")) && r_keygrip)
{
char *serialno;
keypair_info_t keypairlist, kpi;
int count, selection;
err = agent_scd_serialno (&serialno, NULL);
if (err)
{
tty_printf (_("error reading the card: %s\n"),
gpg_strerror (err));
goto ask_again;
}
tty_printf (_("Serial number of the card: %s\n"), serialno);
xfree (serialno);
err = agent_scd_keypairinfo (ctrl, NULL, &keypairlist);
if (err)
{
tty_printf (_("error reading the card: %s\n"),
gpg_strerror (err));
goto ask_again;
}
do
{
char *authkeyref, *encrkeyref, *signkeyref;
agent_scd_getattr_one ("$AUTHKEYID", &authkeyref);
agent_scd_getattr_one ("$ENCRKEYID", &encrkeyref);
agent_scd_getattr_one ("$SIGNKEYID", &signkeyref);
tty_printf (_("Available keys:\n"));
for (count=1, kpi=keypairlist; kpi; kpi = kpi->next, count++)
{
gcry_sexp_t s_pkey;
char *algostr = NULL;
enum gcry_pk_algos algoid = 0;
const char *keyref = kpi->idstr;
int any = 0;
if (keyref
&& !agent_scd_readkey (ctrl, keyref, &s_pkey, NULL))
{
algostr = pubkey_algo_string (s_pkey, &algoid);
gcry_sexp_release (s_pkey);
}
/* We need to tweak the algo in case GCRY_PK_ECC is
* returned because pubkey_algo_string is not aware
* of the OpenPGP algo mapping. We need to
* distinguish between ECDH and ECDSA but we can do
* that only if we got usage flags.
* Note: Keep this in sync with parse_key_parameter_part.
*/
if (algoid == GCRY_PK_ECC && algostr)
{
if (!strcmp (algostr, "ed25519"))
kpi->algo = PUBKEY_ALGO_EDDSA;
else if (!strcmp (algostr, "ed448"))
kpi->algo = PUBKEY_ALGO_EDDSA;
else if (!strcmp (algostr, "cv25519"))
kpi->algo = PUBKEY_ALGO_ECDH;
else if (!strcmp (algostr, "cv448"))
kpi->algo = PUBKEY_ALGO_ECDH;
else if ((kpi->usage & GCRY_PK_USAGE_ENCR))
kpi->algo = PUBKEY_ALGO_ECDH;
else
kpi->algo = PUBKEY_ALGO_ECDSA;
}
else
kpi->algo = map_gcry_pk_to_openpgp (algoid);
tty_printf (" (%d) %s %s %s",
count, kpi->keygrip, keyref, algostr);
if ((kpi->usage & GCRY_PK_USAGE_CERT))
{
tty_printf ("%scert", any?",":" (");
any = 1;
}
if ((kpi->usage & GCRY_PK_USAGE_SIGN))
{
tty_printf ("%ssign%s", any?",":" (",
(signkeyref && keyref
&& !strcmp (signkeyref, keyref))? "*":"");
any = 1;
}
if ((kpi->usage & GCRY_PK_USAGE_AUTH))
{
tty_printf ("%sauth%s", any?",":" (",
(authkeyref && keyref
&& !strcmp (authkeyref, keyref))? "*":"");
any = 1;
}
if ((kpi->usage & GCRY_PK_USAGE_ENCR))
{
tty_printf ("%sencr%s", any?",":" (",
(encrkeyref && keyref
&& !strcmp (encrkeyref, keyref))? "*":"");
any = 1;
}
tty_printf ("%s\n", any?")":"");
xfree (algostr);
}
xfree (answer);
answer = cpr_get ("keygen.cardkey", _("Your selection? "));
cpr_kill_prompt ();
trim_spaces (answer);
selection = atoi (answer);
xfree (authkeyref);
xfree (encrkeyref);
xfree (signkeyref);
}
while (!(selection > 0 && selection < count));
for (count=1,kpi=keypairlist; kpi; kpi = kpi->next, count++)
if (count == selection)
break;
if (!kpi)
{
/* Just in case COUNT is zero (no keys). */
free_keypair_info (keypairlist);
goto ask_again;
}
xfree (keygrip);
keygrip = xstrdup (kpi->keygrip);
cardkey = 1;
algo = kpi->algo;
keytime = kpi->keytime;
/* In expert mode allow to change the usage flags. */
if (opt.expert)
*r_usage = ask_key_flags_with_mask (algo, addmode,
kpi->usage, kpi->usage);
else
{
*r_usage = kpi->usage;
if (addmode)
*r_usage &= ~GCRY_PK_USAGE_CERT;
}
free_keypair_info (keypairlist);
break;
}
else
tty_printf (_("Invalid selection.\n"));
ask_again:
;
}
xfree(answer);
if (r_keygrip)
*r_keygrip = keygrip;
if (r_cardkey)
*r_cardkey = cardkey;
if (r_keytime)
*r_keytime = keytime;
return algo;
}
static unsigned int
get_keysize_range (int algo, unsigned int *min, unsigned int *max)
{
unsigned int def;
unsigned int dummy1, dummy2;
if (!min)
min = &dummy1;
if (!max)
max = &dummy2;
switch(algo)
{
case PUBKEY_ALGO_DSA:
*min = opt.expert? 768 : 1024;
*max=3072;
def=2048;
break;
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
*min=256;
*max=521;
def=256;
break;
case PUBKEY_ALGO_EDDSA:
*min=255;
*max=441;
def=255;
break;
case PUBKEY_ALGO_KYBER:
*min = 768;
*max = 1024;
def = 768;
break;
default:
*min = opt.compliance == CO_DE_VS ? 2048: 1024;
*max = 4096;
def = 3072;
break;
}
return def;
}
/* Return a fixed up keysize depending on ALGO. */
static unsigned int
fixup_keysize (unsigned int nbits, int algo, int silent)
{
unsigned int orig_nbits = nbits;
if (algo == PUBKEY_ALGO_DSA && (nbits % 64))
{
nbits = ((nbits + 63) / 64) * 64;
}
else if (algo == PUBKEY_ALGO_EDDSA)
{
if (nbits < 256)
nbits = 255;
else
nbits = 441;
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
{
if (nbits < 256)
nbits = 256;
else if (nbits < 384)
nbits = 384;
else
nbits = 521;
}
else if (algo == PUBKEY_ALGO_KYBER)
{
/* (in reality the numbers are not bits) */
if (nbits < 768)
nbits = 768;
else if (nbits > 1024)
nbits = 1024;
}
else if ((nbits % 32))
{
nbits = ((nbits + 31) / 32) * 32;
}
if (!silent && orig_nbits != nbits)
tty_printf (_("rounded to %u bits\n"), nbits);
return nbits;
}
/* Ask for the key size. ALGO is the algorithm. If PRIMARY_KEYSIZE
is not 0, the function asks for the size of the encryption
subkey. */
static unsigned
ask_keysize (int algo, unsigned int primary_keysize)
{
unsigned int nbits;
unsigned int min, def, max;
int for_subkey = !!primary_keysize;
int autocomp = 0;
def = get_keysize_range (algo, &min, &max);
if (primary_keysize && !opt.expert)
{
/* Deduce the subkey size from the primary key size. */
if (algo == PUBKEY_ALGO_DSA && primary_keysize > 3072)
nbits = 3072; /* For performance reasons we don't support more
than 3072 bit DSA. However we won't see this
case anyway because DSA can't be used as an
encryption subkey ;-). */
else
nbits = primary_keysize;
autocomp = 1;
goto leave;
}
tty_printf(_("%s keys may be between %u and %u bits long.\n"),
openpgp_pk_algo_name (algo), min, max);
for (;;)
{
char *prompt, *answer;
if (for_subkey)
prompt = xasprintf (_("What keysize do you want "
"for the subkey? (%u) "), def);
else
prompt = xasprintf (_("What keysize do you want? (%u) "), def);
answer = cpr_get ("keygen.size", prompt);
cpr_kill_prompt ();
nbits = *answer? atoi (answer): def;
xfree(prompt);
xfree(answer);
if(nbits<min || nbits>max)
tty_printf(_("%s keysizes must be in the range %u-%u\n"),
openpgp_pk_algo_name (algo), min, max);
else
break;
}
tty_printf (_("Requested keysize is %u bits\n"), nbits);
leave:
nbits = fixup_keysize (nbits, algo, autocomp);
return nbits;
}
/* Ask for the curve. ALGO is the selected algorithm which this
function may adjust. Returns a const string of the name of the
curve. */
const char *
ask_curve (int *algo, int *subkey_algo, const char *current)
{
/* NB: We always use a complete algo list so that we have stable
numbers in the menu regardless on how Gpg was configured. */
struct {
const char *name;
const char* eddsa_curve; /* Corresponding EdDSA curve. */
const char *pretty_name;
unsigned int supported : 1; /* Supported by gpg. */
unsigned int de_vs : 1; /* Allowed in CO_DE_VS. */
unsigned int expert_only : 1; /* Only with --expert */
unsigned int available : 1; /* Available in Libycrypt (runtime checked) */
} curves[] = {
#if GPG_USE_ECDSA || GPG_USE_ECDH
# define MY_USE_ECDSADH 1
#else
# define MY_USE_ECDSADH 0
#endif
{ "Curve25519", "Ed25519", "Curve 25519", !!GPG_USE_EDDSA, 0, 0, 0 },
{ "X448", "Ed448", "Curve 448", !!GPG_USE_EDDSA, 0, 1, 0 },
{ "NIST P-256", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "NIST P-384", NULL, NULL, MY_USE_ECDSADH, 0, 0, 0 },
{ "NIST P-521", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
{ "brainpoolP256r1", NULL, "Brainpool P-256", MY_USE_ECDSADH, 1, 0, 0 },
{ "brainpoolP384r1", NULL, "Brainpool P-384", MY_USE_ECDSADH, 1, 1, 0 },
{ "brainpoolP512r1", NULL, "Brainpool P-512", MY_USE_ECDSADH, 1, 1, 0 },
{ "secp256k1", NULL, NULL, MY_USE_ECDSADH, 0, 1, 0 },
};
#undef MY_USE_ECDSADH
int idx;
char *answer;
const char *result = NULL;
gcry_sexp_t keyparms;
tty_printf (_("Please select which elliptic curve you want:\n"));
keyparms = NULL;
for (idx=0; idx < DIM(curves); idx++)
{
int rc;
curves[idx].available = 0;
if (!curves[idx].supported)
continue;
if (opt.compliance==CO_DE_VS)
{
if (!curves[idx].de_vs)
continue; /* Not allowed. */
}
else if (!opt.expert && curves[idx].expert_only)
continue;
/* We need to switch from the ECDH name of the curve to the
EDDSA name of the curve if we want a signing key. */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].eddsa_curve? curves[idx].eddsa_curve
/**/ : curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
if (subkey_algo && curves[idx].eddsa_curve)
{
/* Both Curve 25519 (or 448) keys are to be created. Check that
Libgcrypt also supports the real Curve25519 (or 448). */
gcry_sexp_release (keyparms);
rc = gcry_sexp_build (&keyparms, NULL,
"(public-key(ecc(curve %s)))",
curves[idx].name);
if (rc)
continue;
if (!gcry_pk_get_curve (keyparms, 0, NULL))
continue;
}
curves[idx].available = 1;
tty_printf (" (%d) %s%s\n", idx + 1,
curves[idx].pretty_name?
curves[idx].pretty_name:curves[idx].name,
idx == 0? _(" *default*"):"");
}
gcry_sexp_release (keyparms);
for (;;)
{
answer = cpr_get ("keygen.curve", _("Your selection? "));
cpr_kill_prompt ();
idx = *answer? atoi (answer) : 1;
if (!*answer && current)
{
xfree(answer);
return NULL;
}
else if (*answer && !idx)
{
/* See whether the user entered the name of the curve. */
for (idx=0; idx < DIM(curves); idx++)
{
if (!opt.expert && curves[idx].expert_only)
continue;
if (!stricmp (curves[idx].name, answer)
|| (curves[idx].pretty_name
&& !stricmp (curves[idx].pretty_name, answer)))
break;
}
if (idx == DIM(curves))
idx = -1;
}
else
idx--;
xfree(answer);
answer = NULL;
if (idx < 0 || idx >= DIM (curves) || !curves[idx].available)
tty_printf (_("Invalid selection.\n"));
else
{
/* If the user selected a signing algorithm and Curve25519
we need to set the algo to EdDSA and update the curve name.
If switching away from EdDSA, we need to set the algo back
to ECDSA. */
if (*algo == PUBKEY_ALGO_ECDSA || *algo == PUBKEY_ALGO_EDDSA)
{
if (curves[idx].eddsa_curve)
{
if (subkey_algo && *subkey_algo == PUBKEY_ALGO_ECDSA)
*subkey_algo = PUBKEY_ALGO_EDDSA;
*algo = PUBKEY_ALGO_EDDSA;
result = curves[idx].eddsa_curve;
}
else
{
if (subkey_algo && *subkey_algo == PUBKEY_ALGO_EDDSA)
*subkey_algo = PUBKEY_ALGO_ECDSA;
*algo = PUBKEY_ALGO_ECDSA;
result = curves[idx].name;
}
}
else
result = curves[idx].name;
break;
}
}
if (!result)
result = curves[0].name;
return result;
}
/****************
* Parse an expire string and return its value in seconds.
* Returns (u32)-1 on error.
* This isn't perfect since scan_isodatestr returns unix time, and
* OpenPGP actually allows a 32-bit time *plus* a 32-bit offset.
* Because of this, we only permit setting expirations up to 2106, but
* OpenPGP could theoretically allow up to 2242. I think we'll all
* just cope for the next few years until we get a 64-bit time_t or
* similar.
*/
static u32
parse_expire_string_with_ct (const char *string, u32 creation_time)
{
int mult;
u32 seconds;
u32 abs_date = 0;
time_t tt;
uint64_t tmp64;
u32 curtime;
if (creation_time == (u32)-1)
curtime = make_timestamp ();
else
curtime = creation_time;
if (!string || !*string || !strcmp (string, "none")
|| !strcmp (string, "never") || !strcmp (string, "-"))
seconds = 0;
else if (!strncmp (string, "seconds=", 8))
seconds = scan_secondsstr (string+8);
else if ((abs_date = scan_isodatestr(string))
&& (abs_date+86400/2) > curtime)
seconds = (abs_date+86400/2) - curtime;
else if ((tt = isotime2epoch_u64 (string)) != (uint64_t)(-1))
{
tmp64 = tt - curtime;
if (tmp64 >= (u32)(-1))
seconds = (u32)(-1) - 1; /* cap value. */
else
seconds = (u32)tmp64;
}
else if ((mult = check_valid_days (string)))
{
tmp64 = scan_secondsstr (string) * 86400L * mult;
if (tmp64 >= (u32)(-1))
seconds = (u32)(-1) - 1; /* cap value. */
else
seconds = (u32)tmp64;
}
else
seconds = (u32)(-1);
return seconds;
}
u32
parse_expire_string ( const char *string )
{
return parse_expire_string_with_ct (string, (u32)-1);
}
/* Parse a Creation-Date string which is either "1986-04-26" or
"19860426T042640". Returns 0 on error. */
static u32
parse_creation_string (const char *string)
{
u32 seconds;
if (!*string)
seconds = 0;
else if ( !strncmp (string, "seconds=", 8) )
seconds = scan_secondsstr (string+8);
else if ( !(seconds = scan_isodatestr (string)))
{
uint64_t tmp = isotime2epoch_u64 (string);
if (tmp == (uint64_t)(-1))
seconds = 0;
else if (tmp > (u32)(-1))
seconds = 0;
else
seconds = tmp;
}
return seconds;
}
/* object == 0 for a key, and 1 for a sig */
u32
ask_expire_interval(int object,const char *def_expire)
{
u32 interval;
char *answer;
switch(object)
{
case 0:
if(def_expire)
BUG();
tty_printf(_("Please specify how long the key should be valid.\n"
" 0 = key does not expire\n"
" <n> = key expires in n days\n"
" <n>w = key expires in n weeks\n"
" <n>m = key expires in n months\n"
" <n>y = key expires in n years\n"));
break;
case 1:
if(!def_expire)
BUG();
tty_printf(_("Please specify how long the signature should be valid.\n"
" 0 = signature does not expire\n"
" <n> = signature expires in n days\n"
" <n>w = signature expires in n weeks\n"
" <n>m = signature expires in n months\n"
" <n>y = signature expires in n years\n"));
break;
default:
BUG();
}
/* Note: The elgamal subkey for DSA has no expiration date because
* it must be signed with the DSA key and this one has the expiration
* date */
answer = NULL;
for(;;)
{
u32 curtime;
xfree(answer);
if(object==0)
answer = cpr_get("keygen.valid",_("Key is valid for? (0) "));
else
{
char *prompt;
prompt = xasprintf (_("Signature is valid for? (%s) "), def_expire);
answer = cpr_get("siggen.valid",prompt);
xfree(prompt);
if(*answer=='\0')
{
xfree (answer);
answer = xstrdup (def_expire);
}
}
cpr_kill_prompt();
trim_spaces(answer);
curtime = make_timestamp ();
interval = parse_expire_string( answer );
if( interval == (u32)-1 )
{
tty_printf(_("invalid value\n"));
continue;
}
if( !interval )
{
tty_printf((object==0)
? _("Key does not expire at all\n")
: _("Signature does not expire at all\n"));
}
else
{
tty_printf(object==0
? _("Key expires at %s\n")
: _("Signature expires at %s\n"),
asctimestamp((ulong)(curtime + interval) ) );
#if SIZEOF_TIME_T <= 4 && !defined (HAVE_UNSIGNED_TIME_T)
if ( (time_t)((ulong)(curtime+interval)) < 0 )
tty_printf (_("Your system can't display dates beyond 2038.\n"
"However, it will be correctly handled up to"
" 2106.\n"));
else
#endif /*SIZEOF_TIME_T*/
if ( (time_t)((unsigned long)(curtime+interval)) < curtime )
{
tty_printf (_("invalid value\n"));
continue;
}
}
if( cpr_enabled() || cpr_get_answer_is_yes("keygen.valid.okay",
_("Is this correct? (y/N) ")) )
break;
}
xfree(answer);
return interval;
}
u32
ask_expiredate (void)
{
u32 x = ask_expire_interval(0,NULL);
return x? make_timestamp() + x : 0;
}
static PKT_user_id *
uid_from_string (const char *string)
{
size_t n;
PKT_user_id *uid;
n = strlen (string);
uid = xmalloc_clear (sizeof *uid + n);
uid->len = n;
strcpy (uid->name, string);
uid->ref = 1;
return uid;
}
/* Return true if the user id UID already exists in the keyblock. */
static int
uid_already_in_keyblock (kbnode_t keyblock, const char *uid)
{
PKT_user_id *uidpkt = uid_from_string (uid);
kbnode_t node;
int result = 0;
for (node=keyblock; node && !result; node=node->next)
if (!is_deleted_kbnode (node)
&& node->pkt->pkttype == PKT_USER_ID
&& !cmp_user_ids (uidpkt, node->pkt->pkt.user_id))
result = 1;
free_user_id (uidpkt);
return result;
}
/* Ask for a user ID. With a MODE of 1 an extra help prompt is
printed for use during a new key creation. If KEYBLOCK is not NULL
the function prevents the creation of an already existing user
ID. IF FULL is not set some prompts are not shown. */
static char *
ask_user_id (int mode, int full, KBNODE keyblock)
{
char *answer;
char *aname, *acomment, *amail, *uid;
if ( !mode )
{
/* TRANSLATORS: This is the new string telling the user what
gpg is now going to do (i.e. ask for the parts of the user
ID). Note that if you do not translate this string, a
different string will be used, which might still have
a correct translation. */
const char *s1 =
N_("\n"
"GnuPG needs to construct a user ID to identify your key.\n"
"\n");
const char *s2 = _(s1);
if (!strcmp (s1, s2))
{
/* There is no translation for the string thus we to use
the old info text. gettext has no way to tell whether
a translation is actually available, thus we need to
to compare again. */
/* TRANSLATORS: This string is in general not anymore used
but you should keep your existing translation. In case
the new string is not translated this old string will
be used. */
const char *s3 = N_("\n"
"You need a user ID to identify your key; "
"the software constructs the user ID\n"
"from the Real Name, Comment and Email Address in this form:\n"
" \"Heinrich Heine (Der Dichter) <heinrichh@duesseldorf.de>\"\n\n");
const char *s4 = _(s3);
if (strcmp (s3, s4))
s2 = s3; /* A translation exists - use it. */
}
tty_printf ("%s", s2) ;
}
uid = aname = acomment = amail = NULL;
for(;;) {
char *p;
int fail=0;
if( !aname ) {
for(;;) {
xfree(aname);
aname = cpr_get("keygen.name",_("Real name: "));
trim_spaces(aname);
cpr_kill_prompt();
if( opt.allow_freeform_uid )
break;
if( strpbrk( aname, "<>" ) )
{
tty_printf(_("Invalid character in name\n"));
tty_printf(_("The characters '%s' and '%s' may not "
"appear in name\n"), "<", ">");
}
else
break;
}
}
if( !amail ) {
for(;;) {
xfree(amail);
amail = cpr_get("keygen.email",_("Email address: "));
trim_spaces(amail);
cpr_kill_prompt();
if( !*amail || opt.allow_freeform_uid )
break; /* no email address is okay */
else if ( !is_valid_mailbox (amail) )
tty_printf(_("Not a valid email address\n"));
else
break;
}
}
if (!acomment) {
if (full) {
for(;;) {
xfree(acomment);
acomment = cpr_get("keygen.comment",_("Comment: "));
trim_spaces(acomment);
cpr_kill_prompt();
if( !*acomment )
break; /* no comment is okay */
else if( strpbrk( acomment, "()" ) )
tty_printf(_("Invalid character in comment\n"));
else
break;
}
}
else {
xfree (acomment);
acomment = xstrdup ("");
}
}
xfree(uid);
uid = p = xmalloc(strlen(aname)+strlen(amail)+strlen(acomment)+12+10);
if (!*aname && *amail && !*acomment && !random_is_faked ())
{ /* Empty name and comment but with mail address. Use
simplified form with only the non-angle-bracketed mail
address. */
p = stpcpy (p, amail);
}
else
{
p = stpcpy (p, aname );
if (*acomment)
p = stpcpy(stpcpy(stpcpy(p," ("), acomment),")");
if (*amail)
p = stpcpy(stpcpy(stpcpy(p," <"), amail),">");
}
/* Append a warning if the RNG is switched into fake mode. */
if ( random_is_faked () )
strcpy(p, " (insecure!)" );
/* print a note in case that UTF8 mapping has to be done */
for(p=uid; *p; p++ ) {
if( *p & 0x80 ) {
tty_printf(_("You are using the '%s' character set.\n"),
get_native_charset() );
break;
}
}
tty_printf(_("You selected this USER-ID:\n \"%s\"\n\n"), uid);
if( !*amail && !opt.allow_freeform_uid
&& (strchr( aname, '@' ) || strchr( acomment, '@'))) {
fail = 1;
tty_printf(_("Please don't put the email address "
"into the real name or the comment\n") );
}
if (!fail && keyblock)
{
if (uid_already_in_keyblock (keyblock, uid))
{
tty_printf (_("Such a user ID already exists on this key!\n"));
fail = 1;
}
}
for(;;) {
/* TRANSLATORS: These are the allowed answers in
lower and uppercase. Below you will find the matching
string which should be translated accordingly and the
letter changed to match the one in the answer string.
n = Change name
c = Change comment
e = Change email
o = Okay (ready, continue)
q = Quit
*/
const char *ansstr = _("NnCcEeOoQq");
if( strlen(ansstr) != 10 )
BUG();
if( cpr_enabled() ) {
answer = xstrdup (ansstr + (fail?8:6));
answer[1] = 0;
}
else if (full) {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (C)omment, (E)mail or (Q)uit? ") :
_("Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
else {
answer = cpr_get("keygen.userid.cmd", fail?
_("Change (N)ame, (E)mail, or (Q)uit? ") :
_("Change (N)ame, (E)mail, or (O)kay/(Q)uit? "));
cpr_kill_prompt();
}
if( strlen(answer) > 1 )
;
else if( *answer == ansstr[0] || *answer == ansstr[1] ) {
xfree(aname); aname = NULL;
break;
}
else if( *answer == ansstr[2] || *answer == ansstr[3] ) {
xfree(acomment); acomment = NULL;
break;
}
else if( *answer == ansstr[4] || *answer == ansstr[5] ) {
xfree(amail); amail = NULL;
break;
}
else if( *answer == ansstr[6] || *answer == ansstr[7] ) {
if( fail ) {
tty_printf(_("Please correct the error first\n"));
}
else {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
break;
}
}
else if( *answer == ansstr[8] || *answer == ansstr[9] ) {
xfree(aname); aname = NULL;
xfree(acomment); acomment = NULL;
xfree(amail); amail = NULL;
xfree(uid); uid = NULL;
break;
}
xfree(answer);
}
xfree(answer);
if (!amail && !acomment)
break;
xfree(uid); uid = NULL;
}
if( uid ) {
char *p = native_to_utf8( uid );
xfree( uid );
uid = p;
}
return uid;
}
/* Basic key generation. Here we divert to the actual generation
* routines based on the requested algorithm. KEYGEN_FLAGS might be
* updated by this function. */
static int
do_create (int algo, unsigned int nbits, const char *curve, kbnode_t pub_root,
u32 timestamp, u32 expiredate, int is_subkey,
int *keygen_flags, const char *passphrase,
char **cache_nonce_addr, char **passwd_nonce_addr,
gpg_error_t (*common_gen_cb)(common_gen_cb_parm_t),
common_gen_cb_parm_t common_gen_cb_parm)
{
gpg_error_t err;
/* Fixme: The entropy collecting message should be moved to a
libgcrypt progress handler. */
if (!opt.batch)
tty_printf (_(
"We need to generate a lot of random bytes. It is a good idea to perform\n"
"some other action (type on the keyboard, move the mouse, utilize the\n"
"disks) during the prime generation; this gives the random number\n"
"generator a better chance to gain enough entropy.\n") );
if (algo == PUBKEY_ALGO_ELGAMAL_E)
err = gen_elg (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
*keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
else if (algo == PUBKEY_ALGO_DSA)
err = gen_dsa (nbits, pub_root, timestamp, expiredate, is_subkey,
*keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
err = gen_ecc (algo, curve, pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
else if (algo == PUBKEY_ALGO_KYBER)
err = gen_kyber (algo, nbits, curve,
pub_root, timestamp, expiredate, is_subkey,
keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
else if (algo == PUBKEY_ALGO_RSA)
err = gen_rsa (algo, nbits, pub_root, timestamp, expiredate, is_subkey,
*keygen_flags, passphrase,
cache_nonce_addr, passwd_nonce_addr,
common_gen_cb, common_gen_cb_parm);
else
BUG();
return err;
}
/* Generate a new user id packet or return NULL if canceled. If
KEYBLOCK is not NULL the function prevents the creation of an
already existing user ID. If UIDSTR is not NULL the user is not
asked but UIDSTR is used to create the user id packet; if the user
id already exists NULL is returned. UIDSTR is expected to be utf-8
encoded and should have already been checked for a valid length
etc. */
PKT_user_id *
generate_user_id (KBNODE keyblock, const char *uidstr)
{
PKT_user_id *uid;
char *p;
if (uidstr)
{
if (uid_already_in_keyblock (keyblock, uidstr))
return NULL; /* Already exists. */
uid = uid_from_string (uidstr);
}
else
{
p = ask_user_id (1, 1, keyblock);
if (!p)
return NULL; /* Canceled. */
uid = uid_from_string (p);
xfree (p);
}
return uid;
}
/* Helper for parse_key_parameter_part_parameter_string for one part of the
* specification string; i.e. ALGO/FLAGS. If STRING is NULL or empty
* success is returned. On error an error code is returned. Note
* that STRING may be modified by this function. NULL may be passed
* for any parameter. FOR_SUBKEY shall be true if this is used as a
* subkey. If CLEAR_CERT is set a default CERT usage will be cleared;
* this is useful if for example the default algorithm is used for a
* subkey. If R_KEYVERSION is not NULL it will receive the version of
* the key; this is currently 4 but can be changed with the flag "v5"
* to create a v5 key. If R_KEYTIME is not NULL and the key has been
* taken from active OpenPGP card, its creation time is stored
* there. */
static gpg_error_t
parse_key_parameter_part (ctrl_t ctrl,
char *string, int for_subkey, int clear_cert,
int *r_algo, unsigned int *r_size,
unsigned int *r_keyuse,
char const **r_curve, int *r_keyversion,
char **r_keygrip, u32 *r_keytime)
{
gpg_error_t err;
char *flags;
int algo;
char *endp;
const char *curve = NULL;
int ecdh_or_ecdsa = 0;
unsigned int size;
int keyuse;
int keyversion = 0; /* Not specified. */
int i;
const char *s;
int from_card = 0;
char *keygrip = NULL;
u32 keytime = 0;
int is_448 = 0;
int is_pqc = 0;
if (!string || !*string)
return 0; /* Success. */
flags = strchr (string, '/');
if (flags)
*flags++ = 0;
algo = 0;
if (!ascii_strcasecmp (string, "card"))
from_card = 1;
else if (strlen (string) >= 3 && (digitp (string+3) || !string[3]))
{
if (!ascii_memcasecmp (string, "rsa", 3))
algo = PUBKEY_ALGO_RSA;
else if (!ascii_memcasecmp (string, "dsa", 3))
algo = PUBKEY_ALGO_DSA;
else if (!ascii_memcasecmp (string, "elg", 3))
algo = PUBKEY_ALGO_ELGAMAL_E;
}
if (from_card)
; /* We need the flags before we can figure out the key to use. */
else if (algo)
{
/* This is one of the algos parsed above (rsa, dsa, or elg). */
if (!string[3])
size = get_keysize_range (algo, NULL, NULL);
else
{
size = strtoul (string+3, &endp, 10);
if (size < 512 || size > 16384 || *endp)
return gpg_error (GPG_ERR_INV_VALUE);
}
}
else if (!ascii_strcasecmp (string, "kyber")
|| !ascii_strcasecmp (string, "kyber768"))
{
/* Get the curve and check that it can technically be used
* (i.e. everything except the EdXXXX curves. */
curve = openpgp_is_curve_supported ("brainpoolP256r1", &algo, NULL);
if (!curve || algo == PUBKEY_ALGO_EDDSA)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
algo = PUBKEY_ALGO_KYBER;
size = 768;
is_pqc = 1;
}
else if (!ascii_strcasecmp (string, "kyber1024"))
{
/* Get the curve and check that it can technically be used
* (i.e. everything except the EdXXXX curves. */
curve = openpgp_is_curve_supported ("brainpoolP384r1", &algo, NULL);
if (!curve || algo == PUBKEY_ALGO_EDDSA)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
algo = PUBKEY_ALGO_KYBER;
size = 1024;
is_pqc = 1;
}
else if (!ascii_strncasecmp (string, "ky768_", 6)
|| !ascii_strncasecmp (string, "ky1024_", 7)
|| !ascii_strncasecmp (string, "kyber768_", 9)
|| !ascii_strncasecmp (string, "kyber1024_", 10)
)
{
/* Get the curve and check that it can technically be used
* (i.e. everything except the EdXXXX curves. */
s = strchr (string, '_');
log_assert (s);
s++;
curve = openpgp_is_curve_supported (s, &algo, NULL);
if (!curve || algo == PUBKEY_ALGO_EDDSA)
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
algo = PUBKEY_ALGO_KYBER;
size = strstr (string, "768_")? 768 : 1024;
is_pqc = 1;
}
else if (!ascii_strcasecmp (string, "dil3"))
{
algo = PUBKEY_ALGO_DIL3_25519;
is_pqc = 1;
}
else if (!ascii_strcasecmp (string, "dil5"))
{
algo = PUBKEY_ALGO_DIL5_448;
is_pqc = 1;
}
else if (!ascii_strcasecmp (string, "sphinx")
|| !ascii_strcasecmp (string, "sphinx_sha2"))
{
algo = PUBKEY_ALGO_SPHINX_SHA2;
is_pqc = 1;
}
else if ((curve = openpgp_is_curve_supported (string, &algo, &size)))
{
if (!algo)
{
algo = PUBKEY_ALGO_ECDH; /* Default ECC algorithm. */
ecdh_or_ecdsa = 1; /* We may need to switch the algo. */
}
if (curve && (!strcmp (curve, "X448") || !strcmp (curve, "Ed448")))
is_448 = 1;
}
else
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* Parse the flags. */
keyuse = 0;
if (flags)
{
char **tokens = NULL;
tokens = strtokenize (flags, ",");
if (!tokens)
return gpg_error_from_syserror ();
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
keyuse |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
keyuse |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
keyuse |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
keyuse |= PUBKEY_USAGE_CERT;
else if (!ascii_strcasecmp (s, "ecdsa") && !from_card)
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDSA;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "ecdh") && !from_card)
{
if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA)
algo = PUBKEY_ALGO_ECDH;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
ecdh_or_ecdsa = 0;
}
else if (!ascii_strcasecmp (s, "eddsa") && !from_card)
{
/* Not required but we allow it for consistency. */
if (algo == PUBKEY_ALGO_EDDSA)
;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_INV_FLAG);
}
}
else if (!ascii_strcasecmp (s, "v5"))
keyversion = 5;
else if (!ascii_strcasecmp (s, "v4"))
keyversion = 4;
else
{
xfree (tokens);
return gpg_error (GPG_ERR_UNKNOWN_FLAG);
}
}
xfree (tokens);
}
/* If not yet decided switch between ecdh and ecdsa unless we want
* to read the algo from the current card. */
if (from_card)
{
keypair_info_t keypairlist, kpi;
char *reqkeyref;
if (!keyuse)
keyuse = (for_subkey? PUBKEY_USAGE_ENC
/* */ : (PUBKEY_USAGE_CERT|PUBKEY_USAGE_SIG));
/* Access the card to make sure we have one and to show the S/N. */
{
char *serialno;
err = agent_scd_serialno (&serialno, NULL);
if (err)
{
log_error (_("error reading the card: %s\n"), gpg_strerror (err));
return err;
}
if (!opt.quiet)
log_info (_("Serial number of the card: %s\n"), serialno);
xfree (serialno);
}
err = agent_scd_keypairinfo (ctrl, NULL, &keypairlist);
if (err)
{
log_error (_("error reading the card: %s\n"), gpg_strerror (err));
return err;
}
agent_scd_getattr_one ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT))
? "$SIGNKEYID":"$ENCRKEYID", &reqkeyref);
algo = 0; /* Should already be the case. */
for (kpi=keypairlist; kpi && !algo; kpi = kpi->next)
{
gcry_sexp_t s_pkey;
char *algostr = NULL;
enum gcry_pk_algos algoid = 0;
const char *keyref = kpi->idstr;
if (!reqkeyref)
continue; /* Card does not provide the info (skip all). */
if (!keyref)
continue; /* Ooops. */
if (strcmp (reqkeyref, keyref))
continue; /* This is not the requested keyref. */
if ((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_CERT))
&& (kpi->usage & (GCRY_PK_USAGE_SIGN|GCRY_PK_USAGE_CERT)))
; /* Okay */
else if ((keyuse & PUBKEY_USAGE_ENC)
&& (kpi->usage & GCRY_PK_USAGE_ENCR))
; /* Okay */
else
continue; /* Not usable for us. */
if (agent_scd_readkey (ctrl, keyref, &s_pkey, NULL))
continue; /* Could not read the key. */
algostr = pubkey_algo_string (s_pkey, &algoid);
gcry_sexp_release (s_pkey);
/* Map to OpenPGP algo number.
* We need to tweak the algo in case GCRY_PK_ECC is
* returned because pubkey_algo_string is not aware
* of the OpenPGP algo mapping. We need to
* distinguish between ECDH and ECDSA but we can do
* that only if we got usage flags.
* Note: Keep this in sync with ask_algo. */
if (algoid == GCRY_PK_ECC && algostr)
{
if (!strcmp (algostr, "ed25519"))
algo = PUBKEY_ALGO_EDDSA;
else if (!strcmp (algostr, "ed448"))
{
algo = PUBKEY_ALGO_EDDSA;
is_448 = 1;
}
else if (!strcmp (algostr, "cv25519"))
algo = PUBKEY_ALGO_ECDH;
else if (!strcmp (algostr, "cv448"))
{
algo = PUBKEY_ALGO_ECDH;
is_448 = 1;
}
else if ((kpi->usage & GCRY_PK_USAGE_ENCR))
algo = PUBKEY_ALGO_ECDH;
else
algo = PUBKEY_ALGO_ECDSA;
}
else
algo = map_gcry_pk_to_openpgp (algoid);
xfree (algostr);
xfree (keygrip);
keygrip = xtrystrdup (kpi->keygrip);
if (!keygrip)
{
err = gpg_error_from_syserror ();
xfree (reqkeyref);
free_keypair_info (keypairlist);
return err;
}
keytime = kpi->keytime;
}
xfree (reqkeyref);
free_keypair_info (keypairlist);
if (!algo || !keygrip)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("no usable key on the card: %s\n", gpg_strerror (err));
xfree (keygrip);
return err;
}
}
else if (ecdh_or_ecdsa && keyuse)
algo = (keyuse & PUBKEY_USAGE_ENC)? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
else if (ecdh_or_ecdsa)
algo = for_subkey? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
/* Set or fix key usage. */
if (!keyuse)
{
if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
keyuse = PUBKEY_USAGE_SIG;
else if (algo == PUBKEY_ALGO_RSA)
keyuse = for_subkey? PUBKEY_USAGE_ENC : PUBKEY_USAGE_SIG;
else
keyuse = PUBKEY_USAGE_ENC;
}
else if (algo == PUBKEY_ALGO_ECDSA || algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_DSA)
{
keyuse &= ~PUBKEY_USAGE_ENC; /* Forbid encryption. */
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ELGAMAL_E)
{
keyuse = PUBKEY_USAGE_ENC; /* Allow only encryption. */
}
/* Make sure a primary key can certify. */
if (!for_subkey)
keyuse |= PUBKEY_USAGE_CERT;
/* But if requested remove th cert usage. */
if (clear_cert)
keyuse &= ~PUBKEY_USAGE_CERT;
/* Check that usage is actually possible. */
if (/**/((keyuse & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((keyuse & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (keyuse & PUBKEY_USAGE_CERT)))
{
xfree (keygrip);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
/* Ed448, X448 and the PQC algos must only be used as v5 keys. */
if (is_448 || is_pqc)
{
if (keyversion == 4)
log_info (_("WARNING: v4 is specified, but overridden by v5.\n"));
keyversion = 5;
}
else if (keyversion == 0)
keyversion = 4;
/* Return values. */
if (r_algo)
*r_algo = algo;
if (r_size)
{
unsigned int min, def, max;
/* Make sure the keysize is in the allowed range. */
def = get_keysize_range (algo, &min, &max);
if (!size)
size = def;
else if (size < min)
size = min;
else if (size > max)
size = max;
*r_size = fixup_keysize (size, algo, 1);
}
if (r_keyuse)
*r_keyuse = keyuse;
if (r_curve)
*r_curve = curve;
if (r_keyversion)
*r_keyversion = keyversion;
if (r_keygrip)
*r_keygrip = keygrip;
else
xfree (keygrip);
if (r_keytime)
*r_keytime = keytime;
return 0;
}
/* Parse and return the standard key generation parameter.
* The string is expected to be in this format:
*
* ALGO[/FLAGS][+SUBALGO[/FLAGS]]
*
* Here ALGO is a string in the same format as printed by the
* keylisting. For example:
*
* rsa3072 := RSA with 3072 bit.
* dsa2048 := DSA with 2048 bit.
* elg2048 := Elgamal with 2048 bit.
* ed25519 := EDDSA using curve Ed25519.
* ed448 := EDDSA using curve Ed448.
* cv25519 := ECDH using curve Curve25519.
* cv448 := ECDH using curve X448.
* nistp256:= ECDSA or ECDH using curve NIST P-256
* kyber := Kyber with the default parameters
* ky768_bp384 := Kyber-768 with BrainpoolP256r1 as second algo
*
* All strings with an unknown prefix are considered an elliptic
* curve. Curves which have no implicit algorithm require that FLAGS
* is given to select whether ECDSA or ECDH is used; this can either
* be done using an algorithm keyword or usage keywords.
*
* FLAGS is a comma delimited string of keywords:
*
* cert := Allow usage Certify
* sign := Allow usage Sign
* encr := Allow usage Encrypt
* auth := Allow usage Authentication
* encrypt := Alias for "encr"
* ecdsa := Use algorithm ECDSA.
* eddsa := Use algorithm EdDSA.
* ecdh := Use algorithm ECDH.
* v5 := Create version 5 key
*
* There are several defaults and fallbacks depending on the
* algorithm. PART can be used to select which part of STRING is
* used:
* -1 := Both parts
* 0 := Only the part of the primary key
* 1 := If there is one part parse that one, if there are
* two parts parse the part which best matches the
* SUGGESTED_USE or in case that can't be evaluated the second part.
* Always return using the args for the primary key (R_ALGO,....).
*
*/
gpg_error_t
parse_key_parameter_string (ctrl_t ctrl,
const char *string, int part,
unsigned int suggested_use,
int *r_algo, unsigned int *r_size,
unsigned int *r_keyuse,
char const **r_curve,
int *r_version,
char **r_keygrip,
u32 *r_keytime,
int *r_subalgo, unsigned int *r_subsize,
unsigned int *r_subkeyuse,
char const **r_subcurve,
int *r_subversion,
char **r_subkeygrip,
u32 *r_subkeytime)
{
gpg_error_t err = 0;
char *primary, *secondary;
if (r_algo)
*r_algo = 0;
if (r_size)
*r_size = 0;
if (r_keyuse)
*r_keyuse = 0;
if (r_curve)
*r_curve = NULL;
if (r_version)
*r_version = 4;
if (r_keygrip)
*r_keygrip = NULL;
if (r_keytime)
*r_keytime = 0;
if (r_subalgo)
*r_subalgo = 0;
if (r_subsize)
*r_subsize = 0;
if (r_subkeyuse)
*r_subkeyuse = 0;
if (r_subcurve)
*r_subcurve = NULL;
if (r_subversion)
*r_subversion = 4;
if (r_subkeygrip)
*r_subkeygrip = NULL;
if (r_subkeytime)
*r_subkeytime = 0;
if (!string || !*string
|| !ascii_strcasecmp (string, "default") || !strcmp (string, "-"))
string = get_default_pubkey_algo ();
else if (!ascii_strcasecmp (string, "future-default")
|| !ascii_strcasecmp (string, "futuredefault"))
string = FUTURE_STD_KEY_PARAM;
else if (!ascii_strcasecmp (string, "card"))
string = "card/cert,sign+card/encr";
primary = xstrdup (string);
secondary = strchr (primary, '+');
if (secondary)
*secondary++ = 0;
if (part == -1 || part == 0)
{
err = parse_key_parameter_part (ctrl, primary,
0, 0, r_algo, r_size,
r_keyuse, r_curve, r_version,
r_keygrip, r_keytime);
if (!err && part == -1)
err = parse_key_parameter_part (ctrl, secondary,
1, 0, r_subalgo, r_subsize,
r_subkeyuse, r_subcurve, r_subversion,
r_subkeygrip, r_subkeytime);
}
else if (part == 1)
{
/* If we have SECONDARY, use that part. If there is only one
* part consider this to be the subkey algo. In case a
* SUGGESTED_USE has been given and the usage of the secondary
* part does not match SUGGESTED_USE try again using the primary
* part. Note that when falling back to the primary key we need
* to force clearing the cert usage. */
if (secondary)
{
err = parse_key_parameter_part (ctrl, secondary,
1, 0,
r_algo, r_size, r_keyuse, r_curve,
r_version, r_keygrip, r_keytime);
if (!err && suggested_use && r_keyuse && !(suggested_use & *r_keyuse))
err = parse_key_parameter_part (ctrl, primary,
1, 1 /*(clear cert)*/,
r_algo, r_size, r_keyuse, r_curve,
r_version, r_keygrip, r_keytime);
}
else
err = parse_key_parameter_part (ctrl, primary,
1, 0,
r_algo, r_size, r_keyuse, r_curve,
r_version, r_keygrip, r_keytime);
}
xfree (primary);
return err;
}
/* Append R to the linked list PARA. */
static void
append_to_parameter (struct para_data_s *para, struct para_data_s *r)
{
log_assert (para);
while (para->next)
para = para->next;
para->next = r;
}
/* Release the parameter list R. */
static void
release_parameter_list (struct para_data_s *r)
{
struct para_data_s *r2;
for (; r ; r = r2)
{
r2 = r->next;
if (r->key == pPASSPHRASE && *r->u.value)
wipememory (r->u.value, strlen (r->u.value));
xfree (r);
}
}
/* Return the N-th parameter of name KEY from PARA. An IDX of 0
* returns the first and so on. */
static struct para_data_s *
get_parameter_idx (struct para_data_s *para, enum para_name key,
unsigned int idx)
{
struct para_data_s *r;
for(r = para; r; r = r->next)
if (r->key == key)
{
if (!idx)
return r;
idx--;
}
return NULL;
}
/* Return the first parameter of name KEY from PARA. */
static struct para_data_s *
get_parameter (struct para_data_s *para, enum para_name key)
{
return get_parameter_idx (para, key, 0);
}
static const char *
get_parameter_value( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
return (r && *r->u.value)? r->u.value : NULL;
}
/* This is similar to get_parameter_value but also returns the empty
string. This is required so that quick_generate_keypair can use an
empty Passphrase to specify no-protection. */
static const char *
get_parameter_passphrase (struct para_data_s *para)
{
struct para_data_s *r = get_parameter (para, pPASSPHRASE);
return r ? r->u.value : NULL;
}
static int
get_parameter_algo (ctrl_t ctrl, struct para_data_s *para, enum para_name key,
int *r_default)
{
int i;
struct para_data_s *r = get_parameter( para, key );
if (r_default)
*r_default = 0;
if (!r)
return -1;
/* Note that we need to handle the ECC algorithms specified as
strings directly because Libgcrypt folds them all to ECC. */
if (!ascii_strcasecmp (r->u.value, "default"))
{
/* Note: If you change this default algo, remember to change it
* also in gpg.c:gpgconf_list. */
/* FIXME: We only allow the algo here and have a separate thing
* for the curve etc. That is a ugly but demanded for backward
* compatibility with the batch key generation. It would be
* better to make full use of parse_key_parameter_string. */
parse_key_parameter_string (ctrl, NULL, 0, 0,
&i, NULL, NULL, NULL, NULL, NULL, NULL,
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (r_default)
*r_default = 1;
}
else if (digitp (r->u.value))
i = atoi( r->u.value );
else if (!strcmp (r->u.value, "ELG-E")
|| !strcmp (r->u.value, "ELG"))
i = PUBKEY_ALGO_ELGAMAL_E;
else if (!ascii_strcasecmp (r->u.value, "EdDSA"))
i = PUBKEY_ALGO_EDDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDSA"))
i = PUBKEY_ALGO_ECDSA;
else if (!ascii_strcasecmp (r->u.value, "ECDH"))
i = PUBKEY_ALGO_ECDH;
else
i = map_gcry_pk_to_openpgp (gcry_pk_map_name (r->u.value));
if (i == PUBKEY_ALGO_RSA_E || i == PUBKEY_ALGO_RSA_S)
i = 0; /* we don't want to allow generation of these algorithms */
return i;
}
/* Parse a usage string. The usage keywords "auth", "sign", "encr"
* may be delimited by space, tab, or comma. On error -1 is returned
* instead of the usage flags. */
static int
parse_usagestr (const char *usagestr)
{
gpg_error_t err;
char **tokens = NULL;
const char *s;
int i;
unsigned int use = 0;
tokens = strtokenize (usagestr, " \t,");
if (!tokens)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
return -1;
}
for (i=0; (s = tokens[i]); i++)
{
if (!*s)
;
else if (!ascii_strcasecmp (s, "sign"))
use |= PUBKEY_USAGE_SIG;
else if (!ascii_strcasecmp (s, "encrypt")
|| !ascii_strcasecmp (s, "encr"))
use |= PUBKEY_USAGE_ENC;
else if (!ascii_strcasecmp (s, "auth"))
use |= PUBKEY_USAGE_AUTH;
else if (!ascii_strcasecmp (s, "cert"))
use |= PUBKEY_USAGE_CERT;
else if (!ascii_strcasecmp (s, "renc"))
use |= PUBKEY_USAGE_RENC;
else if (!ascii_strcasecmp (s, "time"))
use |= PUBKEY_USAGE_TIME;
else if (!ascii_strcasecmp (s, "group"))
use |= PUBKEY_USAGE_GROUP;
else
{
xfree (tokens);
return -1; /* error */
}
}
xfree (tokens);
return use;
}
/*
* Parse the usage parameter and set the keyflags. Returns -1 on
* error, 0 for no usage given or 1 for usage available.
*/
static int
parse_parameter_usage (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
int i;
if (!r)
return 0; /* none (this is an optional parameter)*/
i = parse_usagestr (r->u.value);
if (i == -1)
{
log_error ("%s:%d: invalid usage list\n", fname, r->lnr );
return -1; /* error */
}
r->u.usage = i;
return 1;
}
/* Parse the revocation key specified by NAME, check that the public
* key exists (so that we can get the required public key algorithm),
- * and return a parameter wit the revocation key information. On
+ * and return a parameter with the revocation key information. On
* error print a diagnostic and return NULL. */
static struct para_data_s *
prepare_desig_revoker (ctrl_t ctrl, const char *name)
{
gpg_error_t err;
struct para_data_s *para = NULL;
KEYDB_SEARCH_DESC desc;
int sensitive = 0;
struct revocation_key revkey;
PKT_public_key *revoker_pk = NULL;
size_t fprlen;
if (!ascii_strncasecmp (name, "sensitive:", 10) && !spacep (name+10))
{
name += 10;
sensitive = 1;
}
if (classify_user_id (name, &desc, 1)
|| desc.mode != KEYDB_SEARCH_MODE_FPR)
{
log_info (_("\"%s\" is not a fingerprint\n"), name);
err = gpg_error (GPG_ERR_INV_NAME);
goto leave;
}
revoker_pk = xcalloc (1, sizeof *revoker_pk);
revoker_pk->req_usage = PUBKEY_USAGE_CERT;
err = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, revoker_pk, name, NULL, NULL, 1);
if (err)
goto leave;
fingerprint_from_pk (revoker_pk, revkey.fpr, &fprlen);
if (fprlen != 20 && fprlen != 32)
{
log_info (_("cannot appoint a PGP 2.x style key as a "
"designated revoker\n"));
err = gpg_error (GPG_ERR_UNUSABLE_PUBKEY);
goto leave;
}
revkey.fprlen = fprlen;
revkey.class = 0x80;
if (sensitive)
revkey.class |= 0x40;
revkey.algid = revoker_pk->pubkey_algo;
para = xcalloc (1, sizeof *para);
para->key = pREVOKER;
memcpy (&para->u.revkey, &revkey, sizeof revkey);
leave:
if (err)
log_error ("invalid revocation key '%s': %s\n", name, gpg_strerror (err));
free_public_key (revoker_pk);
return para;
}
/* Parse a pREVOKER parameter into its dedicated parts. */
static int
parse_revocation_key (const char *fname,
struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter( para, key );
struct revocation_key revkey;
char *pn;
int i;
if( !r )
return 0; /* none (this is an optional parameter) */
pn = r->u.value;
revkey.class=0x80;
revkey.algid=atoi(pn);
if(!revkey.algid)
goto fail;
/* Skip to the fpr */
while(*pn && *pn!=':')
pn++;
if(*pn!=':')
goto fail;
pn++;
for(i=0;i<MAX_FINGERPRINT_LEN && *pn && !spacep (pn);i++,pn+=2)
{
int c=hextobyte(pn);
if(c==-1)
goto fail;
revkey.fpr[i]=c;
}
if (i != 20 && i != 32)
goto fail;
revkey.fprlen = i;
/* skip to the tag */
while(*pn && *pn!='s' && *pn!='S')
pn++;
if(ascii_strcasecmp(pn,"sensitive")==0)
revkey.class|=0x40;
memcpy(&r->u.revkey,&revkey,sizeof(struct revocation_key));
return 0;
fail:
log_error("%s:%d: invalid revocation key\n", fname, r->lnr );
return -1; /* error */
}
static u32
get_parameter_u32( struct para_data_s *para, enum para_name key )
{
struct para_data_s *r = get_parameter( para, key );
if( !r )
return 0;
if (r->key == pKEYCREATIONDATE || r->key == pSUBKEYCREATIONDATE
|| r->key == pAUTHKEYCREATIONDATE)
return r->u.creation;
if( r->key == pKEYEXPIRE || r->key == pSUBKEYEXPIRE )
return r->u.expire;
if( r->key == pKEYUSAGE || r->key == pSUBKEYUSAGE )
return r->u.usage;
return (unsigned int)strtoul( r->u.value, NULL, 10 );
}
static unsigned int
get_parameter_uint( struct para_data_s *para, enum para_name key )
{
return get_parameter_u32( para, key );
}
static struct revocation_key *
get_parameter_revkey (struct para_data_s *para, enum para_name key,
unsigned int idx)
{
struct para_data_s *r = get_parameter_idx (para, key, idx);
return r? &r->u.revkey : NULL;
}
static int
get_parameter_bool (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key);
return (r && r->u.abool);
}
static int
proc_parameter_file (ctrl_t ctrl, struct para_data_s *para, const char *fname,
struct output_control_s *outctrl, int card )
{
struct para_data_s *r;
const char *s1, *s2, *s3;
size_t n;
char *p;
strlist_t sl;
int is_default = 0;
int have_user_id = 0;
int err, algo;
u32 creation_time = (u32)-1;
/* Check that we have all required parameters. */
r = get_parameter( para, pKEYTYPE );
if(r)
{
algo = get_parameter_algo (ctrl, para, pKEYTYPE, &is_default);
if (openpgp_pk_test_algo2 (algo, PUBKEY_USAGE_SIG))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
}
else
{
log_error ("%s: no Key-Type specified\n",fname);
return -1;
}
err = parse_parameter_usage (fname, para, pKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if key-usage is not provided and
no default algorithm has been requested. */
r = xmalloc_clear(sizeof(*r));
r->key = pKEYUSAGE;
r->u.usage = (is_default
? (PUBKEY_USAGE_CERT | PUBKEY_USAGE_SIG)
: openpgp_pk_algo_usage(algo));
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Key-Usage not allowed for algo %d\n",
fname, r->lnr, algo);
return -1;
}
}
is_default = 0;
r = get_parameter( para, pSUBKEYTYPE );
if(r)
{
algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, &is_default);
if (openpgp_pk_test_algo (algo))
{
log_error ("%s:%d: invalid algorithm\n", fname, r->lnr );
return -1;
}
err = parse_parameter_usage (fname, para, pSUBKEYUSAGE);
if (!err)
{
/* Default to algo capabilities if subkey-usage is not
provided. Take care not to include RENC. */
r = xmalloc_clear (sizeof(*r));
r->key = pSUBKEYUSAGE;
r->u.usage = (is_default
? PUBKEY_USAGE_ENC
: (openpgp_pk_algo_usage (algo)
& ~PUBKEY_USAGE_RENC) );
append_to_parameter (para, r);
}
else if (err == -1)
return -1;
else
{
r = get_parameter (para, pSUBKEYUSAGE);
if (r && (r->u.usage & ~openpgp_pk_algo_usage (algo)))
{
log_error ("%s:%d: specified Subkey-Usage not allowed"
" for algo %d\n", fname, r->lnr, algo);
return -1;
}
}
}
if( get_parameter_value( para, pUSERID ) )
have_user_id=1;
else
{
/* create the formatted user ID */
s1 = get_parameter_value( para, pNAMEREAL );
s2 = get_parameter_value( para, pNAMECOMMENT );
s3 = get_parameter_value( para, pNAMEEMAIL );
if( s1 || s2 || s3 )
{
n = (s1?strlen(s1):0) + (s2?strlen(s2):0) + (s3?strlen(s3):0);
r = xmalloc_clear( sizeof *r + n + 20 );
r->key = pUSERID;
p = r->u.value;
if( s1 )
p = stpcpy(p, s1 );
if( s2 )
p = stpcpy(stpcpy(stpcpy(p," ("), s2 ),")");
if( s3 )
{
/* If we have only the email part, do not add the space
* and the angle brackets. */
if (*r->u.value)
p = stpcpy(stpcpy(stpcpy(p," <"), s3 ),">");
else
p = stpcpy (p, s3);
}
append_to_parameter (para, r);
have_user_id=1;
}
}
if(!have_user_id)
{
log_error("%s: no User-ID specified\n",fname);
return -1;
}
/* Set preferences, if any. */
keygen_set_std_prefs(get_parameter_value( para, pPREFERENCES ), 0);
/* Set keyserver, if any. */
s1=get_parameter_value( para, pKEYSERVER );
if(s1)
{
struct keyserver_spec *spec;
spec = parse_keyserver_uri (s1, 1);
if(spec)
{
free_keyserver_spec(spec);
opt.def_keyserver_url=s1;
}
else
{
r = get_parameter (para, pKEYSERVER);
log_error("%s:%d: invalid keyserver url\n", fname, r->lnr );
return -1;
}
}
/* Set revoker from parameter file, if any. Must be done first so
* that we don't find a parameter set via prepare_desig_revoker. */
if (parse_revocation_key (fname, para, pREVOKER))
return -1;
- /* Check and appened revokers from the config file. */
+ /* Check and append revokers from the config file. */
for (sl = opt.desig_revokers; sl; sl = sl->next)
{
r = prepare_desig_revoker (ctrl, sl->d);
if (!r)
return -1;
append_to_parameter (para, r);
}
/* Make KEYCREATIONDATE from Creation-Date. We ignore this if the
* key has been taken from a card and a keycreationtime has already
* been set. This is so that we don't generate a key with a
* fingerprint different from the one stored on the OpenPGP card. */
r = get_parameter (para, pCREATIONDATE);
if (r && *r->u.value && !(get_parameter_bool (para, pCARDKEY)
&& get_parameter_u32 (para, pKEYCREATIONDATE)))
{
creation_time = parse_creation_string (r->u.value);
if (!creation_time)
{
log_error ("%s:%d: invalid creation date\n", fname, r->lnr );
return -1;
}
r->u.creation = creation_time;
r->key = pKEYCREATIONDATE; /* Change that entry. */
}
/* Make KEYEXPIRE from Expire-Date. */
r = get_parameter( para, pEXPIREDATE );
if( r && *r->u.value )
{
u32 seconds;
seconds = parse_expire_string_with_ct (r->u.value, creation_time);
if( seconds == (u32)-1 )
{
log_error("%s:%d: invalid expire date\n", fname, r->lnr );
return -1;
}
r->u.expire = seconds;
r->key = pKEYEXPIRE; /* change that entry */
/* Make SUBKEYEXPIRE from Subkey-Expire-Date, if any. */
r = get_parameter( para, pSUBKEYEXPIREDATE );
if( r && *r->u.value )
{
seconds = parse_expire_string_with_ct (r->u.value, creation_time);
if( seconds == (u32)-1 )
{
log_error("%s:%d: invalid subkey expire date\n", fname, r->lnr );
return -1;
}
r->key = pSUBKEYEXPIRE; /* change that entry */
r->u.expire = seconds;
}
else
{
/* Or else, set Expire-Date for the subkey */
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYEXPIRE;
r->u.expire = seconds;
append_to_parameter (para, r);
}
}
do_generate_keypair (ctrl, para, outctrl, card );
return 0;
}
/****************
* Kludge to allow non interactive key generation controlled
* by a parameter file.
* Note, that string parameters are expected to be in UTF-8
*/
static void
read_parameter_file (ctrl_t ctrl, const char *fname )
{
static struct { const char *name;
enum para_name key;
} keywords[] = {
{ "Key-Type", pKEYTYPE},
{ "Key-Length", pKEYLENGTH },
{ "Key-Curve", pKEYCURVE },
{ "Key-Usage", pKEYUSAGE },
{ "Subkey-Type", pSUBKEYTYPE },
{ "Subkey-Length", pSUBKEYLENGTH },
{ "Subkey-Curve", pSUBKEYCURVE },
{ "Subkey-Usage", pSUBKEYUSAGE },
{ "Name-Real", pNAMEREAL },
{ "Name-Email", pNAMEEMAIL },
{ "Name-Comment", pNAMECOMMENT },
{ "Expire-Date", pEXPIREDATE },
{ "Subkey-Expire-Date", pSUBKEYEXPIREDATE },
{ "Creation-Date", pCREATIONDATE },
{ "Passphrase", pPASSPHRASE },
{ "Preferences", pPREFERENCES },
{ "Revoker", pREVOKER },
{ "Handle", pHANDLE },
{ "Keyserver", pKEYSERVER },
{ "Keygrip", pKEYGRIP },
{ "Key-Grip", pKEYGRIP },
{ "Subkey-grip", pSUBKEYGRIP },
{ "Key-Version", pVERSION },
{ "Subkey-Version", pSUBVERSION },
{ NULL, 0 }
};
IOBUF fp;
byte *line;
unsigned int maxlen, nline;
char *p;
int lnr;
const char *err = NULL;
struct para_data_s *para, *r;
int i;
struct output_control_s outctrl;
memset( &outctrl, 0, sizeof( outctrl ) );
outctrl.pub.afx = new_armor_context ();
if( !fname || !*fname)
fname = "-";
fp = iobuf_open (fname);
if (fp && is_secured_file (iobuf_get_fd (fp)))
{
iobuf_close (fp);
fp = NULL;
gpg_err_set_errno (EPERM);
}
if (!fp) {
log_error (_("can't open '%s': %s\n"), fname, strerror(errno) );
return;
}
iobuf_ioctl (fp, IOBUF_IOCTL_NO_CACHE, 1, NULL);
lnr = 0;
err = NULL;
para = NULL;
maxlen = 1024;
line = NULL;
nline = 0;
while ( iobuf_read_line (fp, &line, &nline, &maxlen) ) {
char *keyword, *value;
lnr++;
if( !maxlen ) {
err = "line too long";
break;
}
for( p = line; isspace(*(byte*)p); p++ )
;
if( !*p || *p == '#' )
continue;
keyword = p;
if( *keyword == '%' ) {
for( ; !isspace(*(byte*)p); p++ )
;
if( *p )
*p++ = 0;
for( ; isspace(*(byte*)p); p++ )
;
value = p;
trim_trailing_ws( value, strlen(value) );
if( !ascii_strcasecmp( keyword, "%echo" ) )
log_info("%s\n", value );
else if( !ascii_strcasecmp( keyword, "%dry-run" ) )
outctrl.dryrun = 1;
else if( !ascii_strcasecmp( keyword, "%ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-ask-passphrase" ) )
; /* Dummy for backward compatibility. */
else if( !ascii_strcasecmp( keyword, "%no-protection" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_NO_PROTECTION;
else if( !ascii_strcasecmp( keyword, "%transient-key" ) )
outctrl.keygen_flags |= KEYGEN_FLAG_TRANSIENT_KEY;
else if( !ascii_strcasecmp( keyword, "%commit" ) ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else if( !ascii_strcasecmp( keyword, "%pubring" ) ) {
if( outctrl.pub.fname && !strcmp( outctrl.pub.fname, value ) )
; /* still the same file - ignore it */
else {
xfree( outctrl.pub.newfname );
outctrl.pub.newfname = xstrdup( value );
outctrl.use_files = 1;
}
}
else if( !ascii_strcasecmp( keyword, "%secring" ) ) {
/* Ignore this command. */
}
else
log_info("skipping control '%s' (%s)\n", keyword, value );
continue;
}
if( !(p = strchr( p, ':' )) || p == keyword ) {
err = "missing colon";
break;
}
if( *p )
*p++ = 0;
for( ; isspace(*(byte*)p); p++ )
;
if( !*p ) {
err = "missing argument";
break;
}
value = p;
trim_trailing_ws( value, strlen(value) );
for(i=0; keywords[i].name; i++ ) {
if( !ascii_strcasecmp( keywords[i].name, keyword ) )
break;
}
if( !keywords[i].name ) {
err = "unknown keyword";
break;
}
if( keywords[i].key != pKEYTYPE && !para ) {
err = "parameter block does not start with \"Key-Type\"";
break;
}
if( keywords[i].key == pKEYTYPE && para ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created
(get_parameter_value (para, pHANDLE));
release_parameter_list( para );
para = NULL;
}
else {
for( r = para; r; r = r->next ) {
if( r->key == keywords[i].key )
break;
}
if( r ) {
err = "duplicate keyword";
break;
}
}
if ((keywords[i].key == pVERSION
|| keywords[i].key == pSUBVERSION))
; /* Ignore version. */
else
{
r = xmalloc_clear( sizeof *r + strlen( value ) );
r->lnr = lnr;
r->key = keywords[i].key;
strcpy( r->u.value, value );
r->next = para;
para = r;
}
}
if( err )
log_error("%s:%d: %s\n", fname, lnr, err );
else if( iobuf_error (fp) ) {
log_error("%s:%d: read error\n", fname, lnr);
}
else if( para ) {
outctrl.lnr = lnr;
if (proc_parameter_file (ctrl, para, fname, &outctrl, 0 ))
print_status_key_not_created (get_parameter_value (para, pHANDLE));
}
if( outctrl.use_files ) { /* close open streams */
iobuf_close( outctrl.pub.stream );
/* Must invalidate that ugly cache to actually close it. */
if (outctrl.pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl.pub.fname);
xfree( outctrl.pub.fname );
xfree( outctrl.pub.newfname );
}
xfree (line);
release_parameter_list( para );
iobuf_close (fp);
release_armor_context (outctrl.pub.afx);
}
/* Helper for quick_generate_keypair. */
static struct para_data_s *
quickgen_set_para (struct para_data_s *para, int for_subkey,
int algo, int nbits, const char *curve, unsigned int use,
int version, const char *keygrip, u32 keytime)
{
struct para_data_s *r;
r = xmalloc_clear (sizeof *r + 50);
r->key = for_subkey? pSUBKEYUSAGE : pKEYUSAGE;
if (use)
snprintf (r->u.value, 30, "%s%s%s%s%s%s%s",
(use & PUBKEY_USAGE_ENC)? "encr " : "",
(use & PUBKEY_USAGE_SIG)? "sign " : "",
(use & PUBKEY_USAGE_AUTH)? "auth " : "",
(use & PUBKEY_USAGE_CERT)? "cert " : "",
(use & PUBKEY_USAGE_RENC)? "renc " : "",
(use & PUBKEY_USAGE_TIME)? "time " : "",
(use & PUBKEY_USAGE_GROUP)?"group ": "");
else
strcpy (r->u.value, for_subkey ? "encr" : "sign");
r->next = para;
para = r;
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYTYPE : pKEYTYPE;
snprintf (r->u.value, 20, "%d", algo);
r->next = para;
para = r;
if (keygrip)
{
r = xmalloc_clear (sizeof *r + strlen (keygrip));
r->key = for_subkey? pSUBKEYGRIP : pKEYGRIP;
strcpy (r->u.value, keygrip);
r->next = para;
para = r;
}
else if (curve)
{
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = for_subkey? pSUBKEYCURVE : pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
else
{
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBKEYLENGTH : pKEYLENGTH;
sprintf (r->u.value, "%u", nbits);
r->next = para;
para = r;
}
r = xmalloc_clear (sizeof *r + 20);
r->key = for_subkey? pSUBVERSION : pVERSION;
snprintf (r->u.value, 20, "%d", version);
r->next = para;
para = r;
if (keytime)
{
r = xmalloc_clear (sizeof *r);
r->key = for_subkey? pSUBKEYCREATIONDATE : pKEYCREATIONDATE;
r->u.creation = keytime;
r->next = para;
para = r;
}
return para;
}
/*
* Unattended generation of a standard key.
*/
void
quick_generate_keypair (ctrl_t ctrl, const char *uid, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
int use_tty;
memset (&outctrl, 0, sizeof outctrl);
use_tty = (!opt.batch && !opt.answer_yes
&& !*algostr && !*usagestr && !*expirestr
&& !cpr_enabled ()
&& gnupg_isatty (fileno (stdin))
&& gnupg_isatty (fileno (stdout))
&& gnupg_isatty (fileno (stderr)));
r = xmalloc_clear (sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
uid = trim_spaces (r->u.value);
if (!*uid || (!opt.allow_freeform_uid && !is_valid_user_id (uid)))
{
log_error (_("Key generation failed: %s\n"),
gpg_strerror (GPG_ERR_INV_USER_ID));
goto leave;
}
/* If gpg is directly used on the console ask whether a key with the
given user id shall really be created. */
if (use_tty)
{
tty_printf (_("About to create a key for:\n \"%s\"\n\n"), uid);
if (!cpr_get_answer_is_yes_def ("quick_keygen.okay",
_("Continue? (Y/n) "), 1))
goto leave;
}
/* Check whether such a user ID already exists. */
{
KEYDB_HANDLE kdbhd;
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_EXACT;
desc.u.name = uid;
kdbhd = keydb_new (ctrl);
if (!kdbhd)
goto leave;
err = keydb_search (kdbhd, &desc, 1, NULL);
keydb_release (kdbhd);
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_info (_("A key for \"%s\" already exists\n"), uid);
if (opt.answer_yes)
;
else if (!use_tty
|| !cpr_get_answer_is_yes_def ("quick_keygen.force",
_("Create anyway? (y/N) "), 0))
{
write_status_error ("genkey", gpg_error (304));
log_inc_errorcount (); /* we used log_info */
goto leave;
}
log_info (_("creating anyway\n"));
}
}
if (!*expirestr || strcmp (expirestr, "-") == 0)
expirestr = default_expiration_interval;
if ((!*algostr || !ascii_strcasecmp (algostr, "default")
|| !ascii_strcasecmp (algostr, "future-default")
|| !ascii_strcasecmp (algostr, "futuredefault")
|| !ascii_strcasecmp (algostr, "card"))
&& (!*usagestr || !ascii_strcasecmp (usagestr, "default")
|| !strcmp (usagestr, "-")))
{
/* Use default key parameters. */
int algo, subalgo, version, subversion;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
char *keygrip, *subkeygrip;
u32 keytime, subkeytime;
err = parse_key_parameter_string (ctrl, algostr, -1, 0,
&algo, &size, &keyuse, &curve, &version,
&keygrip, &keytime,
&subalgo, &subsize, &subkeyuse,
&subcurve, &subversion,
&subkeygrip, &subkeytime);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
para = quickgen_set_para (para, 0, algo, size, curve, keyuse, version,
keygrip, keytime);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse,
subversion, subkeygrip, subkeytime);
if (*expirestr)
{
u32 expire;
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
goto leave;
}
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
}
xfree (keygrip);
xfree (subkeygrip);
}
else
{
/* Extended unattended mode. Creates only the primary key. */
int algo, version;
unsigned int use;
u32 expire;
unsigned int nbits;
const char *curve;
char *keygrip;
u32 keytime;
err = parse_algo_usage_expire (ctrl, 0, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve,
&version, &keygrip, &keytime);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
goto leave;
}
para = quickgen_set_para (para, 0, algo, nbits, curve, use, version,
keygrip, keytime);
r = xmalloc_clear (sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
xfree (keygrip);
}
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new key. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
{
const char *s = get_static_passphrase ();
r = xmalloc_clear (sizeof *r + strlen (s));
r->key = pPASSPHRASE;
strcpy (r->u.value, s);
r->next = para;
para = r;
}
if (!ascii_strcasecmp (algostr, "card")
|| !ascii_strncasecmp (algostr, "card/", 5))
{
r = xmalloc_clear (sizeof *r);
r->key = pCARDKEY;
r->u.abool = 1;
r->next = para;
para = r;
}
proc_parameter_file (ctrl, para, "[internal]", &outctrl, 0);
leave:
release_parameter_list (para);
}
/*
* Generate a keypair (fname is only used in batch mode) If
* CARD_SERIALNO is not NULL the function will create the keys on an
* OpenPGP Card. If CARD_BACKUP_KEY has been set and CARD_SERIALNO is
* NOT NULL, the encryption key for the card is generated on the host,
* imported to the card and a backup file created by gpg-agent. If
* FULL is not set only the basic prompts are used (except for batch
* mode).
*/
void
generate_keypair (ctrl_t ctrl, int full, const char *fname,
const char *card_serialno, int card_backup_key)
{
gpg_error_t err;
unsigned int nbits;
char *uid = NULL;
int algo;
unsigned int use;
int both = 0;
u32 expire;
struct para_data_s *para = NULL;
struct para_data_s *r;
struct output_control_s outctrl;
#ifndef ENABLE_CARD_SUPPORT
(void)card_backup_key;
#endif
memset( &outctrl, 0, sizeof( outctrl ) );
if (opt.batch && card_serialno)
{
/* We don't yet support unattended key generation with a card
* serial number. */
log_error (_("can't do this in batch mode\n"));
print_further_info ("key generation with card serial number");
return;
}
if (opt.batch)
{
read_parameter_file (ctrl, fname);
return;
}
if (card_serialno)
{
#ifdef ENABLE_CARD_SUPPORT
struct agent_card_info_s info;
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"),
gpg_strerror (err));
return;
}
r = xcalloc (1, sizeof *r + strlen (card_serialno) );
r->key = pSERIALNO;
strcpy( r->u.value, card_serialno);
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[0].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy (r->u.value, "sign");
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[1].algo );
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy (r->u.value, "encrypt");
r->next = para;
para = r;
if (info.key_attr[1].algo == PUBKEY_ALGO_RSA)
{
r = xcalloc (1, sizeof *r + 20 );
r->key = pSUBKEYLENGTH;
sprintf( r->u.value, "%u", info.key_attr[1].nbits);
r->next = para;
para = r;
}
else if (info.key_attr[1].algo == PUBKEY_ALGO_ECDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_EDDSA
|| info.key_attr[1].algo == PUBKEY_ALGO_ECDH)
{
r = xcalloc (1, sizeof *r + strlen (info.key_attr[1].curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, info.key_attr[1].curve);
r->next = para;
para = r;
}
r = xcalloc (1, sizeof *r + 20 );
r->key = pAUTHKEYTYPE;
sprintf( r->u.value, "%d", info.key_attr[2].algo );
r->next = para;
para = r;
if (card_backup_key)
{
r = xcalloc (1, sizeof *r + 1);
r->key = pCARDBACKUPKEY;
strcpy (r->u.value, "1");
r->next = para;
para = r;
}
#endif /*ENABLE_CARD_SUPPORT*/
}
else if (full) /* Full featured key generation. */
{
int subkey_algo;
char *key_from_hexgrip = NULL;
int cardkey;
u32 keytime;
algo = ask_algo (ctrl, 0, &subkey_algo, &use,
&key_from_hexgrip, &cardkey, &keytime);
if (key_from_hexgrip)
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 40 );
r->key = pKEYGRIP;
strcpy (r->u.value, key_from_hexgrip);
r->next = para;
para = r;
r = xmalloc_clear (sizeof *r);
r->key = pCARDKEY;
r->u.abool = cardkey;
r->next = para;
para = r;
if (cardkey)
{
r = xmalloc_clear (sizeof *r);
r->key = pKEYCREATIONDATE;
r->u.creation = keytime;
r->next = para;
para = r;
}
xfree (key_from_hexgrip);
}
else
{
const char *curve = NULL;
if (subkey_algo)
{
/* Create primary and subkey at once. */
both = 1;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, &subkey_algo, NULL);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = 0;
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
if (!strcmp (curve, "X448") || !strcmp (curve, "Ed448"))
{
r = xmalloc_clear (sizeof *r + 20);
r->key = pVERSION;
snprintf (r->u.value, 20, "%d", 5);
r->next = para;
para = r;
}
}
else
{
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo);
r->next = para;
para = r;
nbits = ask_keysize (algo, 0);
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYUSAGE;
strcpy( r->u.value, "sign" );
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYTYPE;
sprintf( r->u.value, "%d", subkey_algo);
r->next = para;
para = r;
r = xmalloc_clear( sizeof *r + 20 );
r->key = pSUBKEYUSAGE;
strcpy( r->u.value, "encrypt" );
r->next = para;
para = r;
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
if (algo == PUBKEY_ALGO_EDDSA
&& subkey_algo == PUBKEY_ALGO_ECDH)
{
/* Need to switch to a different curve for the
encryption key. */
if (!strcmp (curve, "Ed25519"))
curve = "Curve25519";
else
{
curve = "X448";
r = xmalloc_clear (sizeof *r + 20);
r->key = pSUBVERSION;
snprintf (r->u.value, 20, "%d", 5);
r->next = para;
para = r;
}
}
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pSUBKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
}
}
else /* Create only a single key. */
{
/* For ECC we need to ask for the curve before storing the
algo because ask_curve may change the algo. */
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, NULL, NULL);
r = xmalloc_clear (sizeof *r + strlen (curve));
r->key = pKEYCURVE;
strcpy (r->u.value, curve);
r->next = para;
para = r;
if (!strcmp (curve, "X448") || !strcmp (curve, "Ed448"))
{
r = xmalloc_clear (sizeof *r + 20);
r->key = pVERSION;
snprintf (r->u.value, 20, "%d", 5);
r->next = para;
para = r;
}
}
r = xmalloc_clear( sizeof *r + 20 );
r->key = pKEYTYPE;
sprintf( r->u.value, "%d", algo );
r->next = para;
para = r;
if (use)
{
r = xmalloc_clear( sizeof *r + 25 );
r->key = pKEYUSAGE;
sprintf( r->u.value, "%s%s%s",
(use & PUBKEY_USAGE_SIG)? "sign ":"",
(use & PUBKEY_USAGE_ENC)? "encrypt ":"",
(use & PUBKEY_USAGE_AUTH)? "auth":"" );
r->next = para;
para = r;
}
nbits = 0;
}
if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
/* The curve has already been set. */
}
else
{
nbits = ask_keysize (both? subkey_algo : algo, nbits);
r = xmalloc_clear( sizeof *r + 20 );
r->key = both? pSUBKEYLENGTH : pKEYLENGTH;
sprintf( r->u.value, "%u", nbits);
r->next = para;
para = r;
}
}
}
else /* Default key generation. */
{
int subalgo, version, subversion;
unsigned int size, subsize;
unsigned int keyuse, subkeyuse;
const char *curve, *subcurve;
char *keygrip, *subkeygrip;
u32 keytime, subkeytime;
tty_printf ( _("Note: Use \"%s %s\""
" for a full featured key generation dialog.\n"),
#if USE_GPG2_HACK
GPG_NAME "2"
#else
GPG_NAME
#endif
, "--full-generate-key" );
err = parse_key_parameter_string (ctrl, NULL, -1, 0,
&algo, &size, &keyuse, &curve, &version,
&keygrip, &keytime,
&subalgo, &subsize,
&subkeyuse, &subcurve, &subversion,
&subkeygrip, &subkeytime);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err));
return;
}
para = quickgen_set_para (para, 0,
algo, size, curve, keyuse,
version, keygrip, keytime);
if (subalgo)
para = quickgen_set_para (para, 1,
subalgo, subsize, subcurve, subkeyuse,
subversion, subkeygrip, subkeytime);
xfree (keygrip);
xfree (subkeygrip);
}
expire = full? ask_expire_interval (0, NULL)
: parse_expire_string (default_expiration_interval);
r = xcalloc (1, sizeof *r + 20);
r->key = pKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
r = xcalloc (1, sizeof *r + 20);
r->key = pSUBKEYEXPIRE;
r->u.expire = expire;
r->next = para;
para = r;
uid = ask_user_id (0, full, NULL);
if (!uid)
{
log_error(_("Key generation canceled.\n"));
release_parameter_list( para );
return;
}
r = xcalloc (1, sizeof *r + strlen (uid));
r->key = pUSERID;
strcpy (r->u.value, uid);
r->next = para;
para = r;
proc_parameter_file (ctrl, para, "[internal]", &outctrl, !!card_serialno);
release_parameter_list (para);
}
/* Create and delete a dummy packet to start off a list of kbnodes. */
static void
start_tree(KBNODE *tree)
{
PACKET *pkt;
pkt=xmalloc_clear(sizeof(*pkt));
pkt->pkttype=PKT_NONE;
*tree=new_kbnode(pkt);
delete_kbnode(*tree);
}
/* Write the *protected* secret key to the file. */
static gpg_error_t
card_write_key_to_backup_file (PKT_public_key *sk, const char *backup_dir)
{
gpg_error_t err = 0;
int rc;
char keyid_buffer[2 * 8 + 1];
char name_buffer[50];
char *fname;
IOBUF fp;
mode_t oldmask;
PACKET *pkt = NULL;
format_keyid (pk_keyid (sk), KF_LONG, keyid_buffer, sizeof (keyid_buffer));
snprintf (name_buffer, sizeof name_buffer, "sk_%s.gpg", keyid_buffer);
fname = make_filename (backup_dir, name_buffer, NULL);
/* Note that the umask call is not anymore needed because
iobuf_create now takes care of it. However, it does not harm
and thus we keep it. */
oldmask = umask (077);
if (is_secured_filename (fname))
{
fp = NULL;
gpg_err_set_errno (EPERM);
}
else
fp = iobuf_create (fname, 1);
umask (oldmask);
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create backup file '%s': %s\n"), fname, strerror (errno) );
goto leave;
}
pkt = xcalloc (1, sizeof *pkt);
pkt->pkttype = PKT_SECRET_KEY;
pkt->pkt.secret_key = sk;
rc = build_packet (fp, pkt);
if (rc)
{
log_error ("build packet failed: %s\n", gpg_strerror (rc));
iobuf_cancel (fp);
}
else
{
char *fprbuf;
iobuf_close (fp);
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE, 0, (char*)fname);
log_info (_("Note: backup of card key saved to '%s'\n"), fname);
fprbuf = hexfingerprint (sk, NULL, 0);
if (!fprbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
write_status_text_and_buffer (STATUS_BACKUP_KEY_CREATED, fprbuf,
fname, strlen (fname), 0);
xfree (fprbuf);
}
leave:
xfree (pkt);
xfree (fname);
return err;
}
/* Store key to card and make a backup file in OpenPGP format. */
static gpg_error_t
card_store_key_with_backup (ctrl_t ctrl, PKT_public_key *sub_psk,
const char *backup_dir)
{
gpg_error_t err;
PKT_public_key *sk;
gnupg_isotime_t timestamp;
char *hexgrip = NULL;
struct agent_card_info_s info;
gcry_cipher_hd_t cipherhd = NULL;
char *cache_nonce = NULL;
void *kek = NULL;
size_t keklen;
char *ecdh_param_str = NULL;
memset (&info, 0, sizeof (info));
sk = copy_public_key (NULL, sub_psk);
if (!sk)
{
err = gpg_error_from_syserror ();
goto leave;
}
epoch2isotime (timestamp, (time_t)sk->timestamp);
if (sk->pubkey_algo == PUBKEY_ALGO_ECDH)
{
ecdh_param_str = ecdh_param_str_from_pk (sk);
if (!ecdh_param_str)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = hexkeygrip_from_pk (sk, &hexgrip);
if (err)
goto leave;
err = agent_scd_getattr ("SERIALNO", &info);
if (err)
goto leave;
err = agent_keytocard (hexgrip, 2, 1, info.serialno,
timestamp, ecdh_param_str);
if (err)
goto leave;
err = agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
{
log_error ("error setting up an encryption context: %s\n",
gpg_strerror (err));
goto leave;
}
err = receive_seckey_from_agent (ctrl, cipherhd, 0, 0,
&cache_nonce, hexgrip, sk, NULL);
if (err)
{
log_error ("error getting secret key from agent: %s\n",
gpg_strerror (err));
goto leave;
}
err = card_write_key_to_backup_file (sk, backup_dir);
if (err)
log_error ("writing card key to backup file: %s\n", gpg_strerror (err));
else
{
/* Remove secret key data in agent side. */
agent_scd_learn (NULL, 1);
}
leave:
xfree (info.serialno);
xfree (ecdh_param_str);
xfree (cache_nonce);
gcry_cipher_close (cipherhd);
xfree (kek);
xfree (hexgrip);
free_public_key (sk);
return err;
}
static void
do_generate_keypair (ctrl_t ctrl, struct para_data_s *para,
struct output_control_s *outctrl, int card)
{
gpg_error_t err;
KBNODE pub_root = NULL;
const char *s;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
struct revocation_key *revkey;
int did_sub = 0;
u32 keytimestamp, subkeytimestamp, authkeytimestamp, signtimestamp;
char *cache_nonce = NULL;
int algo;
u32 expire;
const char *key_from_hexgrip = NULL;
int cardkey;
unsigned int keygen_flags;
unsigned int idx;
if (outctrl->dryrun)
{
log_info("dry-run mode - key generation skipped\n");
return;
}
if ( outctrl->use_files )
{
if ( outctrl->pub.newfname )
{
iobuf_close(outctrl->pub.stream);
outctrl->pub.stream = NULL;
if (outctrl->pub.fname)
iobuf_ioctl (NULL, IOBUF_IOCTL_INVALIDATE_CACHE,
0, (char*)outctrl->pub.fname);
xfree( outctrl->pub.fname );
outctrl->pub.fname = outctrl->pub.newfname;
outctrl->pub.newfname = NULL;
if (is_secured_filename (outctrl->pub.fname) )
{
outctrl->pub.stream = NULL;
gpg_err_set_errno (EPERM);
}
else
outctrl->pub.stream = iobuf_create (outctrl->pub.fname, 0);
if (!outctrl->pub.stream)
{
log_error(_("can't create '%s': %s\n"), outctrl->pub.newfname,
strerror(errno) );
return;
}
if (opt.armor)
{
outctrl->pub.afx->what = 1;
push_armor_filter (outctrl->pub.afx, outctrl->pub.stream);
}
}
log_assert( outctrl->pub.stream );
if (opt.verbose)
log_info (_("writing public key to '%s'\n"), outctrl->pub.fname );
}
/* We create the packets as a tree of kbnodes. Because the
structure we create is known in advance we simply generate a
linked list. The first packet is a dummy packet which we flag as
deleted. The very first packet must always be a KEY packet. */
start_tree (&pub_root);
cardkey = get_parameter_bool (para, pCARDKEY);
/* In the case that the keys are created from the card we need to
* take the timestamps from the card. Only in this case a
* pSUBKEYCREATIONDATE or pAUTHKEYCREATIONDATE might be defined and
* then we need to use that so that the fingerprint of the subkey
* also matches the pre-computed and stored one on the card. In
* this case we also use the current time to create the
* self-signatures. */
keytimestamp = get_parameter_u32 (para, pKEYCREATIONDATE);
if (!keytimestamp)
keytimestamp = make_timestamp ();
subkeytimestamp = cardkey? get_parameter_u32 (para, pSUBKEYCREATIONDATE) : 0;
if (!subkeytimestamp)
subkeytimestamp = keytimestamp;
authkeytimestamp = cardkey? get_parameter_u32 (para, pAUTHKEYCREATIONDATE): 0;
if (!authkeytimestamp)
authkeytimestamp = keytimestamp;
signtimestamp = cardkey? make_timestamp () : keytimestamp;
/* log_debug ("XXX: cardkey ..: %d\n", cardkey); */
/* log_debug ("XXX: keytime ..: %s\n", isotimestamp (keytimestamp)); */
/* log_debug ("XXX: subkeytime: %s\n", isotimestamp (subkeytimestamp)); */
/* log_debug ("XXX: authkeytim: %s\n", isotimestamp (authkeytimestamp)); */
/* log_debug ("XXX: signtime .: %s\n", isotimestamp (signtimestamp)); */
/* Fixme: Check that this comment is still valid:
Note that, depending on the backend (i.e. the used scdaemon
version), the card key generation may update TIMESTAMP for each
key. Thus we need to pass TIMESTAMP to all signing function to
make sure that the binding signature is done using the timestamp
of the corresponding (sub)key and not that of the primary key.
An alternative implementation could tell the signing function the
node of the subkey but that is more work than just to pass the
current timestamp. */
algo = get_parameter_algo (ctrl, para, pKEYTYPE, NULL );
expire = get_parameter_u32( para, pKEYEXPIRE );
key_from_hexgrip = get_parameter_value (para, pKEYGRIP);
if (cardkey && !key_from_hexgrip)
BUG ();
keygen_flags = outctrl->keygen_flags;
if (get_parameter_uint (para, pVERSION) == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
if (key_from_hexgrip)
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, cardkey,
pub_root,
keytimestamp,
expire, 0, &keygen_flags);
else if (!card)
err = do_create (algo,
get_parameter_uint( para, pKEYLENGTH ),
get_parameter_value (para, pKEYCURVE),
pub_root,
keytimestamp,
expire, 0,
&keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL,
NULL, NULL);
else
err = gen_card_key (1, algo,
1, pub_root, &keytimestamp,
expire, &keygen_flags);
/* Get the pointer to the generated public key packet. */
if (!err)
{
pri_psk = pub_root->next->pkt->pkt.public_key;
log_assert (pri_psk);
/* Make sure a few fields are correctly set up before going
further. */
pri_psk->flags.primary = 1;
keyid_from_pk (pri_psk, NULL);
/* We don't use pk_keyid to get keyid, because it also asserts
that main_keyid is set! */
keyid_copy (pri_psk->main_keyid, pri_psk->keyid);
}
/* Write all signatures specifying designated revokers. */
for (idx=0;
!err && (revkey = get_parameter_revkey (para, pREVOKER, idx));
idx++)
err = write_direct_sig (ctrl, pub_root, pri_psk,
revkey, signtimestamp, cache_nonce);
if (!err && (s = get_parameter_value (para, pUSERID)))
{
err = write_uid (pub_root, s );
if (!err)
err = write_selfsigs (ctrl, pub_root, pri_psk,
get_parameter_uint (para, pKEYUSAGE),
signtimestamp, cache_nonce);
}
/* Write the auth key to the card before the encryption key. This
is a partial workaround for a PGP bug (as of this writing, all
versions including 8.1), that causes it to try and encrypt to
the most recent subkey regardless of whether that subkey is
actually an encryption type. In this case, the auth key is an
RSA key so it succeeds. */
if (!err && card && get_parameter (para, pAUTHKEYTYPE))
{
err = gen_card_key (3, get_parameter_algo (ctrl, para,
pAUTHKEYTYPE, NULL ),
0, pub_root, &authkeytimestamp, expire,
&keygen_flags);
if (!err)
err = write_keybinding (ctrl, pub_root, pri_psk, NULL,
PUBKEY_USAGE_AUTH, signtimestamp, cache_nonce);
}
if (!err && get_parameter (para, pSUBKEYTYPE))
{
int subkey_algo = get_parameter_algo (ctrl, para, pSUBKEYTYPE, NULL);
key_from_hexgrip = get_parameter_value (para, pSUBKEYGRIP);
keygen_flags = outctrl->keygen_flags;
if (get_parameter_uint (para, pSUBVERSION) == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
if (key_from_hexgrip)
err = do_create_from_keygrip (ctrl, subkey_algo,
key_from_hexgrip, cardkey,
pub_root, subkeytimestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE),
1, &keygen_flags);
else if (get_parameter_value (para, pCARDBACKUPKEY))
{
int lastmode;
unsigned int mykeygenflags = KEYGEN_FLAG_NO_PROTECTION;
err = agent_set_ephemeral_mode (ctrl, 1, &lastmode);
if (err)
log_error ("error switching to ephemeral mode: %s\n",
gpg_strerror (err));
else
{
err = do_create (subkey_algo,
get_parameter_uint (para, pSUBKEYLENGTH),
get_parameter_value (para, pSUBKEYCURVE),
pub_root,
subkeytimestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE), 1,
&mykeygenflags,
get_parameter_passphrase (para),
&cache_nonce, NULL,
NULL, NULL);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
kbnode_t node;
for (node = pub_root; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
log_assert (sub_psk);
err = card_store_key_with_backup (ctrl,
sub_psk, gnupg_homedir ());
}
/* Reset the ephemeral mode as needed. */
if (!lastmode && agent_set_ephemeral_mode (ctrl, 0, NULL))
log_error ("error clearing the ephemeral mode\n");
}
}
else if (!card)
{
err = do_create (subkey_algo,
get_parameter_uint (para, pSUBKEYLENGTH),
get_parameter_value (para, pSUBKEYCURVE),
pub_root,
subkeytimestamp,
get_parameter_u32 (para, pSUBKEYEXPIRE), 1,
&keygen_flags,
get_parameter_passphrase (para),
&cache_nonce, NULL,
NULL, NULL);
if (!err)
{
kbnode_t node;
for (node = pub_root; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
log_assert (sub_psk);
}
}
else
{
err = gen_card_key (2, subkey_algo, 0, pub_root,
&subkeytimestamp, expire, &keygen_flags);
}
if (!err)
err = write_keybinding (ctrl, pub_root, pri_psk, sub_psk,
get_parameter_uint (para, pSUBKEYUSAGE),
signtimestamp, cache_nonce);
did_sub = 1;
}
if (!err && outctrl->use_files) /* Direct write to specified files. */
{
err = write_keyblock (outctrl->pub.stream, pub_root);
if (err)
log_error ("can't write public key: %s\n", gpg_strerror (err));
}
else if (!err) /* Write to the standard keyrings. */
{
KEYDB_HANDLE pub_hd;
pub_hd = keydb_new (ctrl);
if (!pub_hd)
err = gpg_error_from_syserror ();
else
{
err = keydb_locate_writable (pub_hd);
if (err)
log_error (_("no writable public keyring found: %s\n"),
gpg_strerror (err));
}
if (!err && opt.verbose)
{
log_info (_("writing public key to '%s'\n"),
keydb_get_resource_name (pub_hd));
}
if (!err)
{
err = keydb_insert_keyblock (pub_hd, pub_root);
if (err)
log_error (_("error writing public keyring '%s': %s\n"),
keydb_get_resource_name (pub_hd), gpg_strerror (err));
}
keydb_release (pub_hd);
if (!err)
{
int no_enc_rsa;
PKT_public_key *pk;
no_enc_rsa = ((get_parameter_algo (ctrl, para, pKEYTYPE, NULL)
== PUBKEY_ALGO_RSA)
&& get_parameter_uint (para, pKEYUSAGE)
&& !((get_parameter_uint (para, pKEYUSAGE)
& PUBKEY_USAGE_ENC)) );
pk = find_kbnode (pub_root, PKT_PUBLIC_KEY)->pkt->pkt.public_key;
if (!opt.flags.no_auto_trust_new_key)
update_ownertrust (ctrl, pk,
((get_ownertrust (ctrl, pk) & ~TRUST_MASK)
| TRUST_ULTIMATE ));
gen_standard_revoke (ctrl, pk, cache_nonce);
/* Get rid of the first empty packet. */
commit_kbnode (&pub_root);
if (!opt.batch)
{
tty_printf (_("public and secret key created and signed.\n") );
tty_printf ("\n");
merge_keys_and_selfsig (ctrl, pub_root);
list_keyblock_direct (ctrl, pub_root, 0, 1,
opt.fingerprint || opt.with_fingerprint,
1);
/* Note that we ignore errors from the list function
* because that would only be an additional info. It
* has already been remarked that the key has been
* created. */
}
if (!opt.batch
&& (get_parameter_algo (ctrl, para,
pKEYTYPE, NULL) == PUBKEY_ALGO_DSA
|| no_enc_rsa )
&& !get_parameter (para, pSUBKEYTYPE) )
{
tty_printf(_("Note that this key cannot be used for "
"encryption. You may want to use\n"
"the command \"--edit-key\" to generate a "
"subkey for this purpose.\n") );
}
}
}
if (err)
{
if (opt.batch)
log_error ("key generation failed: %s\n", gpg_strerror (err) );
else
tty_printf (_("Key generation failed: %s\n"), gpg_strerror (err) );
write_status_error (card? "card_key_generate":"key_generate", err);
print_status_key_not_created ( get_parameter_value (para, pHANDLE) );
}
else
{
PKT_public_key *pk = find_kbnode (pub_root,
PKT_PUBLIC_KEY)->pkt->pkt.public_key;
print_status_key_created (did_sub? 'B':'P', pk,
get_parameter_value (para, pHANDLE));
}
release_kbnode (pub_root);
xfree (cache_nonce);
}
static gpg_error_t
parse_algo_usage_expire (ctrl_t ctrl, int for_subkey,
const char *algostr, const char *usagestr,
const char *expirestr,
int *r_algo, unsigned int *r_usage, u32 *r_expire,
unsigned int *r_nbits, const char **r_curve,
int *r_version, char **r_keygrip, u32 *r_keytime)
{
gpg_error_t err;
int algo;
unsigned int use, nbits;
u32 expire;
int wantuse;
int version = 4;
const char *curve = NULL;
*r_curve = NULL;
if (r_keygrip)
*r_keygrip = NULL;
if (r_keytime)
*r_keytime = 0;
nbits = 0;
/* Parse the algo string. */
if (algostr && *algostr == '&' && strlen (algostr) == 41)
{
/* Take algo from existing key. */
algo = check_keygrip (ctrl, algostr+1);
/* FIXME: We need the curve name as well. */
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
err = parse_key_parameter_string (ctrl, algostr, for_subkey? 1 : 0,
usagestr? parse_usagestr (usagestr):0,
&algo, &nbits, &use, &curve, &version,
r_keygrip, r_keytime,
NULL, NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return err;
}
/* Parse the usage string. */
if (!usagestr || !*usagestr
|| !ascii_strcasecmp (usagestr, "default") || !strcmp (usagestr, "-"))
; /* Keep usage from parse_key_parameter_string. */
else if ((wantuse = parse_usagestr (usagestr)) != -1)
use = wantuse;
else
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return gpg_error (GPG_ERR_INV_VALUE);
}
/* Make sure a primary key has the CERT usage. */
if (!for_subkey)
use |= PUBKEY_USAGE_CERT;
/* Check that usage is possible. NB: We have the same check in
* parse_key_parameter_string but need it here again in case the
* separate usage value has been given. */
if (/**/((use & (PUBKEY_USAGE_SIG|PUBKEY_USAGE_AUTH|PUBKEY_USAGE_CERT))
&& !pubkey_get_nsig (algo))
|| ((use & PUBKEY_USAGE_ENC)
&& !pubkey_get_nenc (algo))
|| (for_subkey && (use & PUBKEY_USAGE_CERT)))
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
/* Parse the expire string. */
expire = parse_expire_string (expirestr);
if (expire == (u32)-1 )
{
if (r_keygrip)
{
xfree (*r_keygrip);
*r_keygrip = NULL;
}
return gpg_error (GPG_ERR_INV_VALUE);
}
if (curve)
*r_curve = curve;
*r_algo = algo;
*r_usage = use;
*r_expire = expire;
*r_nbits = nbits;
*r_version = version;
return 0;
}
/* Add a new subkey to an existing key. Returns 0 if a new key has
been generated and put into the keyblocks. If any of ALGOSTR,
USAGESTR, or EXPIRESTR is NULL interactive mode is used. */
gpg_error_t
generate_subkeypair (ctrl_t ctrl, kbnode_t keyblock, const char *algostr,
const char *usagestr, const char *expirestr)
{
gpg_error_t err = 0;
int interactive;
kbnode_t node;
PKT_public_key *pri_psk = NULL;
PKT_public_key *sub_psk = NULL;
int algo;
unsigned int use;
u32 expire;
unsigned int nbits = 0;
const char *curve = NULL;
u32 cur_time;
char *key_from_hexgrip = NULL;
u32 keytime = 0;
int cardkey = 0;
char *hexgrip = NULL;
char *serialno = NULL;
char *cache_nonce = NULL;
char *passwd_nonce = NULL;
int keygen_flags = 0;
interactive = (!algostr || !usagestr || !expirestr);
/* Break out the primary key. */
node = find_kbnode (keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; primary key missing in keyblock!\n");
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
pri_psk = node->pkt->pkt.public_key;
cur_time = make_timestamp ();
if (pri_psk->timestamp > cur_time)
{
ulong d = pri_psk->timestamp - cur_time;
log_info ( d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_psk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
err = hexkeygrip_from_pk (pri_psk, &hexgrip);
if (err)
goto leave;
/* FIXME: Right now the primary key won't be a dual key. But this
* will change */
if (agent_get_keyinfo (NULL, hexgrip, &serialno, NULL))
{
if (interactive)
tty_printf (_("Secret parts of primary key are not available.\n"));
else
log_info ( _("Secret parts of primary key are not available.\n"));
err = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
if (serialno)
{
if (interactive)
tty_printf (_("Secret parts of primary key are stored on-card.\n"));
else
log_info ( _("Secret parts of primary key are stored on-card.\n"));
}
if (interactive)
{
algo = ask_algo (ctrl, 1, NULL, &use, &key_from_hexgrip, &cardkey,
&keytime);
log_assert (algo);
if (key_from_hexgrip)
nbits = 0;
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH)
{
curve = ask_curve (&algo, NULL, NULL);
if (curve && (!strcmp (curve, "X448") || !strcmp (curve, "Ed448")))
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
}
else
nbits = ask_keysize (algo, 0);
expire = ask_expire_interval (0, NULL);
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.sub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else /* Unattended mode. */
{
int version;
err = parse_algo_usage_expire (ctrl, 1, algostr, usagestr, expirestr,
&algo, &use, &expire, &nbits, &curve,
&version, &key_from_hexgrip, &keytime);
if (err)
goto leave;
if (version == 5)
keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
}
/* Verify the passphrase now so that we get a cache item for the
* primary key passphrase. The agent also returns a passphrase
* nonce, which we can use to set the passphrase for the subkey to
* that of the primary key. */
{
char *desc = gpg_format_keydesc (ctrl, pri_psk, FORMAT_KEYDESC_NORMAL, 1);
err = agent_passwd (ctrl, hexgrip, desc, 1 /*=verify*/,
&cache_nonce, &passwd_nonce);
xfree (desc);
if (gpg_err_code (err) == GPG_ERR_NOT_IMPLEMENTED
&& gpg_err_source (err) == GPG_ERR_SOURCE_GPGAGENT)
err = 0; /* Very likely that the key is on a card. */
if (err)
goto leave;
}
/* Start creation. */
if (key_from_hexgrip)
{
err = do_create_from_keygrip (ctrl, algo, key_from_hexgrip, cardkey,
keyblock,
keytime? keytime : cur_time,
expire, 1,
&keygen_flags);
}
else
{
const char *passwd;
/* If the pinentry loopback mode is not and we have a static
passphrase (i.e. set with --passphrase{,-fd,-file} while in batch
mode), we use that passphrase for the new subkey. */
if (opt.pinentry_mode != PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
passwd = get_static_passphrase ();
else
passwd = NULL;
err = do_create (algo, nbits, curve,
keyblock, cur_time, expire, 1, &keygen_flags,
passwd, &cache_nonce, &passwd_nonce, NULL, NULL);
}
if (err)
goto leave;
/* Get the pointer to the generated public subkey packet. */
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_psk = node->pkt->pkt.public_key;
/* Write the binding signature. */
err = write_keybinding (ctrl, keyblock, pri_psk, sub_psk, use, cur_time,
cache_nonce);
if (err)
goto leave;
print_status_key_created ('S', sub_psk, NULL);
leave:
xfree (key_from_hexgrip);
xfree (hexgrip);
xfree (serialno);
xfree (cache_nonce);
xfree (passwd_nonce);
if (err)
{
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
write_status_error (cardkey? "card_key_generate":"key_generate", err);
print_status_key_not_created ( NULL );
}
return err;
}
#ifdef ENABLE_CARD_SUPPORT
/* Generate a subkey on a card. */
gpg_error_t
generate_card_subkeypair (ctrl_t ctrl, kbnode_t pub_keyblock,
int keyno, const char *serialno)
{
gpg_error_t err = 0;
kbnode_t node;
PKT_public_key *pri_pk = NULL;
unsigned int use;
u32 expire;
u32 cur_time;
struct para_data_s *para = NULL;
PKT_public_key *sub_pk = NULL;
int algo;
struct agent_card_info_s info;
int keygen_flags = 0; /* FIXME!!! */
log_assert (keyno >= 1 && keyno <= 3);
memset (&info, 0, sizeof (info));
err = agent_scd_getattr ("KEY-ATTR", &info);
if (err)
{
log_error (_("error getting current key info: %s\n"), gpg_strerror (err));
return err;
}
algo = info.key_attr[keyno-1].algo;
para = xtrycalloc (1, sizeof *para + strlen (serialno) );
if (!para)
{
err = gpg_error_from_syserror ();
goto leave;
}
para->key = pSERIALNO;
strcpy (para->u.value, serialno);
/* Break out the primary secret key */
node = find_kbnode (pub_keyblock, PKT_PUBLIC_KEY);
if (!node)
{
log_error ("Oops; public key lost!\n");
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
pri_pk = node->pkt->pkt.public_key;
cur_time = make_timestamp();
if (pri_pk->timestamp > cur_time)
{
ulong d = pri_pk->timestamp - cur_time;
log_info (d==1 ? _("key has been created %lu second "
"in future (time warp or clock problem)\n")
: _("key has been created %lu seconds "
"in future (time warp or clock problem)\n"), d );
if (!opt.ignore_time_conflict)
{
err = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
}
if (pri_pk->version < 4)
{
log_info (_("Note: creating subkeys for v3 keys "
"is not OpenPGP compliant\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
expire = ask_expire_interval (0, NULL);
if (keyno == 1)
use = PUBKEY_USAGE_SIG;
else if (keyno == 2)
use = PUBKEY_USAGE_ENC;
else
use = PUBKEY_USAGE_AUTH;
if (!cpr_enabled() && !cpr_get_answer_is_yes("keygen.cardsub.okay",
_("Really create? (y/N) ")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
/* Note, that depending on the backend, the card key generation may
update CUR_TIME. */
err = gen_card_key (keyno, algo, 0, pub_keyblock, &cur_time, expire,
&keygen_flags);
/* Get the pointer to the generated public subkey packet. */
if (!err)
{
for (node = pub_keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
sub_pk = node->pkt->pkt.public_key;
log_assert (sub_pk);
err = write_keybinding (ctrl, pub_keyblock, pri_pk, sub_pk,
use, cur_time, NULL);
}
leave:
if (err)
log_error (_("Key generation failed: %s\n"), gpg_strerror (err) );
else
print_status_key_created ('S', sub_pk, NULL);
release_parameter_list (para);
return err;
}
#endif /* !ENABLE_CARD_SUPPORT */
/*
* Write a keyblock to an output stream
*/
static int
write_keyblock( IOBUF out, KBNODE node )
{
for( ; node ; node = node->next )
{
if(!is_deleted_kbnode(node))
{
int rc = build_packet( out, node->pkt );
if( rc )
{
log_error("build_packet(%d) failed: %s\n",
node->pkt->pkttype, gpg_strerror (rc) );
return rc;
}
}
}
return 0;
}
/* Note that timestamp is an in/out arg. */
static gpg_error_t
gen_card_key (int keyno, int algo, int is_primary, kbnode_t pub_root,
u32 *timestamp, u32 expireval, int *keygen_flags)
{
#ifdef ENABLE_CARD_SUPPORT
gpg_error_t err;
PACKET *pkt;
PKT_public_key *pk;
char keyid[10];
unsigned char *public;
gcry_sexp_t s_key;
snprintf (keyid, DIM(keyid), "OPENPGP.%d", keyno);
pk = xtrycalloc (1, sizeof *pk );
if (!pk)
return gpg_error_from_syserror ();
pkt = xtrycalloc (1, sizeof *pkt);
if (!pkt)
{
xfree (pk);
return gpg_error_from_syserror ();
}
/* Note: SCD knows the serialnumber, thus there is no point in passing it. */
err = agent_scd_genkey (keyno, 1, timestamp);
/* The code below is not used because we force creation of
* the a card key (3rd arg).
* if (gpg_err_code (rc) == GPG_ERR_EEXIST)
* {
* tty_printf ("\n");
* log_error ("WARNING: key does already exists!\n");
* tty_printf ("\n");
* if ( cpr_get_answer_is_yes( "keygen.card.replace_key",
* _("Replace existing key? ")))
* rc = agent_scd_genkey (keyno, 1, timestamp);
* }
*/
if (err)
{
log_error ("key generation failed: %s\n", gpg_strerror (err));
xfree (pkt);
xfree (pk);
return err;
}
/* Send the READKEY command so that the agent creates a shadow key for
card key. We need to do that now so that we are able to create
the self-signatures. */
err = agent_readkey (NULL, 1, keyid, &public);
if (err)
{
xfree (pkt);
xfree (pk);
return err;
}
err = gcry_sexp_sscan (&s_key, NULL, public,
gcry_sexp_canon_len (public, 0, NULL, NULL));
xfree (public);
if (err)
{
xfree (pkt);
xfree (pk);
return err;
}
/* Force creation of v5 keys for X448. */
if (curve_is_448 (s_key))
*keygen_flags |= KEYGEN_FLAG_CREATE_V5_KEY;
pk->version = (*keygen_flags & KEYGEN_FLAG_CREATE_V5_KEY)? 5 : 4;
if (algo == PUBKEY_ALGO_RSA)
err = key_from_sexp (pk->pkey, s_key, "public-key", "ne");
else if (algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA
|| algo == PUBKEY_ALGO_ECDH )
err = ecckey_from_sexp (pk->pkey, s_key, NULL, algo, pk->version);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
gcry_sexp_release (s_key);
if (err)
{
log_error ("key_from_sexp failed: %s\n", gpg_strerror (err) );
free_public_key (pk);
return err;
}
pk->timestamp = *timestamp;
if (expireval)
pk->expiredate = pk->timestamp + expireval;
pk->pubkey_algo = algo;
pkt->pkttype = is_primary ? PKT_PUBLIC_KEY : PKT_PUBLIC_SUBKEY;
pkt->pkt.public_key = pk;
add_kbnode (pub_root, new_kbnode (pkt));
return 0;
#else
(void)keyno;
(void)is_primary;
(void)pub_root;
(void)timestamp;
(void)expireval;
return gpg_error (GPG_ERR_NOT_SUPPORTED);
#endif /*!ENABLE_CARD_SUPPORT*/
}
diff --git a/g10/keyid.c b/g10/keyid.c
index 08f684829..d0317105d 100644
--- a/g10/keyid.c
+++ b/g10/keyid.c
@@ -1,1487 +1,1487 @@
/* keyid.c - key ID and fingerprint handling
* Copyright (C) 1998, 1999, 2000, 2001, 2003,
* 2004, 2006, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2016, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "gpg.h"
#include "../common/util.h"
#include "main.h"
#include "packet.h"
#include "options.h"
#include "keydb.h"
#include "../common/i18n.h"
#include "rmd160.h"
#include "../common/host2net.h"
#define KEYID_STR_SIZE 19
#ifdef HAVE_UNSIGNED_TIME_T
# define IS_INVALID_TIME_T(a) ((a) == (time_t)(-1))
#else
/* Error or 32 bit time_t and value after 2038-01-19. */
# define IS_INVALID_TIME_T(a) ((a) < 0)
#endif
/* Return a letter describing the public key algorithms. */
int
pubkey_letter( int algo )
{
switch (algo)
{
case PUBKEY_ALGO_RSA: return 'R' ;
case PUBKEY_ALGO_RSA_E: return 'r' ;
case PUBKEY_ALGO_RSA_S: return 's' ;
case PUBKEY_ALGO_ELGAMAL_E: return 'g' ;
case PUBKEY_ALGO_ELGAMAL: return 'G' ;
case PUBKEY_ALGO_DSA: return 'D' ;
case PUBKEY_ALGO_ECDH: return 'e' ; /* ECC DH (encrypt only) */
case PUBKEY_ALGO_ECDSA: return 'E' ; /* ECC DSA (sign only) */
case PUBKEY_ALGO_EDDSA: return 'E' ; /* ECC EdDSA (sign only) */
default: return '?';
}
}
/* Return a string describing the public key algorithm and the
keysize. For elliptic curves the function prints the name of the
curve because the keysize is a property of the curve. The string
is copied to the supplied buffer up a length of BUFSIZE-1.
Examples for the output are:
"rsa3072" - RSA with 3072 bit
"elg1024" - Elgamal with 1024 bit
"ed25519" - EdDSA using the curve Ed25519.
"cv25519" - ECDH using the curve X25519.
"ky768_cv448 - Kyber-768 with X448 as second algo.
"ky1025_bp512 - Kyber-1024 with BrainpoolP256r1 as second algo.
"E_1.2.3.4" - ECC using the unsupported curve with OID "1.2.3.4".
"unknown_N" - Unknown OpenPGP algorithm N.
"E_1.3.6.1.4.1.11591.2.12242973" ECC with a bogus OID.
Note that with Kyber we use "bp" as abbreviation for BrainpoolP and
ignore the final r1 part.
If the option --legacy-list-mode is active, the output use the
legacy format:
"3072R" - RSA with 3072 bit
"1024g" - Elgamal with 1024 bit
"256E" - ECDSA using a curve with 256 bit
The macro PUBKEY_STRING_SIZE may be used to allocate a buffer with
a suitable size. Note that a more general version of this function
exists as get_keyalgo_string. However, that has no special
treatment for the old and unsupported Elgamal which we here print as
xxxNNNN. */
char *
pubkey_string (PKT_public_key *pk, char *buffer, size_t bufsize)
{
const char *prefix = NULL;
int dual = 0;
char *curve;
const char *name;
if (opt.legacy_list_mode)
{
snprintf (buffer, bufsize, "%4u%c",
nbits_from_pk (pk), pubkey_letter (pk->pubkey_algo));
return buffer;
}
switch (pk->pubkey_algo)
{
case PUBKEY_ALGO_RSA:
case PUBKEY_ALGO_RSA_E:
case PUBKEY_ALGO_RSA_S: prefix = "rsa"; break;
case PUBKEY_ALGO_ELGAMAL_E: prefix = "elg"; break;
case PUBKEY_ALGO_DSA: prefix = "dsa"; break;
case PUBKEY_ALGO_ELGAMAL: prefix = "xxx"; break;
case PUBKEY_ALGO_ECDH:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_EDDSA: prefix = ""; break;
case PUBKEY_ALGO_KYBER: prefix = "ky"; dual = 1; break;
case PUBKEY_ALGO_DIL3_25519: prefix = "dil3"; break;
case PUBKEY_ALGO_DIL5_448: prefix = "dil5"; break;
case PUBKEY_ALGO_SPHINX_SHA2: prefix = "sphinx_sha2"; break;
}
if (prefix && *prefix)
{
if (dual)
{
curve = openpgp_oid_to_str (pk->pkey[0]);
/* Note that we prefer the abbreviated name of the curve. */
name = openpgp_oid_to_curve (curve, 2);
if (!name)
name = "unknown";
snprintf (buffer, bufsize, "%s%u_%s",
prefix, nbits_from_pk (pk), name);
xfree (curve);
}
else
snprintf (buffer, bufsize, "%s%u", prefix, nbits_from_pk (pk));
}
else if (prefix)
{
curve = openpgp_oid_to_str (pk->pkey[0]);
name = openpgp_oid_to_curve (curve, 0);
if (name)
snprintf (buffer, bufsize, "%s", name);
else if (curve)
snprintf (buffer, bufsize, "E_%s", curve);
else
snprintf (buffer, bufsize, "E_error");
xfree (curve);
}
else
snprintf (buffer, bufsize, "unknown_%u", (unsigned int)pk->pubkey_algo);
return buffer;
}
/* Helper for compare_pubkey_string. This skips leading spaces,
* commas and optional condition operators and returns a pointer to
* the first non-space character or NULL in case of an error. The
* length of a prefix consisting of letters is then returned ar PFXLEN
* and the value of the number (e.g. 384 for "brainpoolP384r1") at
* NUMBER. R_LENGTH receives the entire length of the algorithm name
* which is terminated by a space, nul, or a comma. If R_CONDITION is
* not NULL, 0 is stored for a leading "=", 1 for a ">", 2 for a ">=",
* -1 for a "<", and -2 for a "<=". If R_CONDITION is NULL no
* condition prefix is allowed. */
static const char *
parse_one_algo_string (const char *str, size_t *pfxlen, unsigned int *number,
size_t *r_length, int *r_condition)
{
int condition = 0;
const char *result;
while (spacep (str) || *str ==',')
str++;
if (!r_condition)
;
else if (*str == '>' && str[1] == '=')
condition = 2, str += 2;
else if (*str == '>' )
condition = 1, str += 1;
else if (*str == '<' && str[1] == '=')
condition = -2, str += 2;
else if (*str == '<')
condition = -1, str += 1;
else if (*str == '=') /* Default. */
str += 1;
if (!alphap (str))
return NULL; /* Error. */
*pfxlen = 1;
for (result = str++; alphap (str); str++)
++*pfxlen;
while (*str == '-' || *str == '+')
str++;
*number = atoi (str);
while (*str && !spacep (str) && *str != ',')
str++;
*r_length = str - result;
if (r_condition)
*r_condition = condition;
return result;
}
/* Helper for compare_pubkey_string. If BPARSED is set to 0 on
* return, an error in ASTR or BSTR was found and further checks are
* not possible. */
static int
compare_pubkey_string_part (const char *astr, const char *bstr_arg,
size_t *bparsed)
{
const char *bstr = bstr_arg;
size_t alen, apfxlen, blen, bpfxlen;
unsigned int anumber, bnumber;
int condition;
*bparsed = 0;
astr = parse_one_algo_string (astr, &apfxlen, &anumber, &alen, &condition);
if (!astr)
return 0; /* Invalid algorithm name. */
bstr = parse_one_algo_string (bstr, &bpfxlen, &bnumber, &blen, &condition);
if (!bstr)
return 0; /* Invalid algorithm name. */
*bparsed = blen + (bstr - bstr_arg);
if (apfxlen != bpfxlen || ascii_strncasecmp (astr, bstr, apfxlen))
return 0; /* false. */
switch (condition)
{
case 2: return anumber >= bnumber;
case 1: return anumber > bnumber;
case -1: return anumber < bnumber;
case -2: return anumber <= bnumber;
}
return alen == blen && !ascii_strncasecmp (astr, bstr, alen);
}
/* Check whether ASTR matches the constraints given by BSTR. ASTR may
* be any algo string like "rsa2048", "ed25519" and BSTR may be a
* constraint which is in the simplest case just another algo string.
* BSTR may have more that one string in which case they are comma
* separated and any match will return true. It is possible to prefix
* BSTR with ">", ">=", "<=", or "<". That prefix operator is applied
* to the number part of the algorithm, i.e. the first sequence of
* digits found before end-of-string or a comma. Examples:
*
* | ASTR | BSTR | result |
* |----------+----------------------+--------|
* | rsa2048 | rsa2048 | true |
* | rsa2048 | >=rsa2048 | true |
* | rsa2048 | >rsa2048 | false |
* | ed25519 | >rsa1024 | false |
* | ed25519 | ed25519 | true |
* | nistp384 | >nistp256 | true |
* | nistp521 | >=rsa3072, >nistp384 | true |
*/
int
compare_pubkey_string (const char *astr, const char *bstr)
{
size_t bparsed;
int result;
while (*bstr)
{
result = compare_pubkey_string_part (astr, bstr, &bparsed);
if (result)
return 1;
if (!bparsed)
return 0; /* Syntax error in ASTR or BSTR. */
bstr += bparsed;
}
return 0;
}
/* Hash a public key and allow to specify the to be used format.
* Note that if the v5 format is requested for a v4 key, a 0x04 as
* version is hashed instead of the 0x05. */
static void
do_hash_public_key (gcry_md_hd_t md, PKT_public_key *pk, int use_v5)
{
unsigned int n;
unsigned int nn[PUBKEY_MAX_NPKEY];
byte *pp[PUBKEY_MAX_NPKEY];
int i;
unsigned int nbits;
size_t nbytes;
int npkey = pubkey_get_npkey (pk->pubkey_algo);
n = use_v5? 10 : 6;
/* FIXME: We can avoid the extra malloc by calling only the first
mpi_print here which computes the required length and calling the
real mpi_print only at the end. The speed advantage would only be
for ECC (opaque MPIs) or if we could implement an mpi_print
variant with a callback handler to do the hashing. */
if (npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
pp[0] = gcry_mpi_get_opaque (pk->pkey[0], &nbits);
nn[0] = (nbits+7)/8;
n+=nn[0];
}
else
{
for (i=0; i < npkey; i++ )
{
if (!pk->pkey[i])
{
/* This case may only happen if the parsing of the MPI
failed but the key was anyway created. May happen
during "gpg KEYFILE". */
pp[i] = NULL;
nn[i] = 0;
}
else if (pk->pubkey_algo == PUBKEY_ALGO_KYBER && i == 2)
{
/* Ugly: We need to re-construct the wire format of the
* key parameter. It would be easier to use a second
- * index for pp and nn which we could bump independet of
+ * index for pp and nn which we could bump independent of
* i. */
const char *p;
p = gcry_mpi_get_opaque (pk->pkey[i], &nbits);
nn[i] = (nbits+7)/8;
pp[i] = xmalloc (4 + nn[i] + 1);
if (p)
{
pp[i][0] = nn[i] >> 24;
pp[i][1] = nn[i] >> 16;
pp[i][2] = nn[i] >> 8;
pp[i][3] = nn[i];
memcpy (pp[i] + 4 , p, nn[i]);
nn[i] += 4;
}
else
pp[i] = NULL;
n += nn[i];
}
else if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_OPAQUE))
{
const char *p;
int is_sos = 0;
if (gcry_mpi_get_flag (pk->pkey[i], GCRYMPI_FLAG_USER2))
is_sos = 2;
p = gcry_mpi_get_opaque (pk->pkey[i], &nbits);
pp[i] = xmalloc ((nbits+7)/8 + is_sos);
if (p)
memcpy (pp[i] + is_sos, p, (nbits+7)/8);
else
pp[i] = NULL;
if (is_sos)
{
if (*p)
{
nbits = ((nbits + 7) / 8) * 8;
if (nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
}
pp[i][0] = (nbits >> 8);
pp[i][1] = nbits;
}
nn[i] = (nbits+7)/8 + is_sos;
n += nn[i];
}
else
{
if (gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0,
&nbytes, pk->pkey[i]))
BUG ();
pp[i] = xmalloc (nbytes);
if (gcry_mpi_print (GCRYMPI_FMT_PGP, pp[i], nbytes,
&nbytes, pk->pkey[i]))
BUG ();
nn[i] = nbytes;
n += nn[i];
}
}
}
if (use_v5)
{
gcry_md_putc ( md, 0x9a ); /* ctb */
gcry_md_putc ( md, n >> 24 ); /* 4 byte length header (upper bits) */
gcry_md_putc ( md, n >> 16 );
}
else
{
gcry_md_putc ( md, 0x99 ); /* ctb */
}
gcry_md_putc ( md, n >> 8 ); /* lower bits of the length header. */
gcry_md_putc ( md, n );
gcry_md_putc ( md, pk->version );
gcry_md_putc ( md, pk->timestamp >> 24 );
gcry_md_putc ( md, pk->timestamp >> 16 );
gcry_md_putc ( md, pk->timestamp >> 8 );
gcry_md_putc ( md, pk->timestamp );
gcry_md_putc ( md, pk->pubkey_algo );
if (use_v5) /* Hash the 32 bit length */
{
n -= 10;
gcry_md_putc ( md, n >> 24 );
gcry_md_putc ( md, n >> 16 );
gcry_md_putc ( md, n >> 8 );
gcry_md_putc ( md, n );
}
if(npkey==0 && pk->pkey[0]
&& gcry_mpi_get_flag (pk->pkey[0], GCRYMPI_FLAG_OPAQUE))
{
if (pp[0])
gcry_md_write (md, pp[0], nn[0]);
}
else
{
for(i=0; i < npkey; i++ )
{
if (pp[i])
gcry_md_write ( md, pp[i], nn[i] );
xfree(pp[i]);
}
}
}
/* Hash a public key. This function is useful for v4 and v5
* fingerprints and for v3 or v4 key signing. */
void
hash_public_key (gcry_md_hd_t md, PKT_public_key *pk)
{
do_hash_public_key (md, pk, (pk->version == 5));
}
/* fixme: Check whether we can replace this function or if not
describe why we need it. */
u32
v3_keyid (gcry_mpi_t a, u32 *ki)
{
byte *buffer, *p;
size_t nbytes;
if (gcry_mpi_print (GCRYMPI_FMT_USG, NULL, 0, &nbytes, a ))
BUG ();
/* fixme: allocate it on the stack */
buffer = xmalloc (nbytes);
if (gcry_mpi_print( GCRYMPI_FMT_USG, buffer, nbytes, NULL, a ))
BUG ();
if (nbytes < 8) /* oops */
ki[0] = ki[1] = 0;
else
{
p = buffer + nbytes - 8;
ki[0] = buf32_to_u32 (p);
p += 4;
ki[1] = buf32_to_u32 (p);
}
xfree (buffer);
return ki[1];
}
/* Return PK's keyid. The memory is owned by PK. */
u32 *
pk_keyid (PKT_public_key *pk)
{
keyid_from_pk (pk, NULL);
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) == 0
&& ! pk->flags.primary)
log_bug ("keyid and main_keyid are the same, but primary flag not set!\n");
if (keyid_cmp (pk->keyid, pk->main_keyid) != 0
&& pk->flags.primary)
log_bug ("keyid and main_keyid are different, but primary flag set!\n");
#endif
return pk->keyid;
}
/* Return the keyid of the primary key associated with PK. The memory
is owned by PK. */
u32 *
pk_main_keyid (PKT_public_key *pk)
{
/* Uncomment this for help tracking down bugs related to keyid or
main_keyid not being set correctly. */
#if 0
if (! (pk->main_keyid[0] || pk->main_keyid[1]))
log_bug ("pk->main_keyid not set!\n");
#endif
return pk->main_keyid;
}
/* Copy the keyid in SRC to DEST and return DEST. */
u32 *
keyid_copy (u32 *dest, const u32 *src)
{
dest[0] = src[0];
dest[1] = src[1];
return dest;
}
char *
format_keyid (u32 *keyid, int format, char *buffer, int len)
{
if (! buffer)
{
len = KEYID_STR_SIZE;
buffer = xtrymalloc (len);
if (!buffer)
return NULL;
}
if (format == KF_DEFAULT)
format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch (format)
{
case KF_NONE:
if (len)
*buffer = 0;
break;
case KF_SHORT:
snprintf (buffer, len, "%08lX", (ulong)keyid[1]);
break;
case KF_LONG:
snprintf (buffer, len, "%08lX%08lX", (ulong)keyid[0], (ulong)keyid[1]);
break;
case KF_0xSHORT:
snprintf (buffer, len, "0x%08lX", (ulong)keyid[1]);
break;
case KF_0xLONG:
snprintf (buffer, len, "0x%08lX%08lX", (ulong)keyid[0],(ulong)keyid[1]);
break;
default:
BUG();
}
return buffer;
}
size_t
keystrlen(void)
{
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
switch(format)
{
case KF_NONE:
return 0;
case KF_SHORT:
return 8;
case KF_LONG:
return 16;
case KF_0xSHORT:
return 10;
case KF_0xLONG:
return 18;
default:
BUG();
}
}
const char *
keystr (u32 *keyid)
{
static char keyid_str[KEYID_STR_SIZE];
int format = opt.keyid_format;
if (format == KF_DEFAULT)
format = KF_NONE;
if (format == KF_NONE)
format = KF_LONG;
return format_keyid (keyid, format, keyid_str, sizeof (keyid_str));
}
/* This function returns the key id of the main and possible the
* subkey as one string. It is used by error messages. */
const char *
keystr_with_sub (u32 *main_kid, u32 *sub_kid)
{
static char buffer[KEYID_STR_SIZE+1+KEYID_STR_SIZE];
char *p;
int format = opt.keyid_format;
if (format == KF_NONE)
format = KF_LONG;
format_keyid (main_kid, format, buffer, KEYID_STR_SIZE);
if (sub_kid)
{
p = buffer + strlen (buffer);
*p++ = '/';
format_keyid (sub_kid, format, p, KEYID_STR_SIZE);
}
return buffer;
}
const char *
keystr_from_pk(PKT_public_key *pk)
{
keyid_from_pk(pk,NULL);
return keystr(pk->keyid);
}
const char *
keystr_from_pk_with_sub (PKT_public_key *main_pk, PKT_public_key *sub_pk)
{
keyid_from_pk (main_pk, NULL);
if (sub_pk)
keyid_from_pk (sub_pk, NULL);
return keystr_with_sub (main_pk->keyid, sub_pk? sub_pk->keyid:NULL);
}
/* Return PK's key id as a string using the default format. PK owns
the storage. */
const char *
pk_keyid_str (PKT_public_key *pk)
{
return keystr (pk_keyid (pk));
}
const char *
keystr_from_desc(KEYDB_SEARCH_DESC *desc)
{
switch(desc->mode)
{
case KEYDB_SEARCH_MODE_LONG_KID:
case KEYDB_SEARCH_MODE_SHORT_KID:
return keystr(desc->u.kid);
case KEYDB_SEARCH_MODE_FPR:
{
u32 keyid[2];
if (desc->fprlen == 32)
{
keyid[0] = buf32_to_u32 (desc->u.fpr);
keyid[1] = buf32_to_u32 (desc->u.fpr+4);
}
else if (desc->fprlen == 20)
{
keyid[0] = buf32_to_u32 (desc->u.fpr+12);
keyid[1] = buf32_to_u32 (desc->u.fpr+16);
}
else if (desc->fprlen == 16)
return "?v3 fpr?";
else /* oops */
return "?vx fpr?";
return keystr(keyid);
}
default:
BUG();
}
}
/* Compute the fingerprint and keyid and store it in PK. */
static void
compute_fingerprint (PKT_public_key *pk)
{
const byte *dp;
gcry_md_hd_t md;
size_t len;
if (gcry_md_open (&md, pk->version == 5 ? GCRY_MD_SHA256 : GCRY_MD_SHA1, 0))
BUG ();
hash_public_key (md, pk);
gcry_md_final (md);
dp = gcry_md_read (md, 0);
len = gcry_md_get_algo_dlen (gcry_md_get_algo (md));
log_assert (len <= MAX_FINGERPRINT_LEN);
memcpy (pk->fpr, dp, len);
pk->fprlen = len;
if (pk->version == 5)
{
pk->keyid[0] = buf32_to_u32 (dp);
pk->keyid[1] = buf32_to_u32 (dp+4);
}
else
{
pk->keyid[0] = buf32_to_u32 (dp+12);
pk->keyid[1] = buf32_to_u32 (dp+16);
}
gcry_md_close( md);
}
/*
* Get the keyid from the public key PK and store it at KEYID unless
* this is NULL. Returns the 32 bit short keyid.
*/
u32
keyid_from_pk (PKT_public_key *pk, u32 *keyid)
{
u32 dummy_keyid[2];
if (!keyid)
keyid = dummy_keyid;
if (!pk->fprlen)
compute_fingerprint (pk);
keyid[0] = pk->keyid[0];
keyid[1] = pk->keyid[1];
if (pk->fprlen == 32)
return keyid[0];
else
return keyid[1];
}
/*
* Get the keyid from the fingerprint. This function is simple for
* most keys, but has to do a key lookup for old v3 keys where the
* keyid is not part of the fingerprint.
*/
u32
keyid_from_fingerprint (ctrl_t ctrl, const byte *fprint,
size_t fprint_len, u32 *keyid)
{
u32 dummy_keyid[2];
if( !keyid )
keyid = dummy_keyid;
if (fprint_len != 20 && fprint_len != 32)
{
/* This is special as we have to lookup the key first. */
PKT_public_key pk;
int rc;
memset (&pk, 0, sizeof pk);
rc = get_pubkey_byfprint (ctrl, &pk, NULL, fprint, fprint_len);
if( rc )
{
log_printhex (fprint, fprint_len,
"Oops: keyid_from_fingerprint: no pubkey; fpr:");
keyid[0] = 0;
keyid[1] = 0;
}
else
keyid_from_pk (&pk, keyid);
}
else
{
const byte *dp = fprint;
if (fprint_len == 20) /* v4 key */
{
keyid[0] = buf32_to_u32 (dp+12);
keyid[1] = buf32_to_u32 (dp+16);
}
else /* v5 key */
{
keyid[0] = buf32_to_u32 (dp);
keyid[1] = buf32_to_u32 (dp+4);
}
}
return keyid[1];
}
u32
keyid_from_sig (PKT_signature *sig, u32 *keyid)
{
if( keyid )
{
keyid[0] = sig->keyid[0];
keyid[1] = sig->keyid[1];
}
return sig->keyid[1]; /*FIXME:shortkeyid*/
}
byte *
namehash_from_uid (PKT_user_id *uid)
{
if (!uid->namehash)
{
uid->namehash = xmalloc (20);
if (uid->attrib_data)
rmd160_hash_buffer (uid->namehash, uid->attrib_data, uid->attrib_len);
else
rmd160_hash_buffer (uid->namehash, uid->name, uid->len);
}
return uid->namehash;
}
/*
* Return the number of bits used in PK. For Kyber we return the
* octet count of the Kyber part and not of the ECC (thus likely
* values are 768 or 1024).
*/
unsigned int
nbits_from_pk (PKT_public_key *pk)
{
if (pk->pubkey_algo == PUBKEY_ALGO_KYBER)
{
unsigned int nbits;
if (!gcry_mpi_get_opaque (pk->pkey[2], &nbits))
return 0;
switch (nbits/8)
{
case 800: nbits = 512; break;
case 1184: nbits = 768; break;
case 1568: nbits = 1024; break;
- default: nbits = 0; break; /* Unkown version. */
+ default: nbits = 0; break; /* Unknown version. */
}
return nbits;
}
else
return pubkey_nbits (pk->pubkey_algo, pk->pkey);
}
/* Convert an UTC TIMESTAMP into an UTC yyyy-mm-dd string. Return
* that string. The caller should pass a buffer with at least a size
* of MK_DATESTR_SIZE. */
char *
mk_datestr (char *buffer, size_t bufsize, u32 timestamp)
{
time_t atime = timestamp;
struct tm *tp;
if (IS_INVALID_TIME_T (atime))
strcpy (buffer, "????" "-??" "-??"); /* Mark this as invalid. */
else
{
tp = gmtime (&atime);
snprintf (buffer, bufsize, "%04d-%02d-%02d",
1900+tp->tm_year, tp->tm_mon+1, tp->tm_mday );
}
return buffer;
}
/*
* return a string with the creation date of the pk
* Note: this is alloced in a static buffer.
* Format is: yyyy-mm-dd
*/
const char *
dateonlystr_from_pk (PKT_public_key *pk)
{
static char buffer[MK_DATESTR_SIZE];
return mk_datestr (buffer, sizeof buffer, pk->timestamp);
}
/* Same as dateonlystr_from_pk but with a global option a full iso
* timestamp is returned. In this case it shares a static buffer with
* isotimestamp(). */
const char *
datestr_from_pk (PKT_public_key *pk)
{
if (opt.flags.full_timestrings)
return isotimestamp (pk->timestamp);
else
return dateonlystr_from_pk (pk);
}
const char *
dateonlystr_from_sig (PKT_signature *sig )
{
static char buffer[MK_DATESTR_SIZE];
return mk_datestr (buffer, sizeof buffer, sig->timestamp);
}
const char *
datestr_from_sig (PKT_signature *sig )
{
if (opt.flags.full_timestrings)
return isotimestamp (sig->timestamp);
else
return dateonlystr_from_sig (sig);
}
const char *
expirestr_from_pk (PKT_public_key *pk)
{
static char buffer[MK_DATESTR_SIZE];
if (!pk->expiredate)
return _("never ");
if (opt.flags.full_timestrings)
return isotimestamp (pk->expiredate);
return mk_datestr (buffer, sizeof buffer, pk->expiredate);
}
const char *
expirestr_from_sig (PKT_signature *sig)
{
static char buffer[MK_DATESTR_SIZE];
if (!sig->expiredate)
return _("never ");
if (opt.flags.full_timestrings)
return isotimestamp (sig->expiredate);
return mk_datestr (buffer, sizeof buffer, sig->expiredate);
}
const char *
revokestr_from_pk( PKT_public_key *pk )
{
static char buffer[MK_DATESTR_SIZE];
if(!pk->revoked.date)
return _("never ");
if (opt.flags.full_timestrings)
return isotimestamp (pk->revoked.date);
return mk_datestr (buffer, sizeof buffer, pk->revoked.date);
}
const char *
usagestr_from_pk (PKT_public_key *pk, int fill)
{
static char buffer[10];
int i = 0;
unsigned int use = pk->pubkey_usage;
if ( use & PUBKEY_USAGE_SIG )
buffer[i++] = 'S';
if ( use & PUBKEY_USAGE_CERT )
buffer[i++] = 'C';
if ( use & PUBKEY_USAGE_ENC )
buffer[i++] = 'E';
if ( (use & PUBKEY_USAGE_AUTH) )
buffer[i++] = 'A';
if ( (use & PUBKEY_USAGE_RENC) )
buffer[i++] = 'R';
if ( (use & PUBKEY_USAGE_TIME) )
buffer[i++] = 'T';
if ( (use & PUBKEY_USAGE_GROUP) )
buffer[i++] = 'G';
while (fill && i < 4)
buffer[i++] = ' ';
buffer[i] = 0;
return buffer;
}
const char *
colon_strtime (u32 t)
{
static char buf[20];
if (!t)
return "";
snprintf (buf, sizeof buf, "%lu", (ulong)t);
return buf;
}
const char *
colon_datestr_from_pk (PKT_public_key *pk)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)pk->timestamp);
return buf;
}
const char *
colon_datestr_from_sig (PKT_signature *sig)
{
static char buf[20];
snprintf (buf, sizeof buf, "%lu", (ulong)sig->timestamp);
return buf;
}
const char *
colon_expirestr_from_sig (PKT_signature *sig)
{
static char buf[20];
if (!sig->expiredate)
return "";
snprintf (buf, sizeof buf,"%lu", (ulong)sig->expiredate);
return buf;
}
/*
* Return a byte array with the fingerprint for the given PK/SK
* The length of the array is returned in ret_len. Caller must free
* the array or provide an array of length MAX_FINGERPRINT_LEN.
*/
byte *
fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len)
{
if (!pk->fprlen)
compute_fingerprint (pk);
if (!array)
array = xmalloc (pk->fprlen);
memcpy (array, pk->fpr, pk->fprlen);
if (ret_len)
*ret_len = pk->fprlen;
return array;
}
/*
* Return a byte array with the fingerprint for the given PK/SK The
* length of the array is returned in ret_len. Caller must free the
* array or provide an array of length MAX_FINGERPRINT_LEN. This
- * version creates a v5 fingerprint even vor v4 keys.
+ * version creates a v5 fingerprint even for v4 keys.
*/
byte *
v5_fingerprint_from_pk (PKT_public_key *pk, byte *array, size_t *ret_len)
{
const byte *dp;
gcry_md_hd_t md;
if (pk->version == 5)
return fingerprint_from_pk (pk, array, ret_len);
if (gcry_md_open (&md, GCRY_MD_SHA256, 0))
BUG ();
do_hash_public_key (md, pk, 1);
gcry_md_final (md);
dp = gcry_md_read (md, 0);
if (!array)
array = xmalloc (32);
memcpy (array, dp, 32);
gcry_md_close (md);
if (ret_len)
*ret_len = 32;
return array;
}
/*
* This is the core of fpr20_from_pk which directly takes a
* fingerprint and its length instead of the public key. See below
* for details.
*/
void
fpr20_from_fpr (const byte *fpr, unsigned int fprlen, byte array[20])
{
if (fprlen >= 32) /* v5 fingerprint (or larger) */
{
memcpy (array + 0, fpr + 20, 4);
memcpy (array + 4, fpr + 24, 4);
memcpy (array + 8, fpr + 28, 4);
memcpy (array + 12, fpr + 0, 4); /* kid[0] */
memcpy (array + 16, fpr + 4, 4); /* kid[1] */
}
else if (fprlen == 20) /* v4 fingerprint */
memcpy (array, fpr, 20);
else /* v3 or too short: fill up with zeroes. */
{
memset (array, 0, 20);
memcpy (array, fpr, fprlen);
}
}
/*
* Get FPR20 for the given PK/SK into ARRAY.
*
* FPR20 is special form of fingerprint of length 20 for the record of
* trustdb. For v4key, having fingerprint with SHA-1, FPR20 is the
* same one. For v5key, FPR20 is constructed from its fingerprint
* with SHA-2, so that its kid of last 8-byte can be as same as
* kid of v5key fingerprint.
*
*/
void
fpr20_from_pk (PKT_public_key *pk, byte array[20])
{
if (!pk->fprlen)
compute_fingerprint (pk);
fpr20_from_fpr (pk->fpr, pk->fprlen, array);
}
/* Return an allocated buffer with the fingerprint of PK formatted as
* a plain hexstring. If BUFFER is NULL the result is a malloc'd
* string. If BUFFER is not NULL the result will be copied into this
* buffer. In the latter case BUFLEN describes the length of the
* buffer; if this is too short the function terminates the process.
* Returns a malloc'ed string or BUFFER. A suitable length for BUFFER
* is (2*MAX_FINGERPRINT_LEN + 1). */
char *
hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen)
{
if (!pk->fprlen)
compute_fingerprint (pk);
if (!buffer)
{
buffer = xtrymalloc (2 * pk->fprlen + 1);
if (!buffer)
return NULL;
}
else if (buflen < 2 * pk->fprlen + 1)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
bin2hex (pk->fpr, pk->fprlen, buffer);
return buffer;
}
/* Same as hexfingerprint but returns a v5 fingerprint also for a v4
* key. */
char *
v5hexfingerprint (PKT_public_key *pk, char *buffer, size_t buflen)
{
char fprbuf[32];
if (pk->version == 5)
return hexfingerprint (pk, buffer, buflen);
if (!buffer)
{
buffer = xtrymalloc (2 * 32 + 1);
if (!buffer)
return NULL;
}
else if (buflen < 2 * 32 + 1)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
v5_fingerprint_from_pk (pk, fprbuf, NULL);
return bin2hex (fprbuf, 32, buffer);
}
/* Pretty print a hex fingerprint. If BUFFER is NULL the result is a
malloc'd string. If BUFFER is not NULL the result will be copied
into this buffer. In the latter case BUFLEN describes the length
of the buffer; if this is too short the function terminates the
process. Returns a malloc'ed string or BUFFER. A suitable length
for BUFFER is (MAX_FORMATTED_FINGERPRINT_LEN + 1). */
char *
format_hexfingerprint (const char *fingerprint, char *buffer, size_t buflen)
{
int hexlen = strlen (fingerprint);
int space;
int i, j;
if (hexlen == 40) /* v4 fingerprint */
{
space = (/* The characters and the NUL. */
40 + 1
/* After every fourth character, we add a space (except
the last). */
+ 40 / 4 - 1
/* Half way through we add a second space. */
+ 1);
}
else if (hexlen == 64 || hexlen == 50) /* v5 fingerprint */
{
/* The v5 fingerprint is commonly printed truncated to 25
* octets. We accept the truncated as well as the full hex
* version here and format it like this:
* 19347 BC987 24640 25F99 DF3EC 2E000 0ED98 84892 E1F7B 3EA4C
*/
hexlen = 50;
space = 10 * 5 + 9 + 1;
}
else /* Other fingerprint versions - print as is. */
{
/* We truncated here so that we do not need to provide a buffer
* of a length which is in reality never used. */
if (hexlen > MAX_FORMATTED_FINGERPRINT_LEN - 1)
hexlen = MAX_FORMATTED_FINGERPRINT_LEN - 1;
space = hexlen + 1;
}
if (!buffer)
buffer = xmalloc (space);
else if (buflen < space)
log_fatal ("%s: buffer too short (%zu)\n", __func__, buflen);
if (hexlen == 40) /* v4 fingerprint */
{
for (i = 0, j = 0; i < 40; i ++)
{
if (i && !(i % 4))
buffer[j ++] = ' ';
if (i == 40 / 2)
buffer[j ++] = ' ';
buffer[j ++] = fingerprint[i];
}
buffer[j ++] = 0;
log_assert (j == space);
}
else if (hexlen == 50) /* v5 fingerprint */
{
for (i=j=0; i < 50; i++)
{
if (i && !(i % 5))
buffer[j++] = ' ';
buffer[j++] = fingerprint[i];
}
buffer[j++] = 0;
log_assert (j == space);
}
else
{
mem2str (buffer, fingerprint, space);
}
return buffer;
}
/* Return the so called KEYGRIP which is the SHA-1 hash of the public
* key parameters expressed as an canonical encoded S-Exp. ARRAY must
* be 20 bytes long. Returns 0 on success or an error code. If
* GET_SECOND Is one and PK has dual algorithm, the keygrip of the
* second algorithm is return; GPG_ERR_FALSE is returned if the algo
* is not a dual algorithm. */
gpg_error_t
keygrip_from_pk (PKT_public_key *pk, unsigned char *array, int get_second)
{
gpg_error_t err;
gcry_sexp_t s_pkey;
if (DBG_PACKET)
log_debug ("get_keygrip for public key%s\n", get_second?" (second)":"");
if (get_second && pk->pubkey_algo != PUBKEY_ALGO_KYBER)
return gpg_error (GPG_ERR_FALSE);
switch (pk->pubkey_algo)
{
case GCRY_PK_DSA:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(dsa(p%m)(q%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1],
pk->pkey[2], pk->pkey[3]);
break;
case GCRY_PK_ELG:
case GCRY_PK_ELG_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pk->pkey[0], pk->pkey[1], pk->pkey[2]);
break;
case GCRY_PK_RSA:
case GCRY_PK_RSA_S:
case GCRY_PK_RSA_E:
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))",
pk->pkey[0], pk->pkey[1]);
break;
case PUBKEY_ALGO_EDDSA:
case PUBKEY_ALGO_ECDSA:
case PUBKEY_ALGO_ECDH:
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
if (!curve)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_build (&s_pkey, NULL,
pk->pubkey_algo == PUBKEY_ALGO_EDDSA?
"(public-key(ecc(curve%s)(flags eddsa)(q%m)))":
(pk->pubkey_algo == PUBKEY_ALGO_ECDH
&& openpgp_oid_is_cv25519 (pk->pkey[0]))?
"(public-key(ecc(curve%s)(flags djb-tweak)(q%m)))":
"(public-key(ecc(curve%s)(q%m)))",
curve, pk->pkey[1]);
xfree (curve);
}
}
break;
case PUBKEY_ALGO_KYBER:
if (get_second)
{
char tmpname[15];
snprintf (tmpname, sizeof tmpname, "kyber%u", nbits_from_pk (pk));
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(%s(p%m)))",
tmpname, pk->pkey[2]);
}
else
{
char *curve = openpgp_oid_to_str (pk->pkey[0]);
if (!curve)
err = gpg_error_from_syserror ();
else
{
err = gcry_sexp_build (&s_pkey, NULL,
openpgp_oid_is_cv25519 (pk->pkey[0])
? "(public-key(ecc(curve%s)(flags djb-tweak)(q%m)))"
: "(public-key(ecc(curve%s)(q%m)))",
curve, pk->pkey[1]);
xfree (curve);
}
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
break;
}
if (err)
return err;
if (!gcry_pk_get_keygrip (s_pkey, array))
{
char *hexfpr;
hexfpr = hexfingerprint (pk, NULL, 0);
log_info ("error computing keygrip (fpr=%s)\n", hexfpr);
xfree (hexfpr);
memset (array, 0, 20);
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
if (DBG_PACKET)
log_printhex (array, 20, "keygrip=");
/* FIXME: Save the keygrip in PK. */
}
gcry_sexp_release (s_pkey);
return err;
}
/* Store an allocated buffer with the keygrip of PK encoded as a
* hexstring at r_GRIP. Returns 0 on success. For dual algorithms
* the keygrips are delimited by a comma. */
gpg_error_t
hexkeygrip_from_pk (PKT_public_key *pk, char **r_grip)
{
gpg_error_t err;
char *buf;
unsigned char grip[KEYGRIP_LEN];
unsigned char grip2[KEYGRIP_LEN];
*r_grip = NULL;
err = keygrip_from_pk (pk, grip, 0);
if (!err)
{
if (pk->pubkey_algo == PUBKEY_ALGO_KYBER)
{
err = keygrip_from_pk (pk, grip2, 1);
if (err)
goto leave;
buf = xtrymalloc (2 * KEYGRIP_LEN * 2 + 1 + 1);
}
else
buf = xtrymalloc (KEYGRIP_LEN * 2 + 1);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
bin2hex (grip, KEYGRIP_LEN, buf);
if (pk->pubkey_algo == PUBKEY_ALGO_KYBER)
{
buf[2*KEYGRIP_LEN] = ',';
bin2hex (grip2, KEYGRIP_LEN, buf+2*KEYGRIP_LEN+1);
}
*r_grip = buf;
}
leave:
return err;
}
/* Return a hexfied malloced string of the ECDH parameters for an ECDH
* key from the public key PK. Returns NULL on error. */
char *
ecdh_param_str_from_pk (PKT_public_key *pk)
{
const unsigned char *s;
unsigned int n;
if (!pk
|| pk->pubkey_algo != PUBKEY_ALGO_ECDH
|| !gcry_mpi_get_flag (pk->pkey[2], GCRYMPI_FLAG_OPAQUE)
|| !(s = gcry_mpi_get_opaque (pk->pkey[2], &n)) || !n)
{
gpg_err_set_errno (EINVAL);
return NULL; /* Invalid parameter */
}
n = (n+7)/8;
return bin2hex (s, n, NULL);
}
diff --git a/g10/options.h b/g10/options.h
index ae429fcc1..644eff359 100644
--- a/g10/options.h
+++ b/g10/options.h
@@ -1,468 +1,468 @@
/* options.h
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2010, 2011 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef G10_OPTIONS_H
#define G10_OPTIONS_H
#include <sys/types.h>
#include "../common/types.h"
#include <stdint.h>
#include "main.h"
#include "packet.h"
#include "tofu.h"
#include "../common/session-env.h"
#include "../common/compliance.h"
/* Object to hold information pertaining to a keyserver; it also
allows building a list of keyservers. For historic reasons this is
not a strlist_t. */
struct keyserver_spec
{
struct keyserver_spec *next;
char *uri;
};
typedef struct keyserver_spec *keyserver_spec_t;
/* Global options for GPG. */
EXTERN_UNLESS_MAIN_MODULE
struct
{
int verbose;
int quiet;
unsigned debug;
int armor;
char *outfile;
estream_t outfp; /* Hack, sometimes used in place of outfile. */
off_t max_output;
/* If > 0 a hint with the expected number of input data bytes. This
* is not necessary an exact number but intended to be used for
* progress info and to decide on how to allocate buffers. */
uint64_t input_size_hint;
/* The AEAD chunk size expressed as a power of 2. */
int chunk_size;
int dry_run;
int autostart;
int list_only;
int mimemode;
int textmode;
int expert;
const char *def_sig_expire;
int ask_sig_expire;
const char *def_cert_expire;
int ask_cert_expire;
int batch; /* run in batch mode */
int answer_yes; /* answer yes on most questions */
int answer_no; /* answer no on most questions */
int check_sigs; /* check key signatures */
int with_colons;
int with_key_data;
int with_icao_spelling; /* Print ICAO spelling with fingerprints. */
int with_v5_fingerprint; /* Option --with-v5-fingerprint active. */
int with_fingerprint; /* Option --with-fingerprint active. */
int with_subkey_fingerprint; /* Option --with-subkey-fingerprint active. */
int with_keygrip; /* Option --with-keygrip active. */
int with_key_screening;/* Option --with-key-screening active. */
int with_tofu_info; /* Option --with-tofu_info active. */
int with_secret; /* Option --with-secret active. */
int with_wkd_hash; /* Option --with-wkd-hash. */
int with_key_origin; /* Option --with-key-origin. */
int fingerprint; /* list fingerprints */
int list_sigs; /* list signatures */
int no_armor;
int list_packets; /* Option --list-packets active. */
int def_cipher_algo;
int force_mdc;
int disable_mdc;
int force_aead;
int def_digest_algo;
int cert_digest_algo;
int compress_algo;
int explicit_compress_option; /* A compress option was explicitly given. */
int compress_level;
int bz2_compress_level;
int bz2_decompress_lowmem;
strlist_t def_secret_key;
char *def_recipient;
int def_recipient_self;
strlist_t secret_keys_to_try;
/* A list of mail addresses (addr-spec) provided by the user with
* the option --sender. */
strlist_t sender_list;
/* A list of fingerprints added as designated revokers to new keys. */
strlist_t desig_revokers;
int def_cert_level;
int min_cert_level;
int ask_cert_level;
int emit_version; /* 0 = none,
1 = major only,
2 = major and minor,
3 = full version,
4 = full version plus OS string. */
int marginals_needed;
int completes_needed;
int max_cert_depth;
char *agent_program;
char *keyboxd_program;
char *dirmngr_program;
int disable_dirmngr;
const char *def_new_key_algo;
/* Options to be passed to the gpg-agent */
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
int skip_verify;
int skip_hidden_recipients;
/* TM_CLASSIC must be zero to accommodate trustdbsg generated before
we started storing the trust model inside the trustdb. */
enum
{
TM_CLASSIC=0, TM_PGP=1, TM_EXTERNAL=2,
TM_ALWAYS, TM_DIRECT, TM_AUTO, TM_TOFU, TM_TOFU_PGP
} trust_model;
enum tofu_policy tofu_default_policy;
int force_ownertrust;
enum gnupg_compliance_mode compliance;
enum
{
KF_DEFAULT, KF_NONE, KF_SHORT, KF_LONG, KF_0xSHORT, KF_0xLONG
} keyid_format;
const char *set_filename;
strlist_t comments;
int throw_keyids;
const char *photo_viewer;
int s2k_mode;
int s2k_digest_algo;
int s2k_cipher_algo;
unsigned char s2k_count; /* This is the encoded form, not the raw
count */
int not_dash_escaped;
int escape_from;
int lock_once;
keyserver_spec_t keyserver; /* The list of configured keyservers. */
struct
{
unsigned int options;
unsigned int import_options;
unsigned int export_options;
char *http_proxy;
} keyserver_options;
int exec_disable;
int exec_path_set;
unsigned int import_options;
unsigned int export_options;
unsigned int list_options;
unsigned int verify_options;
const char *def_preference_list;
const char *def_keyserver_url;
prefitem_t *personal_cipher_prefs;
prefitem_t *personal_digest_prefs;
prefitem_t *personal_compress_prefs;
struct weakhash *weak_digests;
int no_perm_warn;
char *temp_dir;
int no_encrypt_to;
int encrypt_to_default_key;
int interactive;
struct notation *sig_notations;
struct notation *cert_notations;
strlist_t sig_policy_url;
strlist_t cert_policy_url;
strlist_t sig_keyserver_url;
strlist_t cert_subpackets;
strlist_t sig_subpackets;
int allow_non_selfsigned_uid;
int allow_freeform_uid;
int no_literal;
ulong set_filesize;
int fast_list_mode;
int legacy_list_mode;
int ignore_time_conflict;
int ignore_valid_from;
int ignore_crc_error;
int ignore_mdc_error;
int ignore_expiration;
int command_fd;
const char *override_session_key;
int show_session_key;
const char *gpg_agent_info;
int try_all_secrets;
int no_expensive_trust_checks;
int no_sig_cache;
int no_auto_check_trustdb;
int preserve_permissions;
int no_homedir_creation;
struct groupitem *grouplist;
int mangle_dos_filenames;
int enable_progress_filter;
unsigned int screen_columns;
unsigned int screen_lines;
byte *show_subpackets;
int rfc2440_text;
unsigned int min_rsa_length; /* Used for compliance checks. */
/* If true, let write failures on the status-fd exit the process. */
int exit_on_status_write_error;
/* If > 0, limit the number of card insertion prompts to this
value. */
int limit_card_insert_tries;
/* The list of --assert-signer option values. Note: The values are
* modify to be uppercase if they represent a fingerrint */
strlist_t assert_signer_list;
/* A single string with the comma delimited args from
* --assert-pubkey_algo. */
char *assert_pubkey_algos;
struct
{
/* If set, require an 0x19 backsig to be present on signatures
made by signing subkeys. If not set, a missing backsig is not
an error (but an invalid backsig still is). */
unsigned int require_cross_cert:1;
unsigned int use_embedded_filename:1;
unsigned int utf8_filename:1;
unsigned int dsa2:1;
unsigned int allow_old_cipher_algos:1;
unsigned int allow_weak_digest_algos:1;
unsigned int allow_weak_key_signatures:1;
unsigned int large_rsa:1;
unsigned int disable_signer_uid:1;
unsigned int include_key_block:1;
unsigned int auto_key_import:1;
/* Flag to enable experimental features from RFC4880bis. */
unsigned int rfc4880bis:1;
/* Hack: --output is not given but OUTFILE was temporary set to "-". */
unsigned int dummy_outfile:1;
/* Force the use of the OpenPGP card and do not allow the use of
* another card. */
unsigned int use_only_openpgp_card:1;
unsigned int full_timestrings:1;
/* Force signing keys even if a key signature already exists. */
unsigned int force_sign_key:1;
/* On key generation do not set the ownertrust. */
unsigned int no_auto_trust_new_key:1;
/* The next flag is set internally iff IMPORT_SELF_SIGS_ONLY has
* been set by the user and is not the default value. */
unsigned int expl_import_self_sigs_only:1;
/* The next flag is set internally iff IMPORT_CLEAN has
* been set by the user and is not the default value. */
unsigned int expl_import_clean:1;
/* Fail if an operation can't be done in the requested compliance
* mode. */
unsigned int require_compliance:1;
unsigned int require_pqc_encryption:1;
} flags;
/* Linked list of ways to find a key if the key isn't on the local
keyring. */
struct akl
{
enum {
AKL_NODEFAULT,
AKL_LOCAL,
AKL_CERT,
AKL_PKA,
AKL_DANE,
AKL_WKD,
AKL_LDAP,
AKL_NTDS,
AKL_KEYSERVER,
AKL_SPEC
} type;
keyserver_spec_t spec;
struct akl *next;
} *auto_key_locate;
/* The value of --key-origin. See parse_key_origin(). */
int key_origin;
char *key_origin_url;
int passphrase_repeat;
int pinentry_mode;
int request_origin;
int unwrap_encryption;
int only_sign_text_ids;
int no_symkey_cache; /* Disable the cache used for --symmetric. */
int use_keyboxd; /* Use the external keyboxd as storage backend. */
/* Compatibility flags (COMPAT_FLAG_xxxx). */
unsigned int compat_flags;
} opt;
/* CTRL is used to keep some global variables we currently can't
avoid. Future concurrent versions of gpg will put it into a per
request structure CTRL. */
EXTERN_UNLESS_MAIN_MODULE
struct {
int in_auto_key_retrieve; /* True if we are doing an
auto_key_retrieve. */
/* Hack to store the last error. We currently need it because the
proc_packet machinery is not able to reliabale return error
codes. Thus for the --server purposes we store some of the error
codes here. FIXME! */
gpg_error_t lasterr;
/* Kludge to silence some warnings using --secret-key-list. */
int silence_parse_warnings;
} glo_ctrl;
#define DBG_PACKET_VALUE 1 /* debug packet reading/writing */
#define DBG_MPI_VALUE 2 /* debug mpi details */
#define DBG_CRYPTO_VALUE 4 /* debug crypto handling */
/* (may reveal sensitive data) */
#define DBG_FILTER_VALUE 8 /* debug internal filter handling */
#define DBG_IOBUF_VALUE 16 /* debug iobuf stuff */
#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_TRUST_VALUE 256 /* debug the trustdb */
#define DBG_HASHING_VALUE 512 /* debug hashing operations */
#define DBG_IPC_VALUE 1024 /* debug assuan communication */
#define DBG_CLOCK_VALUE 4096
#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */
#define DBG_EXTPROG_VALUE 16384 /* debug external program calls */
/* Tests for the debugging flags. */
#define DBG_PACKET (opt.debug & DBG_PACKET_VALUE)
#define DBG_MPI (opt.debug & DBG_MPI_VALUE)
#define DBG_CRYPTO (opt.debug & DBG_CRYPTO_VALUE)
#define DBG_FILTER (opt.debug & DBG_FILTER_VALUE)
#define DBG_CACHE (opt.debug & DBG_CACHE_VALUE)
#define DBG_TRUST (opt.debug & DBG_TRUST_VALUE)
#define DBG_HASHING (opt.debug & DBG_HASHING_VALUE)
#define DBG_IPC (opt.debug & DBG_IPC_VALUE)
#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE)
#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
#define DBG_EXTPROG (opt.debug & DBG_EXTPROG_VALUE)
/* FIXME: We need to check why we did not put this into opt. */
#define DBG_MEMORY memory_debug_mode
#define DBG_MEMSTAT memory_stat_debug_mode
EXTERN_UNLESS_MAIN_MODULE int memory_debug_mode;
EXTERN_UNLESS_MAIN_MODULE int memory_stat_debug_mode;
/* Compatibility flags */
#define COMPAT_PARALLELIZED 1 /* Use threaded hashing for signatures. */
#define COMPAT_T7014_OLD 2 /* Use initial T7014 test data. */
-/* Compliance test macors. */
+/* Compliance test macros. */
#define GNUPG (opt.compliance==CO_GNUPG || opt.compliance==CO_DE_VS)
#define RFC2440 (opt.compliance==CO_RFC2440)
#define RFC4880 (opt.compliance==CO_RFC4880)
#define PGP7 (opt.compliance==CO_PGP7)
#define PGP8 (opt.compliance==CO_PGP8)
#define PGPX (PGP7 || PGP8)
/* Various option flags. Note that there should be no common string
names between the IMPORT_ and EXPORT_ flags as they can be mixed in
the keyserver-options option. */
#define IMPORT_LOCAL_SIGS (1<<0)
#define IMPORT_REPAIR_PKS_SUBKEY_BUG (1<<1)
#define IMPORT_FAST (1<<2)
#define IMPORT_SHOW (1<<3)
#define IMPORT_MERGE_ONLY (1<<4)
#define IMPORT_MINIMAL (1<<5)
#define IMPORT_CLEAN (1<<6)
#define IMPORT_NO_SECKEY (1<<7)
#define IMPORT_KEEP_OWNERTTRUST (1<<8)
#define IMPORT_EXPORT (1<<9)
#define IMPORT_RESTORE (1<<10)
#define IMPORT_REPAIR_KEYS (1<<11)
#define IMPORT_DRY_RUN (1<<12)
#define IMPORT_SELF_SIGS_ONLY (1<<14)
#define IMPORT_COLLAPSE_UIDS (1<<15)
#define IMPORT_COLLAPSE_SUBKEYS (1<<16)
#define IMPORT_BULK (1<<17)
#define IMPORT_IGNORE_ATTRIBUTES (1<<18)
#define EXPORT_LOCAL_SIGS (1<<0)
#define EXPORT_ATTRIBUTES (1<<1)
#define EXPORT_SENSITIVE_REVKEYS (1<<2)
#define EXPORT_RESET_SUBKEY_PASSWD (1<<3)
#define EXPORT_MINIMAL (1<<5)
#define EXPORT_CLEAN (1<<6)
#define EXPORT_DANE_FORMAT (1<<7)
#define EXPORT_BACKUP (1<<10)
#define EXPORT_REVOCS (1<<11)
#define EXPORT_MODE1003 (1<<12)
#define EXPORT_REALCLEAN (1<<13)
#define LIST_SHOW_PHOTOS (1<<0)
#define LIST_SHOW_POLICY_URLS (1<<1)
#define LIST_SHOW_STD_NOTATIONS (1<<2)
#define LIST_SHOW_USER_NOTATIONS (1<<3)
#define LIST_SHOW_NOTATIONS (LIST_SHOW_STD_NOTATIONS|LIST_SHOW_USER_NOTATIONS)
#define LIST_SHOW_KEYSERVER_URLS (1<<4)
#define LIST_SHOW_UID_VALIDITY (1<<5)
#define LIST_SHOW_UNUSABLE_UIDS (1<<6)
#define LIST_SHOW_UNUSABLE_SUBKEYS (1<<7)
#define LIST_SHOW_KEYRING (1<<8)
#define LIST_SHOW_SIG_EXPIRE (1<<9)
#define LIST_SHOW_SIG_SUBPACKETS (1<<10)
#define LIST_SHOW_USAGE (1<<11)
#define LIST_SHOW_ONLY_FPR_MBOX (1<<12)
#define LIST_SORT_SIGS (1<<13)
#define LIST_SHOW_PREF (1<<14)
#define LIST_SHOW_PREF_VERBOSE (1<<15)
#define LIST_SHOW_UNUSABLE_SIGS (1<<16)
#define LIST_SHOW_X509_NOTATIONS (1<<17)
#define LIST_STORE_X509_NOTATIONS (1<<18)
#define LIST_SHOW_OWNERTRUST (1<<19)
#define VERIFY_SHOW_PHOTOS (1<<0)
#define VERIFY_SHOW_POLICY_URLS (1<<1)
#define VERIFY_SHOW_STD_NOTATIONS (1<<2)
#define VERIFY_SHOW_USER_NOTATIONS (1<<3)
#define VERIFY_SHOW_NOTATIONS (VERIFY_SHOW_STD_NOTATIONS|VERIFY_SHOW_USER_NOTATIONS)
#define VERIFY_SHOW_KEYSERVER_URLS (1<<4)
#define VERIFY_SHOW_UID_VALIDITY (1<<5)
#define VERIFY_SHOW_UNUSABLE_UIDS (1<<6)
#define VERIFY_SHOW_PRIMARY_UID_ONLY (1<<9)
#define KEYSERVER_HTTP_PROXY (1<<0)
#define KEYSERVER_TIMEOUT (1<<1)
#define KEYSERVER_ADD_FAKE_V3 (1<<2)
#define KEYSERVER_AUTO_KEY_RETRIEVE (1<<3)
#define KEYSERVER_HONOR_KEYSERVER_URL (1<<4)
#endif /*G10_OPTIONS_H*/
diff --git a/g10/packet.h b/g10/packet.h
index 459e38dda..e10baba4d 100644
--- a/g10/packet.h
+++ b/g10/packet.h
@@ -1,968 +1,968 @@
/* packet.h - OpenPGP packet definitions
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007 Free Software Foundation, Inc.
* Copyright (C) 2015 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef G10_PACKET_H
#define G10_PACKET_H
#include "../common/types.h"
#include "../common/iobuf.h"
#include "../common/strlist.h"
#include "dek.h"
#include "filter.h"
#include "../common/openpgpdefs.h"
#include "../common/userids.h"
#include "../common/util.h"
#define DEBUG_PARSE_PACKET 1
/* Maximum length of packets to avoid excessive memory allocation. */
#define MAX_KEY_PACKET_LENGTH (256 * 1024)
#define MAX_UID_PACKET_LENGTH ( 2 * 1024)
#define MAX_COMMENT_PACKET_LENGTH ( 64 * 1024)
#define MAX_ATTR_PACKET_LENGTH ( 16 * 1024*1024)
/* Constants to allocate static MPI arrays. */
#define PUBKEY_MAX_NPKEY OPENPGP_MAX_NPKEY
#define PUBKEY_MAX_NSKEY OPENPGP_MAX_NSKEY
#define PUBKEY_MAX_NSIG OPENPGP_MAX_NSIG
#define PUBKEY_MAX_NENC OPENPGP_MAX_NENC
/* Usage flags */
#define PUBKEY_USAGE_SIG GCRY_PK_USAGE_SIGN /* Good for signatures. */
#define PUBKEY_USAGE_ENC GCRY_PK_USAGE_ENCR /* Good for encryption. */
#define PUBKEY_USAGE_CERT GCRY_PK_USAGE_CERT /* Also good to certify keys.*/
#define PUBKEY_USAGE_AUTH GCRY_PK_USAGE_AUTH /* Good for authentication. */
#define PUBKEY_USAGE_UNKNOWN GCRY_PK_USAGE_UNKN /* Unknown usage flag. */
#define PUBKEY_USAGE_NONE 256 /* No usage given. */
#if (GCRY_PK_USAGE_SIGN | GCRY_PK_USAGE_ENCR | GCRY_PK_USAGE_CERT \
| GCRY_PK_USAGE_AUTH | GCRY_PK_USAGE_UNKN) >= 256
# error Please choose another value for PUBKEY_USAGE_NONE
#endif
#define PUBKEY_USAGE_GROUP 512 /* Group flag. */
#define PUBKEY_USAGE_RENC 1024 /* Restricted encryption. */
#define PUBKEY_USAGE_TIME 2048 /* Timestamp use. */
/* Bitflags to convey hints on what kind of signature is created. */
#define SIGNHINT_KEYSIG 1
#define SIGNHINT_SELFSIG 2
#define SIGNHINT_ADSK 4
/* Helper macros. */
#define is_RSA(a) ((a)==PUBKEY_ALGO_RSA || (a)==PUBKEY_ALGO_RSA_E \
|| (a)==PUBKEY_ALGO_RSA_S )
#define is_ELGAMAL(a) ((a)==PUBKEY_ALGO_ELGAMAL_E)
#define is_DSA(a) ((a)==PUBKEY_ALGO_DSA)
/* A pointer to the packet object. */
typedef struct packet_struct PACKET;
/* PKT_GPG_CONTROL types */
typedef enum {
CTRLPKT_CLEARSIGN_START = 1,
CTRLPKT_PIPEMODE = 2,
CTRLPKT_PLAINTEXT_MARK =3
} ctrlpkttype_t;
typedef enum {
PREFTYPE_NONE = 0,
PREFTYPE_SYM = 1,
PREFTYPE_HASH = 2,
PREFTYPE_ZIP = 3,
PREFTYPE_AEAD = 4
} preftype_t;
typedef struct {
byte type;
byte value;
} prefitem_t;
/* A string-to-key specifier as defined in RFC 4880, Section 3.7. */
typedef struct
{
int mode; /* Must be an integer due to the GNU modes 1001 et al. */
byte hash_algo;
byte salt[8];
/* The *coded* (i.e., the serialized version) iteration count. */
u32 count;
} STRING2KEY;
/* A symmetric-key encrypted session key packet as defined in RFC
4880, Section 5.3. All fields are serialized. */
typedef struct {
/* We support version 4 (rfc4880) and 5 (rfc4880bis). */
byte version;
/* The cipher algorithm used to encrypt the session key. Note that
* this may be different from the algorithm that is used to encrypt
* bulk data. */
byte cipher_algo;
/* The AEAD algorithm or 0 for CFB encryption. */
byte aead_algo;
/* The string-to-key specifier. */
STRING2KEY s2k;
/* The length of SESKEY in bytes or 0 if this packet does not
encrypt a session key. (In the latter case, the results of the
S2K function on the password is the session key. See RFC 4880,
Section 5.3.) */
byte seskeylen;
/* The session key as encrypted by the S2K specifier. For AEAD this
* includes the nonce and the authentication tag. */
byte seskey[1];
} PKT_symkey_enc;
/* A public-key encrypted session key packet as defined in RFC 4880,
Section 5.1. All fields are serialized. */
typedef struct {
/* The 64-bit keyid. */
u32 keyid[2];
/* The packet's version. Currently, only version 3 is defined. */
byte version;
/* The algorithm used for the public key encryption scheme. */
byte pubkey_algo;
/* The session key algorithm used by some pubkey algos. */
byte seskey_algo;
/* Whether to hide the key id. This value is not directly
serialized. */
byte throw_keyid;
/* The session key. */
gcry_mpi_t data[PUBKEY_MAX_NENC];
} PKT_pubkey_enc;
/* An object to build a list of public-key encrypted session key. */
struct pubkey_enc_list
{
struct pubkey_enc_list *next;
u32 keyid[2];
int pubkey_algo;
int seskey_algo;
int result;
gcry_mpi_t data[PUBKEY_MAX_NENC];
};
/* A one-pass signature packet as defined in RFC 4880, Section
5.4. All fields are serialized. */
typedef struct {
u32 keyid[2]; /* The 64-bit keyid */
/* The signature's classification (RFC 4880, Section 5.2.1). */
byte sig_class;
byte digest_algo; /* algorithm used for digest */
byte pubkey_algo; /* algorithm used for public key scheme */
/* A message can be signed by multiple keys. In this case, there
are n one-pass signature packets before the message to sign and
n signatures packets after the message. It is conceivable that
someone wants to not only sign the message, but all of the
signatures. Now we need to distinguish between signing the
message and signing the message plus the surrounding
signatures. This is the point of this flag. If set, it means:
I sign all of the data starting at the next packet. */
byte last;
} PKT_onepass_sig;
/* A v4 OpenPGP signature has a hashed and unhashed area containing
co-called signature subpackets (RFC 4880, Section 5.2.3). These
areas are described by this data structure. Use enum_sig_subpkt to
parse this area. */
typedef struct {
size_t size; /* allocated */
size_t len; /* used (serialized) */
byte data[1]; /* the serialized subpackes (serialized) */
} subpktarea_t;
/* The in-memory representation of a designated revoker signature
subpacket (RFC 4880, Section 5.2.3.15). */
struct revocation_key {
/* A bit field. 0x80 must be set. 0x40 means this information is
sensitive (and should not be uploaded to a keyserver by
default). */
byte class;
/* The public-key algorithm ID. */
byte algid;
/* The length of the fingerprint. */
byte fprlen;
/* The fingerprint of the authorized key. */
byte fpr[MAX_FINGERPRINT_LEN];
};
/* A signature packet (RFC 4880, Section 5.2). Only a subset of these
fields are directly serialized (these are marked as such); the rest
are read from the subpackets, which are not synthesized when
serializing this data structure (i.e., when using build_packet()).
Instead, the subpackets must be created by hand. */
typedef struct
{
struct
{
unsigned checked:1; /* Signature has been checked. */
unsigned valid:1; /* Signature is good (if checked is set). */
unsigned chosen_selfsig:1; /* A selfsig that is the chosen one. */
unsigned unknown_critical:1;
unsigned exportable:1;
unsigned revocable:1;
unsigned policy_url:1; /* At least one policy URL is present */
unsigned notation:1; /* At least one notation is present */
unsigned pref_ks:1; /* At least one preferred keyserver is present */
unsigned key_block:1; /* A key block subpacket is present. */
unsigned expired:1;
} flags;
/* The key that allegedly generated this signature. (Directly
serialized in v3 sigs; for v4 sigs, this must be explicitly added
as an issuer subpacket (5.2.3.5.) */
u32 keyid[2];
/* When the signature was made (seconds since the Epoch). (Directly
serialized in v3 sigs; for v4 sigs, this must be explicitly added
as a signature creation time subpacket (5.2.3.4).) */
u32 timestamp;
u32 expiredate; /* Expires at this date or 0 if not at all. */
/* The serialization format used / to use. If 0, then defaults to
version 3. (Serialized.) */
byte version;
/* The signature type. (See RFC 4880, Section 5.2.1.) */
byte sig_class;
/* Algorithm used for public key scheme (e.g., PUBKEY_ALGO_RSA).
(Serialized.) */
byte pubkey_algo;
/* Algorithm used for digest (e.g., DIGEST_ALGO_SHA1).
(Serialized.) */
byte digest_algo;
byte trust_depth;
byte trust_value;
const byte *trust_regexp;
struct revocation_key *revkey;
int numrevkeys;
- int help_counter; /* Used internally bu some functions. */
+ int help_counter; /* Used internally by some functions. */
char *signers_uid; /* Malloced value of the SIGNERS_UID
* subpacket or NULL. This string has
* already been sanitized. */
subpktarea_t *hashed; /* All subpackets with hashed data (v4 only). */
subpktarea_t *unhashed; /* Ditto for unhashed data. */
/* First 2 bytes of the digest. (Serialized. Note: this is not
automatically filled in when serializing a signature!) */
byte digest_start[2];
/* The signature. (Serialized.) */
gcry_mpi_t data[PUBKEY_MAX_NSIG];
/* The message digest and its length (in bytes). Note the maximum
digest length is 512 bits (64 bytes). If DIGEST_LEN is 0, then
the digest's value has not been saved here. */
byte digest[512 / 8];
int digest_len;
} PKT_signature;
#define ATTRIB_IMAGE 1
/* This is the cooked form of attributes. */
struct user_attribute {
byte type;
const byte *data;
u32 len;
};
/* A user id (RFC 4880, Section 5.11) or a user attribute packet (RFC
4880, Section 5.12). Only a subset of these fields are directly
serialized (these are marked as such); the rest are read from the
self-signatures in merge_keys_and_selfsig()). */
typedef struct
{
int ref; /* reference counter */
/* The length of NAME. */
int len;
struct user_attribute *attribs;
int numattribs;
/* If this is not NULL, the packet is a user attribute rather than a
user id (See RFC 4880 5.12). (Serialized.) */
byte *attrib_data;
/* The length of ATTRIB_DATA. */
unsigned long attrib_len;
byte *namehash;
u16 help_key_usage;
u32 help_key_expire;
int help_full_count;
int help_marginal_count;
u32 expiredate; /* expires at this date or 0 if not at all */
prefitem_t *prefs; /* list of preferences (may be NULL)*/
u32 created; /* according to the self-signature */
u32 keyupdate; /* From the ring trust packet. */
char *updateurl; /* NULL or the URL of the last update origin. */
byte keyorg; /* From the ring trust packet. */
byte selfsigversion;
struct
{
unsigned int mdc:1;
unsigned int aead:1;
unsigned int ks_modify:1;
unsigned int compacted:1;
unsigned int primary:2; /* 2 if set via the primary flag, 1 if calculated */
/* Note that this flag is set in a
* keyblock at max for one User ID and for
* one User Attribute per keyblock. */
unsigned int revoked:1;
unsigned int expired:1;
} flags;
char *mbox; /* NULL or the result of mailbox_from_userid. */
/* The text contained in the user id packet, which is normally the
* name and email address of the key holder (See RFC 4880 5.11).
* (Serialized.). For convenience an extra Nul is always appended. */
char name[1];
} PKT_user_id;
struct revoke_info
{
/* revoked at this date */
u32 date;
/* the keyid of the revoking key (selfsig or designated revoker) */
u32 keyid[2];
/* the algo of the revoking key */
byte algo;
};
/* Information pertaining to secret keys. */
struct seckey_info
{
unsigned int is_protected:1; /* The secret info is protected and must */
/* be decrypted before use, the protected */
/* MPIs are simply (void*) pointers to memory */
/* and should never be passed to a mpi_xxx() */
unsigned int sha1chk:1; /* SHA1 is used instead of a 16 bit checksum */
u16 csum; /* Checksum for old protection modes. */
byte algo; /* Cipher used to protect the secret information. */
STRING2KEY s2k; /* S2K parameter. */
byte ivlen; /* Used length of the IV. */
byte iv[16]; /* Initialization vector for CFB mode. */
};
/****************
* The in-memory representation of a public key (RFC 4880, Section
* 5.5). Note: this structure contains significantly more information
* than is contained in an OpenPGP public key packet. This
* information is derived from the self-signed signatures (by
* merge_keys_and_selfsig()) and is ignored when serializing the
* packet. The fields that are actually written out when serializing
* this packet are marked as accordingly.
*
* We assume that secret keys have the same number of parameters as
* the public key and that the public parameters are the first items
* in the PKEY array. Thus NPKEY is always less than NSKEY and it is
* possible to compare the secret and public keys by comparing the
* first NPKEY elements of the PKEY array. Note that since GnuPG 2.1
* we don't use secret keys anymore directly because they are managed
* by gpg-agent. However for parsing OpenPGP key files we need a way
* to temporary store those secret keys. We do this by putting them
* into the public key structure and extending the PKEY field to NSKEY
* elements; the extra secret key information are stored in the
* SECKEY_INFO field.
*/
typedef struct
{
/* When the key was created. (Serialized.) */
u32 timestamp;
u32 expiredate; /* expires at this date or 0 if not at all */
u32 max_expiredate; /* must not expire past this date */
struct revoke_info revoked;
/* An OpenPGP packet consists of a header and a body. This is the
size of the header. If this is 0, an appropriate size is
automatically chosen based on the size of the body.
(Serialized.) */
byte hdrbytes;
/* The serialization format. If 0, the default version (4) is used
when serializing. (Serialized.) */
byte version;
byte selfsigversion; /* highest version of all of the self-sigs */
/* The public key algorithm. (Serialized.) */
byte pubkey_algo;
u16 pubkey_usage; /* carries the usage info. */
byte req_usage; /* hack to pass a request to getkey() */
byte fprlen; /* 0 or length of FPR. */
u32 has_expired; /* set to the expiration date if expired */
/* keyid of the primary key. Never access this value directly.
Instead, use pk_main_keyid(). */
u32 main_keyid[2];
/* keyid of this key. Never access this value directly! Instead,
use pk_keyid(). */
u32 keyid[2];
/* Fingerprint of the key. Only valid if FPRLEN is not 0. */
byte fpr[MAX_FINGERPRINT_LEN];
prefitem_t *prefs; /* list of preferences (may be NULL) */
struct
{
unsigned int mdc:1; /* MDC feature set. */
unsigned int aead:1; /* AEAD feature set. */
unsigned int disabled_valid:1;/* The next flag is valid. */
unsigned int disabled:1; /* The key has been disabled. */
unsigned int primary:1; /* This is a primary key. */
unsigned int revoked:2; /* Key has been revoked.
1 = revoked by the owner
2 = revoked by designated revoker. */
unsigned int maybe_revoked:1; /* A designated revocation is
present, but without the key to
check it. */
unsigned int valid:1; /* Key (especially subkey) is valid. */
unsigned int dont_cache:1; /* Do not cache this key. */
unsigned int backsig:2; /* 0=none, 1=bad, 2=good. */
unsigned int serialno_valid:1;/* SERIALNO below is valid. */
unsigned int exact:1; /* Found via exact (!) search. */
} flags;
PKT_user_id *user_id; /* If != NULL: found by that uid. */
struct revocation_key *revkey;
int numrevkeys;
u32 trust_timestamp;
byte trust_depth;
byte trust_value;
byte keyorg; /* From the ring trust packet. */
u32 keyupdate; /* From the ring trust packet. */
char *updateurl; /* NULL or the URL of the last update origin. */
const byte *trust_regexp;
char *serialno; /* Malloced hex string or NULL if it is
likely not on a card. See also
flags.serialno_valid. */
/* If not NULL this malloced structure describes a secret key.
(Serialized.) */
struct seckey_info *seckey_info;
/* The public key. Contains pubkey_get_npkey (pubkey_algo) +
pubkey_get_nskey (pubkey_algo) MPIs. (If pubkey_get_npkey
returns 0, then the algorithm is not understood and the PKEY
contains a single opaque MPI.) (Serialized.) */
gcry_mpi_t pkey[PUBKEY_MAX_NSKEY]; /* Right, NSKEY elements. */
} PKT_public_key;
/* Evaluates as true if the pk is disabled, and false if it isn't. If
there is no disable value cached, fill one in. */
#define pk_is_disabled(a) \
(((a)->flags.disabled_valid)? \
((a)->flags.disabled):(cache_disabled_value(ctrl,(a))))
typedef struct {
int len; /* length of data */
char data[1];
} PKT_comment;
/* A compression packet (RFC 4880, Section 5.6). */
typedef struct {
/* Not used. */
u32 len;
/* Whether the serialized version of the packet used / should use
the new format. */
byte new_ctb;
/* The compression algorithm. */
byte algorithm;
/* An iobuf holding the data to be decompressed. (This is not used
for compression!) */
iobuf_t buf;
} PKT_compressed;
/* A symmetrically encrypted data packet (RFC 4880, Section 5.7) or a
symmetrically encrypted integrity protected data packet (Section
5.13) */
typedef struct {
/* Remaining length of encrypted data. */
u32 len;
/* When encrypting in CFB mode, the first block size bytes of data
* are random data and the following 2 bytes are copies of the last
* two bytes of the random data (RFC 4880, Section 5.7). This
* provides a simple check that the key is correct. EXTRALEN is the
* size of this extra data or, in AEAD mode, the length of the
* headers and the tags. This is used by build_packet when writing
* out the packet's header. */
int extralen;
/* Whether the serialized version of the packet used / should use
the new format. */
byte new_ctb;
/* Whether the packet has an indeterminate length (old format) or
was encoded using partial body length headers (new format).
Note: this is ignored when encrypting. */
byte is_partial;
/* If 0, MDC is disabled. Otherwise, the MDC method that was used
(only DIGEST_ALGO_SHA1 has ever been defined). */
byte mdc_method;
/* If 0, AEAD is not used. Otherwise, the used AEAD algorithm.
* MDC_METHOD (above) shall be zero if AEAD is used. */
byte aead_algo;
/* The cipher algo for/from the AEAD packet. 0 for other encryption
* packets. */
byte cipher_algo;
/* The chunk byte from the AEAD packet. */
byte chunkbyte;
/* An iobuf holding the data to be decrypted. (This is not used for
encryption!) */
iobuf_t buf;
} PKT_encrypted;
typedef struct {
byte hash[20];
} PKT_mdc;
/* Subtypes for the ring trust packet. */
#define RING_TRUST_SIG 0 /* The classical signature cache. */
#define RING_TRUST_KEY 1 /* A KEYORG on a primary key. */
#define RING_TRUST_UID 2 /* A KEYORG on a user id. */
/* The local only ring trust packet which OpenPGP declares as
* implementation defined. GnuPG uses this to cache signature
* verification status and since 2.1.18 also to convey information
* about the origin of a key. Note that this packet is not part
* struct packet_struct because we use it only local in the packet
* parser and builder. */
typedef struct {
unsigned int trustval;
unsigned int sigcache;
unsigned char subtype; /* The subtype of this ring trust packet. */
unsigned char keyorg; /* The origin of the key (KEYORG_*). */
u32 keyupdate; /* The wall time the key was last updated. */
char *url; /* NULL or the URL of the source. */
} PKT_ring_trust;
/* A plaintext packet (see RFC 4880, 5.9). */
typedef struct {
/* The length of data in BUF or 0 if unknown. */
u32 len;
/* A buffer containing the data stored in the packet's body. */
iobuf_t buf;
byte new_ctb;
byte is_partial; /* partial length encoded */
/* The data's formatting. This is either 'b', 't', 'u', 'l' or '1'
(however, the last two are deprecated). */
int mode;
u32 timestamp;
/* The name of the file. This can be at most 255 characters long,
since namelen is just a byte in the serialized format. */
int namelen;
char name[1];
} PKT_plaintext;
typedef struct {
int control;
size_t datalen;
char data[1];
} PKT_gpg_control;
/* combine all packets into a union */
struct packet_struct {
pkttype_t pkttype;
union {
void *generic;
PKT_symkey_enc *symkey_enc; /* PKT_SYMKEY_ENC */
PKT_pubkey_enc *pubkey_enc; /* PKT_PUBKEY_ENC */
PKT_onepass_sig *onepass_sig; /* PKT_ONEPASS_SIG */
PKT_signature *signature; /* PKT_SIGNATURE */
PKT_public_key *public_key; /* PKT_PUBLIC_[SUB]KEY */
PKT_public_key *secret_key; /* PKT_SECRET_[SUB]KEY */
PKT_comment *comment; /* PKT_COMMENT */
PKT_user_id *user_id; /* PKT_USER_ID */
PKT_compressed *compressed; /* PKT_COMPRESSED */
PKT_encrypted *encrypted; /* PKT_ENCRYPTED[_MDC] */
PKT_mdc *mdc; /* PKT_MDC */
PKT_plaintext *plaintext; /* PKT_PLAINTEXT */
PKT_gpg_control *gpg_control; /* PKT_GPG_CONTROL */
} pkt;
};
#define init_packet(a) do { (a)->pkttype = 0; \
(a)->pkt.generic = NULL; \
} while(0)
/* A notation. See RFC 4880, Section 5.2.3.16. */
struct notation
{
/* The notation's name. */
char *name;
/* If the notation is human readable, then the value is stored here
as a NUL-terminated string. If it is not human readable a human
readable approximation of the binary value _may_ be stored
here. */
char *value;
/* Sometimes we want to %-expand the value. In these cases, we save
that transformed value here. */
char *altvalue;
/* If the notation is not human readable or the function does not
want to distinguish that, then the value is stored here. */
unsigned char *bdat;
/* The amount of data stored in BDAT.
Note: if this is 0 and BDAT is NULL, this does not necessarily
mean that the value is human readable. It could be that we have
a 0-length value. To determine whether the notation is human
readable, always check if VALUE is not NULL. This works, because
if a human-readable value has a length of 0, we will still
allocate space for the NUL byte. */
size_t blen;
struct
{
/* The notation is critical. */
unsigned int critical:1;
/* The notation is human readable. */
unsigned int human:1;
/* The notation should be deleted. */
unsigned int ignore:1;
} flags;
/* A field to facilitate creating a list of notations. */
struct notation *next;
};
typedef struct notation *notation_t;
/*-- mainproc.c --*/
void reset_literals_seen(void);
int proc_packets (ctrl_t ctrl, void *ctx, iobuf_t a );
int proc_signature_packets (ctrl_t ctrl, void *ctx, iobuf_t a,
strlist_t signedfiles, const char *sigfile );
int proc_signature_packets_by_fd (ctrl_t ctrl, void *anchor, IOBUF a,
gnupg_fd_t signed_data_fd);
int proc_encryption_packets (ctrl_t ctrl, void *ctx, iobuf_t a);
int list_packets( iobuf_t a );
const byte *issuer_fpr_raw (PKT_signature *sig, size_t *r_len);
char *issuer_fpr_string (PKT_signature *sig);
/*-- parse-packet.c --*/
void register_known_notation (const char *string);
/* Sets the packet list mode to MODE (i.e., whether we are dumping a
packet or not). Returns the current mode. This allows for
temporarily suspending dumping by doing the following:
int saved_mode = set_packet_list_mode (0);
...
set_packet_list_mode (saved_mode);
*/
int set_packet_list_mode( int mode );
/* A context used with parse_packet. */
struct parse_packet_ctx_s
{
iobuf_t inp; /* The input stream with the packets. */
struct packet_struct last_pkt; /* The last parsed packet. */
int free_last_pkt; /* Indicates that LAST_PKT must be freed. */
int skip_meta; /* Skip ring trust packets. */
unsigned int n_parsed_packets; /* Number of parsed packets. */
};
typedef struct parse_packet_ctx_s *parse_packet_ctx_t;
#define init_parse_packet(a,i) do { \
(a)->inp = (i); \
(a)->last_pkt.pkttype = 0; \
(a)->last_pkt.pkt.generic= NULL;\
(a)->free_last_pkt = 0; \
(a)->skip_meta = 0; \
(a)->n_parsed_packets = 0; \
} while (0)
#define deinit_parse_packet(a) do { \
if ((a)->free_last_pkt) \
free_packet (NULL, (a)); \
} while (0)
#if DEBUG_PARSE_PACKET
/* There are debug functions and should not be used directly. */
int dbg_search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t *retpos, int with_uid,
const char* file, int lineno );
int dbg_parse_packet (parse_packet_ctx_t ctx, PACKET *ret_pkt,
const char *file, int lineno);
int dbg_copy_all_packets( iobuf_t inp, iobuf_t out,
const char* file, int lineno );
int dbg_copy_some_packets( iobuf_t inp, iobuf_t out, off_t stopoff,
const char* file, int lineno );
int dbg_skip_some_packets( iobuf_t inp, unsigned n,
const char* file, int lineno );
#define search_packet( a,b,c,d ) \
dbg_search_packet( (a), (b), (c), (d), __FILE__, __LINE__ )
#define parse_packet( a, b ) \
dbg_parse_packet( (a), (b), __FILE__, __LINE__ )
#define copy_all_packets( a,b ) \
dbg_copy_all_packets((a),(b), __FILE__, __LINE__ )
#define copy_some_packets( a,b,c ) \
dbg_copy_some_packets((a),(b),(c), __FILE__, __LINE__ )
#define skip_some_packets( a,b ) \
dbg_skip_some_packets((a),(b), __FILE__, __LINE__ )
#else
/* Return the next valid OpenPGP packet in *PKT. (This function will
* skip any packets whose type is 0.) CTX must have been setup prior to
* calling this function.
*
* Returns 0 on success, -1 if EOF is reached, and an error code
* otherwise. In the case of an error, the packet in *PKT may be
* partially constructed. As such, even if there is an error, it is
* necessary to free *PKT to avoid a resource leak. To detect what
* has been allocated, clear *PKT before calling this function. */
int parse_packet (parse_packet_ctx_t ctx, PACKET *pkt);
/* Return the first OpenPGP packet in *PKT that contains a key (either
* a public subkey, a public key, a secret subkey or a secret key) or,
* if WITH_UID is set, a user id.
*
* Saves the position in the pipeline of the start of the returned
* packet (according to iobuf_tell) in RETPOS, if it is not NULL.
*
* The return semantics are the same as parse_packet. */
int search_packet (parse_packet_ctx_t ctx, PACKET *pkt,
off_t *retpos, int with_uid);
/* Copy all packets (except invalid packets, i.e., those with a type
* of 0) from INP to OUT until either an error occurs or EOF is
* reached.
*
* Returns -1 when end of file is reached or an error code, if an
* error occurred. (Note: this function never returns 0, because it
* effectively keeps going until it gets an EOF.) */
int copy_all_packets (iobuf_t inp, iobuf_t out );
/* Like copy_all_packets, but stops at the first packet that starts at
* or after STOPOFF (as indicated by iobuf_tell).
*
* Example: if STOPOFF is 100, the first packet in INP goes from
* 0 to 110 and the next packet starts at offset 111, then the packet
* starting at offset 0 will be completely processed (even though it
* extends beyond STOPOFF) and the packet starting at offset 111 will
* not be processed at all. */
int copy_some_packets (iobuf_t inp, iobuf_t out, off_t stopoff);
/* Skips the next N packets from INP.
*
* If parsing a packet returns an error code, then the function stops
* immediately and returns the error code. Note: in the case of an
* error, this function does not indicate how many packets were
* successfully processed. */
int skip_some_packets (iobuf_t inp, unsigned int n);
#endif
/* Parse a signature packet and store it in *SIG.
The signature packet is read from INP. The OpenPGP header (the tag
and the packet's length) have already been read; the next byte read
from INP should be the first byte of the packet's contents. The
packet's type (as extract from the tag) must be passed as PKTTYPE
and the packet's length must be passed as PKTLEN. This is used as
the upper bound on the amount of data read from INP. If the packet
is shorter than PKTLEN, the data at the end will be silently
skipped. If an error occurs, an error code will be returned. -1
means the EOF was encountered. 0 means parsing was successful. */
int parse_signature( iobuf_t inp, int pkttype, unsigned long pktlen,
PKT_signature *sig );
/* Given a signature packet, either:
*
* - test whether there are any subpackets with the critical bit set
* that we don't understand,
*
* - list the subpackets, or,
*
* - find a subpacket with a specific type.
*
* The WANT_HASHED flag indicates that the hashed area shall be
* considered.
*
* REQTYPE indicates the type of operation.
*
* If REQTYPE is SIGSUBPKT_TEST_CRITICAL, then this function checks
* whether there are any subpackets that have the critical bit and
* which GnuPG cannot handle. If GnuPG understands all subpackets
* whose critical bit is set, then this function returns simply
* returns SUBPKTS. If there is a subpacket whose critical bit is set
* and which GnuPG does not understand, then this function returns
* NULL and, if START is not NULL, sets *START to the 1-based index of
* the subpacket that violates the constraint.
*
* If REQTYPE is SIGSUBPKT_LIST_HASHED or SIGSUBPKT_LIST_UNHASHED, the
* packets are dumped. Note: if REQTYPE is SIGSUBPKT_LIST_HASHED,
* this function does not check whether the hash is correct; this is
* merely an indication of the section that the subpackets came from.
*
* If REQTYPE is anything else, then this function interprets the
* values as a subpacket type and looks for the first subpacket with
* that type. If such a packet is found, *CRITICAL (if not NULL) is
* set if the critical bit was set, *RET_N is set to the offset of the
* subpacket's content within the SUBPKTS buffer, *START is set to the
* 1-based index of the subpacket within the buffer, and returns
* &SUBPKTS[*RET_N].
*
* *START is the number of initial subpackets to not consider. Thus,
* if *START is 2, then the first 2 subpackets are ignored.
*/
const byte *enum_sig_subpkt (PKT_signature *sig, int want_hashed,
sigsubpkttype_t reqtype,
size_t *ret_n, int *start, int *critical );
/* Shorthand for:
*
* enum_sig_subpkt (sig, want_hashed, reqtype, ret_n, NULL, NULL);
*/
const byte *parse_sig_subpkt (PKT_signature *sig, int want_hashed,
sigsubpkttype_t reqtype,
size_t *ret_n );
/* This calls parse_sig_subpkt first on the hashed signature area in
* SIG and then, if that returns NULL, calls parse_sig_subpkt on the
* unhashed subpacket area in SIG. */
const byte *parse_sig_subpkt2 (PKT_signature *sig, sigsubpkttype_t reqtype);
/* Returns whether the N byte large buffer BUFFER is sufficient to
hold a subpacket of type TYPE. Note: the buffer refers to the
contents of the subpacket (not the header) and it must already be
initialized: for some subpackets, it checks some internal
constraints.
Returns 0 if the size is acceptable. Returns -2 if the buffer is
definitely too short. To check for an error, check whether the
return value is less than 0. */
int parse_one_sig_subpkt( const byte *buffer, size_t n, int type );
/* Looks for revocation key subpackets (see RFC 4880 5.2.3.15) in the
hashed area of the signature packet. Any that are found are added
to SIG->REVKEY and SIG->NUMREVKEYS is updated appropriately. */
void parse_revkeys(PKT_signature *sig);
/* Extract the attributes from the buffer at UID->ATTRIB_DATA and
update UID->ATTRIBS and UID->NUMATTRIBS accordingly. */
int parse_attribute_subpkts(PKT_user_id *uid);
/* Set the UID->NAME field according to the attributes. MAX_NAMELEN
must be at least 71. */
void make_attribute_uidname(PKT_user_id *uid, size_t max_namelen);
/* Allocate and initialize a new GPG control packet. DATA is the data
to save in the packet. */
PACKET *create_gpg_control ( ctrlpkttype_t type,
const byte *data,
size_t datalen );
/*-- build-packet.c --*/
gpg_error_t build_keyblock_image (kbnode_t keyblock, iobuf_t *r_iobuf);
int build_packet (iobuf_t out, PACKET *pkt);
gpg_error_t build_packet_and_meta (iobuf_t out, PACKET *pkt);
gpg_error_t gpg_mpi_write (iobuf_t out, gcry_mpi_t a, unsigned int *t_nwritten);
gpg_error_t gpg_mpi_write_opaque_nohdr (iobuf_t out, gcry_mpi_t a);
gpg_error_t gpg_mpi_write_opaque_32 (iobuf_t out, gcry_mpi_t a,
unsigned int *r_nwritten);
u32 calc_packet_length( PACKET *pkt );
void build_sig_subpkt( PKT_signature *sig, sigsubpkttype_t type,
const byte *buffer, size_t buflen );
void build_sig_subpkt_from_sig (PKT_signature *sig, PKT_public_key *pksk,
unsigned int signhints);
int delete_sig_subpkt(subpktarea_t *buffer, sigsubpkttype_t type );
void build_attribute_subpkt(PKT_user_id *uid,byte type,
const void *buf,u32 buflen,
const void *header,u32 headerlen);
struct notation *string_to_notation(const char *string,int is_utf8);
struct notation *blob_to_notation(const char *name,
const char *data, size_t len);
struct notation *sig_to_notation(PKT_signature *sig);
struct notation *search_sig_notations (PKT_signature *sig, const char *name);
void free_notation (struct notation *notation);
/*-- free-packet.c --*/
void free_symkey_enc( PKT_symkey_enc *enc );
void free_pubkey_enc( PKT_pubkey_enc *enc );
void free_seckey_enc( PKT_signature *enc );
void release_public_key_parts( PKT_public_key *pk );
void free_public_key( PKT_public_key *key );
void free_attributes(PKT_user_id *uid);
void free_user_id( PKT_user_id *uid );
void free_comment( PKT_comment *rem );
void free_packet (PACKET *pkt, parse_packet_ctx_t parsectx);
prefitem_t *copy_prefs (const prefitem_t *prefs);
PKT_public_key *copy_public_key_basics (PKT_public_key *d, PKT_public_key *s);
PKT_public_key *copy_public_key( PKT_public_key *d, PKT_public_key *s );
PKT_signature *copy_signature( PKT_signature *d, PKT_signature *s );
PKT_user_id *scopy_user_id (PKT_user_id *sd );
int cmp_public_keys( PKT_public_key *a, PKT_public_key *b );
int cmp_signatures( PKT_signature *a, PKT_signature *b );
int cmp_user_ids( PKT_user_id *a, PKT_user_id *b );
/*-- sig-check.c --*/
/* Check a signature. This is shorthand for check_signature2 with
the unnamed arguments passed as NULL. */
int check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest);
/* Check a signature. Looks up the public key from the key db. (If
* R_PK is not NULL, it is stored at RET_PK.) DIGEST contains a
* valid hash context that already includes the signed data. This
* function adds the relevant meta-data to the hash before finalizing
* it and verifying the signature. FOCRED_PK is usually NULL. */
gpg_error_t check_signature2 (ctrl_t ctrl,
PKT_signature *sig, gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
PKT_public_key *forced_pk,
u32 *r_expiredate, int *r_expired, int *r_revoked,
PKT_public_key **r_pk);
/*-- pubkey-enc.c --*/
gpg_error_t get_session_key (ctrl_t ctrl, struct pubkey_enc_list *k, DEK *dek);
gpg_error_t get_override_session_key (DEK *dek, const char *string);
/*-- compress.c --*/
int handle_compressed (ctrl_t ctrl, void *ctx, PKT_compressed *cd,
int (*callback)(iobuf_t, void *), void *passthru );
/*-- decrypt-data.c --*/
int decrypt_data (ctrl_t ctrl, void *ctx, PKT_encrypted *ed, DEK *dek,
int *compliance_error);
/*-- plaintext.c --*/
gpg_error_t get_output_file (const byte *embedded_name, int embedded_namelen,
iobuf_t data, char **fnamep, estream_t *fpp);
int handle_plaintext( PKT_plaintext *pt, md_filter_context_t *mfx,
int nooutput, int clearsig );
int ask_for_detached_datafile( gcry_md_hd_t md, gcry_md_hd_t md2,
const char *inname, int textmode );
/*-- sign.c --*/
int make_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig, PKT_public_key *pk,
PKT_user_id *uid, PKT_public_key *subpk,
PKT_public_key *pksk, int sigclass,
u32 timestamp, u32 duration,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque,
const char *cache_nonce);
gpg_error_t update_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig,
PKT_signature *orig_sig,
PKT_public_key *pk,
PKT_user_id *uid,
PKT_public_key *subpk,
PKT_public_key *pksk,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque );
/*-- keygen.c --*/
PKT_user_id *generate_user_id (kbnode_t keyblock, const char *uidstr);
#endif /*G10_PACKET_H*/
diff --git a/g10/pkclist.c b/g10/pkclist.c
index 42e124e9e..afc8f56bc 100644
--- a/g10/pkclist.c
+++ b/g10/pkclist.c
@@ -1,1789 +1,1789 @@
/* pkclist.c - create a list of public keys
* Copyright (C) 1998-2020 Free Software Foundation, Inc.
* Copyright (C) 1997-2019 Werner Koch
* Copyright (C) 2015-2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "trustdb.h"
#include "../common/ttyio.h"
#include "../common/status.h"
#include "photoid.h"
#include "../common/i18n.h"
#include "../common/mbox-util.h"
#include "tofu.h"
#define CONTROL_D ('D' - 'A' + 1)
static void
send_status_inv_recp (int reason, const char *name)
{
char buf[40];
snprintf (buf, sizeof buf, "%d ", reason);
write_status_text_and_buffer (STATUS_INV_RECP, buf,
name, strlen (name),
-1);
}
/****************
* Show the revocation reason as it is stored with the given signature
*/
static void
do_show_revocation_reason( PKT_signature *sig )
{
size_t n, nn;
const byte *p, *pp;
int seq = 0;
const char *text;
while ((p = enum_sig_subpkt (sig, 1, SIGSUBPKT_REVOC_REASON,
&n, &seq, NULL)) ) {
if( !n )
continue; /* invalid - just skip it */
if( *p == 0 )
text = _("No reason specified");
else if( *p == 0x01 )
text = _("Key is superseded");
else if( *p == 0x02 )
text = _("Key has been compromised");
else if( *p == 0x03 )
text = _("Key is no longer used");
else if( *p == 0x20 )
text = _("User ID is no longer valid");
else
text = NULL;
log_info ( _("reason for revocation: "));
if (text)
log_printf ("%s\n", text);
else
log_printf ("code=%02x\n", *p );
n--; p++;
pp = NULL;
do {
/* We don't want any empty lines, so skip them */
while( n && *p == '\n' ) {
p++;
n--;
}
if( n ) {
pp = memchr( p, '\n', n );
nn = pp? pp - p : n;
log_info ( _("revocation comment: ") );
es_write_sanitized (log_get_stream(), p, nn, NULL, NULL);
log_printf ("\n");
p += nn; n -= nn;
}
} while( pp );
}
}
/* Mode 0: try and find the revocation based on the pk (i.e. check
subkeys, etc.) Mode 1: use only the revocation on the main pk */
void
show_revocation_reason (ctrl_t ctrl, PKT_public_key *pk, int mode)
{
/* Hmmm, this is not so easy because we have to duplicate the code
* used in the trustdb to calculate the keyflags. We need to find
* a clean way to check revocation certificates on keys and
* signatures. And there should be no duplicate code. Because we
* enter this function only when the trustdb told us that we have
* a revoked key, we could simply look for a revocation cert and
* display this one, when there is only one. Let's try to do this
* until we have a better solution. */
KBNODE node, keyblock = NULL;
byte fingerprint[MAX_FINGERPRINT_LEN];
size_t fingerlen;
int rc;
/* get the keyblock */
fingerprint_from_pk( pk, fingerprint, &fingerlen );
rc = get_pubkey_byfprint (ctrl, NULL, &keyblock, fingerprint, fingerlen);
if( rc ) { /* that should never happen */
log_debug( "failed to get the keyblock\n");
return;
}
for( node=keyblock; node; node = node->next ) {
if( (mode && node->pkt->pkttype == PKT_PUBLIC_KEY) ||
( ( node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
&& !cmp_public_keys( node->pkt->pkt.public_key, pk ) ) )
break;
}
if( !node ) {
log_debug("Oops, PK not in keyblock\n");
release_kbnode( keyblock );
return;
}
/* now find the revocation certificate */
for( node = node->next; node ; node = node->next ) {
if( node->pkt->pkttype == PKT_PUBLIC_SUBKEY )
break;
if( node->pkt->pkttype == PKT_SIGNATURE
&& (node->pkt->pkt.signature->sig_class == 0x20
|| node->pkt->pkt.signature->sig_class == 0x28 ) ) {
/* FIXME: we should check the signature here */
do_show_revocation_reason ( node->pkt->pkt.signature );
break;
}
}
/* We didn't find it, so check if the whole key is revoked */
if(!node && !mode)
show_revocation_reason (ctrl, pk, 1);
release_kbnode( keyblock );
}
/****************
* mode: 0 = standard
* 1 = Without key info and additional menu option 'm'
* this does also add an option to set the key to ultimately trusted.
* Returns:
* -2 = nothing changed - caller should show some additional info
* -1 = quit operation
* 0 = nothing changed
* 1 = new ownertrust now in new_trust
*/
#ifndef NO_TRUST_MODELS
static int
do_edit_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int mode,
unsigned *new_trust, int defer_help )
{
char *p;
u32 keyid[2];
int changed=0;
int quit=0;
int show=0;
int min_num;
int did_help=defer_help;
unsigned int minimum = tdb_get_min_ownertrust (ctrl, pk, 0);
switch(minimum)
{
default:
case TRUST_UNDEFINED: min_num=1; break;
case TRUST_NEVER: min_num=2; break;
case TRUST_MARGINAL: min_num=3; break;
case TRUST_FULLY: min_num=4; break;
}
keyid_from_pk (pk, keyid);
for(;;) {
/* A string with valid answers.
TRANSLATORS: These are the allowed answers in lower and
uppercase. Below you will find the matching strings which
should be translated accordingly and the letter changed to
match the one in the answer string.
i = please show me more information
m = back to the main menu
s = skip this key
q = quit
*/
const char *ans = _("iImMqQsS");
if( !did_help )
{
if( !mode )
{
KBNODE keyblock, un;
tty_printf (_("No trust value assigned to:\n"));
print_key_line (ctrl, NULL, pk, 0);
p = get_user_id_native (ctrl, keyid);
tty_printf (_(" \"%s\"\n"),p);
xfree (p);
keyblock = get_pubkeyblock (ctrl, keyid);
if (!keyblock)
BUG ();
for (un=keyblock; un; un = un->next)
{
if (un->pkt->pkttype != PKT_USER_ID )
continue;
if (un->pkt->pkt.user_id->flags.revoked)
continue;
if (un->pkt->pkt.user_id->flags.expired)
continue;
/* Only skip textual primaries */
if (un->pkt->pkt.user_id->flags.primary
&& !un->pkt->pkt.user_id->attrib_data )
continue;
if((opt.verify_options&VERIFY_SHOW_PHOTOS)
&& un->pkt->pkt.user_id->attrib_data)
show_photos (ctrl,
un->pkt->pkt.user_id->attribs,
un->pkt->pkt.user_id->numattribs, pk,
un->pkt->pkt.user_id);
p=utf8_to_native(un->pkt->pkt.user_id->name,
un->pkt->pkt.user_id->len,0);
tty_printf(_(" aka \"%s\"\n"),p);
}
print_fingerprint (ctrl, NULL, pk, 2);
tty_printf("\n");
release_kbnode (keyblock);
}
if(opt.trust_model==TM_DIRECT)
{
tty_printf(_("How much do you trust that this key actually "
"belongs to the named user?\n"));
tty_printf("\n");
}
else
{
/* This string also used in keyedit.c:trustsig_prompt */
tty_printf(_("Please decide how far you trust this user to"
" correctly verify other users' keys\n"
"(by looking at passports, checking fingerprints from"
" different sources, etc.)\n"));
tty_printf("\n");
}
if(min_num<=1)
tty_printf (_(" %d = I don't know or won't say\n"), 1);
if(min_num<=2)
tty_printf (_(" %d = I do NOT trust\n"), 2);
if(min_num<=3)
tty_printf (_(" %d = I trust marginally\n"), 3);
if(min_num<=4)
tty_printf (_(" %d = I trust fully\n"), 4);
if (mode)
tty_printf (_(" %d = I trust ultimately\n"), 5);
#if 0
/* not yet implemented */
tty_printf (" i = please show me more information\n");
#endif
if( mode )
tty_printf(_(" m = back to the main menu\n"));
else
{
tty_printf(_(" s = skip this key\n"));
tty_printf(_(" q = quit\n"));
}
tty_printf("\n");
if(minimum)
tty_printf(_("The minimum trust level for this key is: %s\n\n"),
trust_value_to_string(minimum));
did_help = 1;
}
if( strlen(ans) != 8 )
BUG();
p = cpr_get("edit_ownertrust.value",_("Your decision? "));
trim_spaces(p);
cpr_kill_prompt();
if( !*p )
did_help = 0;
else if( *p && p[1] )
;
else if( !p[1] && ((*p >= '0'+min_num) && *p <= (mode?'5':'4')) )
{
unsigned int trust;
switch( *p )
{
case '1': trust = TRUST_UNDEFINED; break;
case '2': trust = TRUST_NEVER ; break;
case '3': trust = TRUST_MARGINAL ; break;
case '4': trust = TRUST_FULLY ; break;
case '5': trust = TRUST_ULTIMATE ; break;
default: BUG();
}
if (trust == TRUST_ULTIMATE
&& !cpr_get_answer_is_yes ("edit_ownertrust.set_ultimate.okay",
_("Do you really want to set this key"
" to ultimate trust? (y/N) ")))
; /* no */
else
{
*new_trust = trust;
changed = 1;
break;
}
}
#if 0
/* not yet implemented */
else if( *p == ans[0] || *p == ans[1] )
{
tty_printf(_("Certificates leading to an ultimately trusted key:\n"));
show = 1;
break;
}
#endif
else if( mode && (*p == ans[2] || *p == ans[3] || *p == CONTROL_D ) )
{
break ; /* back to the menu */
}
else if( !mode && (*p == ans[6] || *p == ans[7] ) )
{
break; /* skip */
}
else if( !mode && (*p == ans[4] || *p == ans[5] ) )
{
quit = 1;
break ; /* back to the menu */
}
xfree(p); p = NULL;
}
xfree(p);
return show? -2: quit? -1 : changed;
}
#endif /*!NO_TRUST_MODELS*/
/*
* Display a menu to change the ownertrust of the key PK (which should
* be a primary key).
* For mode values see do_edit_ownertrust ()
*/
#ifndef NO_TRUST_MODELS
int
edit_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int mode )
{
unsigned int trust = 0;
int no_help = 0;
for(;;)
{
switch ( do_edit_ownertrust (ctrl, pk, mode, &trust, no_help ) )
{
case -1: /* quit */
return -1;
case -2: /* show info */
no_help = 1;
break;
case 1: /* trust value set */
trust &= ~TRUST_FLAG_DISABLED;
trust |= get_ownertrust (ctrl, pk) & TRUST_FLAG_DISABLED;
update_ownertrust (ctrl, pk, trust );
return 1;
default:
return 0;
}
}
}
#endif /*!NO_TRUST_MODELS*/
/****************
* Check whether we can trust this pk which has a trustlevel of TRUSTLEVEL
* Returns: true if we trust.
*/
static int
do_we_trust( PKT_public_key *pk, unsigned int trustlevel )
{
/* We should not be able to get here with a revoked or expired
key */
if(trustlevel & TRUST_FLAG_REVOKED
|| trustlevel & TRUST_FLAG_SUB_REVOKED
|| (trustlevel & TRUST_MASK) == TRUST_EXPIRED)
{
if (opt.ignore_expiration)
return 0;
BUG ();
}
if( opt.trust_model==TM_ALWAYS )
{
if( opt.verbose )
log_info("No trust check due to '--trust-model always' option\n");
return 1;
}
switch(trustlevel & TRUST_MASK)
{
default:
log_error ("invalid trustlevel %u returned from validation layer\n",
trustlevel);
/* fall through */
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
log_info(_("%s: There is no assurance this key belongs"
" to the named user\n"),keystr_from_pk(pk));
return 0; /* no */
case TRUST_MARGINAL:
log_info(_("%s: There is limited assurance this key belongs"
" to the named user\n"),keystr_from_pk(pk));
return 1; /* yes */
case TRUST_FULLY:
if( opt.verbose )
log_info(_("This key probably belongs to the named user\n"));
return 1; /* yes */
case TRUST_ULTIMATE:
if( opt.verbose )
log_info(_("This key belongs to us\n"));
return 1; /* yes */
case TRUST_NEVER:
/* This can be returned by TOFU, which can return negative
assertions. */
log_info(_("%s: This key is bad! It has been marked as untrusted!\n"),
keystr_from_pk(pk));
return 0; /* no */
}
return 1; /*NOTREACHED*/
}
/****************
* wrapper around do_we_trust, so we can ask whether to use the
* key anyway.
*/
static int
do_we_trust_pre (ctrl_t ctrl, PKT_public_key *pk, unsigned int trustlevel )
{
int rc;
rc = do_we_trust( pk, trustlevel );
if( !opt.batch && !rc )
{
print_key_info (ctrl, NULL, 0, pk, 0);
print_fingerprint (ctrl, NULL, pk, 2);
tty_printf("\n");
if ((trustlevel & TRUST_MASK) == TRUST_NEVER)
tty_printf(
_("This key is bad! It has been marked as untrusted! If you\n"
"*really* know what you are doing, you may answer the next\n"
"question with yes.\n"));
else
tty_printf(
_("It is NOT certain that the key belongs to the person named\n"
"in the user ID. If you *really* know what you are doing,\n"
"you may answer the next question with yes.\n"));
tty_printf("\n");
if (is_status_enabled ())
{
u32 kid[2];
char *hint_str;
keyid_from_pk (pk, kid);
hint_str = get_long_user_id_string (ctrl, kid);
write_status_text ( STATUS_USERID_HINT, hint_str );
xfree (hint_str);
}
if( cpr_get_answer_is_yes("untrusted_key.override",
_("Use this key anyway? (y/N) ")) )
rc = 1;
/* Hmmm: Should we set a flag to tell the user about
* his decision the next time he encrypts for this recipient?
*/
}
return rc;
}
/* Write a TRUST_foo status line including the validation model and if
* MBOX is not NULL the targeted User ID's mbox. */
static void
write_trust_status (int statuscode, int trustlevel, const char *mbox)
{
#ifdef NO_TRUST_MODELS
write_status (statuscode);
#else /* NO_TRUST_MODELS */
int tm;
/* For the combined tofu+pgp method, we return the trust model which
* was responsible for the trustlevel. */
if (opt.trust_model == TM_TOFU_PGP)
tm = (trustlevel & TRUST_FLAG_TOFU_BASED)? TM_TOFU : TM_PGP;
else
tm = opt.trust_model;
if (mbox)
{
char *escmbox = percent_escape (mbox, NULL);
write_status_strings (statuscode, "0 ", trust_model_string (tm),
" ", escmbox? escmbox : "?", NULL);
xfree (escmbox);
}
else
write_status_strings (statuscode, "0 ", trust_model_string (tm), NULL);
#endif /* NO_TRUST_MODELS */
}
/* Return true if MBOX matches one of the names in opt.sender_list. */
static int
is_in_sender_list (const char *mbox)
{
strlist_t sl;
for (sl = opt.sender_list; sl; sl = sl->next)
if (!strcmp (mbox, sl->d))
return 1;
return 0;
}
/* Check whether we can trust this signature. KEYBLOCK contains the
* key PK used to check the signature SIG. We need PK here in
* addition to KEYBLOCK so that we know the subkey used for
* verification. Returns an error code if we should not trust this
* signature (i.e. done by an not trusted key). */
gpg_error_t
check_signatures_trust (ctrl_t ctrl, kbnode_t keyblock, PKT_public_key *pk,
PKT_signature *sig)
{
gpg_error_t err = 0;
int uidbased = 0; /* 1 = signer's UID, 2 = use --sender option. */
unsigned int trustlevel = TRUST_UNKNOWN;
PKT_public_key *mainpk;
PKT_user_id *targetuid;
const char *testedtarget = NULL;
const char *statusmbox = NULL;
kbnode_t n;
if (opt.trust_model == TM_ALWAYS)
{
if (!opt.quiet)
log_info(_("WARNING: Using untrusted key!\n"));
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
goto leave;
}
log_assert (keyblock->pkt->pkttype == PKT_PUBLIC_KEY);
mainpk = keyblock->pkt->pkt.public_key;
if ((pk->flags.maybe_revoked && !pk->flags.revoked)
|| (mainpk->flags.maybe_revoked && !mainpk->flags.revoked))
log_info(_("WARNING: this key might be revoked (revocation key"
" not present)\n"));
/* Figure out the user ID which was used to create the signature.
* Note that the Signer's UID may be not a valid addr-spec but the
* plain value from the sub-packet; thus we need to check this
* before looking for the matching User ID (our parser makes sure
* that signers_uid has only the mbox if there is an mbox). */
if (is_valid_mailbox (sig->signers_uid))
uidbased = 1; /* We got the signer's UID and it is an addr-spec. */
else if (opt.sender_list)
uidbased = 2;
else
uidbased = 0;
targetuid = NULL;
if (uidbased)
{
- u32 tmpcreated = 0; /* Helper to find the lates user ID. */
+ u32 tmpcreated = 0; /* Helper to find the latest user ID. */
PKT_user_id *tmpuid;
for (n=keyblock; n; n = n->next)
if (n->pkt->pkttype == PKT_USER_ID
&& !(tmpuid = n->pkt->pkt.user_id)->attrib_data
&& tmpuid->created /* (is valid) */
&& !tmpuid->flags.revoked
&& !tmpuid->flags.expired)
{
if (!tmpuid->mbox)
tmpuid->mbox = mailbox_from_userid (tmpuid->name, 0);
if (!tmpuid->mbox)
continue;
if (uidbased == 1)
{
if (!strcmp (tmpuid->mbox, sig->signers_uid)
&& tmpuid->created > tmpcreated)
{
tmpcreated = tmpuid->created;
targetuid = tmpuid;
}
}
else
{
if (is_in_sender_list (tmpuid->mbox)
&& tmpuid->created > tmpcreated)
{
tmpcreated = tmpuid->created;
targetuid = tmpuid;
}
}
}
/* In addition restrict based on --sender. */
if (uidbased == 1 && opt.sender_list
&& targetuid && !is_in_sender_list (targetuid->mbox))
{
testedtarget = targetuid->mbox;
targetuid = NULL;
}
}
if (uidbased && !targetuid)
statusmbox = testedtarget? testedtarget : sig->signers_uid;
else if (uidbased)
statusmbox = targetuid->mbox;
else
statusmbox = NULL;
if (opt.verbose && statusmbox)
log_info (_("checking User ID \"%s\"\n"), statusmbox);
trustlevel = get_validity (ctrl, NULL, pk, targetuid, sig, 1);
if (uidbased && !targetuid)
{
/* No user ID given but requested - force an undefined
* trustlevel but keep the trust flags. */
trustlevel &= ~TRUST_MASK;
trustlevel |= TRUST_UNDEFINED;
if (!opt.quiet)
{
if (testedtarget)
log_info (_("option %s given but issuer \"%s\" does not match\n"),
"--sender", testedtarget);
else if (uidbased == 1)
log_info (_("issuer \"%s\" does not match any User ID\n"),
sig->signers_uid);
else if (opt.sender_list)
log_info (_("option %s given but no matching User ID found\n"),
"--sender");
}
}
if ( (trustlevel & TRUST_FLAG_REVOKED) )
{
write_status (STATUS_KEYREVOKED);
if (pk->flags.revoked == 2 || mainpk->flags.revoked == 2)
log_info(_("WARNING: This key has been revoked by its"
" designated revoker!\n"));
else
log_info(_("WARNING: This key has been revoked by its owner!\n"));
log_info(_(" This could mean that the signature is forged.\n"));
show_revocation_reason (ctrl, pk, 0);
}
else if ((trustlevel & TRUST_FLAG_SUB_REVOKED) )
{
write_status( STATUS_KEYREVOKED );
log_info(_("WARNING: This subkey has been revoked by its owner!\n"));
show_revocation_reason (ctrl, pk, 0);
}
if ((trustlevel & TRUST_FLAG_DISABLED))
log_info (_("Note: This key has been disabled.\n"));
/* Now let the user know what up with the trustlevel. */
switch ( (trustlevel & TRUST_MASK) )
{
case TRUST_EXPIRED:
log_info(_("Note: This key has expired!\n"));
print_fingerprint (ctrl, NULL, pk, 1);
break;
default:
log_error ("invalid trustlevel %u returned from validation layer\n",
trustlevel);
/* fall through */
case TRUST_UNKNOWN:
case TRUST_UNDEFINED:
write_trust_status (STATUS_TRUST_UNDEFINED, trustlevel, statusmbox);
if (uidbased)
log_info(_("WARNING: The key's User ID is not certified with"
" a trusted signature!\n"));
else
log_info(_("WARNING: This key is not certified with"
" a trusted signature!\n"));
log_info(_(" There is no indication that the "
"signature belongs to the owner.\n" ));
print_fingerprint (ctrl, NULL, pk, 1);
break;
case TRUST_NEVER:
/* This level can be returned by TOFU, which supports negative
* assertions. */
write_trust_status (STATUS_TRUST_NEVER, trustlevel, statusmbox);
log_info(_("WARNING: We do NOT trust this key!\n"));
log_info(_(" The signature is probably a FORGERY.\n"));
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
err = gpg_error (GPG_ERR_BAD_SIGNATURE);
break;
case TRUST_MARGINAL:
write_trust_status (STATUS_TRUST_MARGINAL, trustlevel, statusmbox);
if (uidbased)
log_info(_("WARNING: The key's User ID is not certified with"
" sufficiently trusted signatures!\n"));
else
log_info(_("WARNING: This key is not certified with"
" sufficiently trusted signatures!\n"));
log_info(_(" It is not certain that the"
" signature belongs to the owner.\n" ));
print_fingerprint (ctrl, NULL, pk, 1);
break;
case TRUST_FULLY:
write_trust_status (STATUS_TRUST_FULLY, trustlevel, statusmbox);
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
break;
case TRUST_ULTIMATE:
write_trust_status (STATUS_TRUST_ULTIMATE, trustlevel, statusmbox);
if (opt.with_fingerprint)
print_fingerprint (ctrl, NULL, pk, 1);
break;
}
leave:
return err;
}
void
release_pk_list (pk_list_t pk_list)
{
PK_LIST pk_rover;
for ( ; pk_list; pk_list = pk_rover)
{
pk_rover = pk_list->next;
free_public_key ( pk_list->pk );
xfree ( pk_list );
}
}
static int
key_present_in_pk_list(PK_LIST pk_list, PKT_public_key *pk)
{
for( ; pk_list; pk_list = pk_list->next)
if (cmp_public_keys(pk_list->pk, pk) == 0)
return 0;
return -1;
}
/*
* Return a malloced string with a default recipient if there is any
* Fixme: We don't distinguish between malloc failure and no-default-recipient.
*/
static char *
default_recipient (ctrl_t ctrl)
{
PKT_public_key *pk;
char *result;
if (opt.def_recipient)
return xtrystrdup (opt.def_recipient);
if (!opt.def_recipient_self)
return NULL;
pk = xtrycalloc (1, sizeof *pk );
if (!pk)
return NULL;
if (get_seckey_default (ctrl, pk))
{
free_public_key (pk);
return NULL;
}
result = hexfingerprint (pk, NULL, 0);
free_public_key (pk);
return result;
}
/* Helper for build_pk_list to find and check one key. This helper is
* also used directly in server mode by the RECIPIENTS command. On
* success the new key is added to PK_LIST_ADDR. NAME is the user id
* of the key. USE the requested usage and a set MARK_HIDDEN will
* mark the key in the updated list as a hidden recipient. If
* FROM_FILE is true, NAME is not a user ID but the name of a file
* holding a key. */
gpg_error_t
find_and_check_key (ctrl_t ctrl, const char *name, unsigned int use,
int mark_hidden, int from_file, pk_list_t *pk_list_addr)
{
int rc;
PKT_public_key *pk;
kbnode_t keyblock = NULL;
kbnode_t node;
if (!name || !*name)
return gpg_error (GPG_ERR_INV_USER_ID);
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
return gpg_error_from_syserror ();
pk->req_usage = use;
if (from_file)
rc = get_pubkey_fromfile (ctrl, pk, name, &keyblock);
else
rc = get_best_pubkey_byname (ctrl, GET_PUBKEY_NORMAL,
NULL, pk, name, &keyblock, 0);
if (rc)
{
int code;
/* Key not found or other error. */
log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) );
switch (gpg_err_code (rc))
{
case GPG_ERR_NO_SECKEY:
case GPG_ERR_NO_PUBKEY: code = 1; break;
case GPG_ERR_INV_USER_ID: code = 14; break;
default: code = 0; break;
}
send_status_inv_recp (code, name);
free_public_key (pk);
return rc;
}
rc = openpgp_pk_test_algo2 (pk->pubkey_algo, use);
if (rc)
{
/* Key found but not usable for us (e.g. sign-only key). */
release_kbnode (keyblock);
send_status_inv_recp (3, name); /* Wrong key usage */
log_error (_("%s: skipped: %s\n"), name, gpg_strerror (rc) );
free_public_key (pk);
return rc;
}
/* Key found and usable. Check validity. */
if (!from_file)
{
int trustlevel;
trustlevel = get_validity (ctrl, keyblock, pk, pk->user_id, NULL, 1);
if ( (trustlevel & TRUST_FLAG_DISABLED) )
{
/* Key has been disabled. */
release_kbnode (keyblock);
send_status_inv_recp (13, name);
log_info (_("%s: skipped: public key is disabled\n"), name);
free_public_key (pk);
return GPG_ERR_UNUSABLE_PUBKEY;
}
if ( !do_we_trust_pre (ctrl, pk, trustlevel) )
{
/* We don't trust this key. */
release_kbnode (keyblock);
send_status_inv_recp (10, name);
free_public_key (pk);
return GPG_ERR_UNUSABLE_PUBKEY;
}
}
/* Skip the actual key if the key is already present in the
list. */
if (!key_present_in_pk_list (*pk_list_addr, pk))
{
if (!opt.quiet)
log_info (_("%s: skipped: public key already present\n"), name);
free_public_key (pk);
}
else
{
pk_list_t r;
r = xmalloc (sizeof *r);
r->pk = pk;
r->next = *pk_list_addr;
r->flags = mark_hidden? 1:0;
*pk_list_addr = r;
}
for (node = keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_PUBLIC_SUBKEY
&& ((pk=node->pkt->pkt.public_key)->pubkey_usage & PUBKEY_USAGE_RENC)
&& pk->flags.valid
&& !pk->flags.revoked
&& !pk->flags.disabled
&& !pk->has_expired
&& key_present_in_pk_list (*pk_list_addr, pk))
{
pk_list_t r;
r = xmalloc (sizeof *r);
r->pk = copy_public_key (NULL, pk);
r->next = *pk_list_addr;
r->flags = mark_hidden? 1:0; /* FIXME: Use PK_LIST_HIDDEN ? */
*pk_list_addr = r;
}
release_kbnode (keyblock);
return 0;
}
/* This is the central function to collect the keys for recipients.
* It is thus used to prepare a public key encryption. encrypt-to
* keys, default keys and the keys for the actual recipients are all
* collected here. When not in batch mode and no recipient has been
* passed on the commandline, the function will also ask for
* recipients.
*
* RCPTS is a string list with the recipients; NULL is an allowed
* value but not very useful. Group expansion is done on these names;
* they may be in any of the user Id formats we can handle. The flags
* bits for each string in the string list are used for:
*
* - PK_LIST_ENCRYPT_TO :: This is an encrypt-to recipient.
* - PK_LIST_HIDDEN :: This is a hidden recipient.
* - PK_LIST_FROM_FILE :: The argument is a file with a key.
*
* On success a list of keys is stored at the address RET_PK_LIST; the
* caller must free this list. On error the value at this address is
* not changed.
*/
int
build_pk_list (ctrl_t ctrl, strlist_t rcpts, PK_LIST *ret_pk_list)
{
PK_LIST pk_list = NULL;
PKT_public_key *pk=NULL;
int rc=0;
int any_recipients=0;
strlist_t rov,remusr;
char *def_rec = NULL;
char pkstrbuf[PUBKEY_STRING_SIZE];
/* Try to expand groups if any have been defined. */
if (opt.grouplist)
remusr = expand_group (rcpts, 0);
else
remusr = rcpts;
/* XXX: Change this function to use get_pubkeys instead of
get_pubkey_byname to detect ambiguous key specifications and warn
about duplicate keyblocks. For ambiguous key specifications on
the command line or provided interactively, prompt the user to
select the best key. If a key specification is ambiguous and we
are in batch mode, die. */
if (opt.encrypt_to_default_key)
{
static int warned;
const char *default_key = parse_def_secret_key (ctrl);
if (default_key)
{
PK_LIST r = xmalloc_clear (sizeof *r);
r->pk = xmalloc_clear (sizeof *r->pk);
r->pk->req_usage = PUBKEY_USAGE_ENC;
rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, r->pk, default_key, NULL, NULL, 0);
if (rc)
{
xfree (r->pk);
xfree (r);
log_error (_("can't encrypt to '%s'\n"), default_key);
if (!opt.quiet)
log_info (_("(check argument of option '%s')\n"),
"--default-key");
}
else
{
r->next = pk_list;
r->flags = 0;
pk_list = r;
}
}
else if (opt.def_secret_key)
{
if (! warned)
log_info (_("option '%s' given, but no valid default keys given\n"),
"--encrypt-to-default-key");
warned = 1;
}
else
{
if (! warned)
log_info (_("option '%s' given, but option '%s' not given\n"),
"--encrypt-to-default-key", "--default-key");
warned = 1;
}
}
/* Check whether there are any recipients in the list and build the
* list of the encrypt-to ones (we always trust them). */
for ( rov = remusr; rov; rov = rov->next )
{
if ( !(rov->flags & PK_LIST_ENCRYPT_TO) )
{
/* This is a regular recipient; i.e. not an encrypt-to
one. */
any_recipients = 1;
/* Hidden recipients are not allowed while in PGP mode,
issue a warning and switch into GnuPG mode. */
if ((rov->flags & PK_LIST_HIDDEN) && (PGP7 || PGP8))
{
log_info(_("option '%s' may not be used in %s mode\n"),
"--hidden-recipient",
gnupg_compliance_option_string (opt.compliance));
compliance_failure();
}
}
else if (!opt.no_encrypt_to)
{
/* --encrypt-to has not been disabled. Check this
encrypt-to key. */
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
/* We explicitly allow encrypt-to to an disabled key; thus
we pass 1 for the second last argument and 1 as the last
argument to disable AKL. */
if ((rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, pk, rov->d, NULL, NULL, 1)))
{
free_public_key ( pk ); pk = NULL;
log_error (_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) );
send_status_inv_recp (0, rov->d);
goto fail;
}
else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
/* Skip the actual key if the key is already present
* in the list. Add it to our list if not. */
if (key_present_in_pk_list(pk_list, pk) == 0)
{
free_public_key (pk); pk = NULL;
if (!opt.quiet)
log_info (_("%s: skipped: public key already present\n"),
rov->d);
}
else
{
PK_LIST r;
r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = (rov->flags&PK_LIST_HIDDEN)?1:0;
pk_list = r;
/* Hidden encrypt-to recipients are not allowed while
in PGP mode, issue a warning and switch into
GnuPG mode. */
if ((r->flags&PK_LIST_ENCRYPT_TO) && (PGP7 || PGP8))
{
log_info(_("option '%s' may not be used in %s mode\n"),
"--hidden-encrypt-to",
gnupg_compliance_option_string (opt.compliance));
compliance_failure();
}
}
}
else
{
/* The public key is not usable for encryption. */
free_public_key( pk ); pk = NULL;
log_error(_("%s: skipped: %s\n"), rov->d, gpg_strerror (rc) );
send_status_inv_recp (3, rov->d); /* Wrong key usage */
goto fail;
}
}
}
/* If we don't have any recipients yet and we are not in batch mode
drop into interactive selection mode. */
if ( !any_recipients && !opt.batch )
{
int have_def_rec;
char *answer = NULL;
strlist_t backlog = NULL;
if (pk_list)
any_recipients = 1;
def_rec = default_recipient(ctrl);
have_def_rec = !!def_rec;
if ( !have_def_rec )
tty_printf(_("You did not specify a user ID. (you may use \"-r\")\n"));
for (;;)
{
rc = 0;
xfree(answer);
if ( have_def_rec )
{
/* A default recipient is taken as the first entry. */
answer = def_rec;
def_rec = NULL;
}
else if (backlog)
{
/* This is part of our trick to expand and display groups. */
answer = strlist_pop (&backlog);
}
else
{
/* Show the list of already collected recipients and ask
for more. */
PK_LIST iter;
tty_printf("\n");
tty_printf(_("Current recipients:\n"));
for (iter=pk_list;iter;iter=iter->next)
{
u32 keyid[2];
keyid_from_pk(iter->pk,keyid);
tty_printf ("%s/%s %s \"",
pubkey_string (iter->pk,
pkstrbuf, sizeof pkstrbuf),
keystr(keyid),
datestr_from_pk (iter->pk));
if (iter->pk->user_id)
tty_print_utf8_string(iter->pk->user_id->name,
iter->pk->user_id->len);
else
{
size_t n;
char *p = get_user_id (ctrl, keyid, &n, NULL);
tty_print_utf8_string ( p, n );
xfree(p);
}
tty_printf("\"\n");
}
answer = cpr_get_utf8("pklist.user_id.enter",
_("\nEnter the user ID. "
"End with an empty line: "));
trim_spaces(answer);
cpr_kill_prompt();
}
if ( !answer || !*answer )
{
xfree(answer);
break; /* No more recipients entered - get out of loop. */
}
/* Do group expand here too. The trick here is to continue
the loop if any expansion occurred. The code above will
then list all expanded keys. */
if (expand_id(answer,&backlog,0))
continue;
/* Get and check key for the current name. */
free_public_key (pk);
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
rc = get_pubkey_byname (ctrl, GET_PUBKEY_NORMAL,
NULL, pk, answer, NULL, NULL, 0);
if (rc)
tty_printf(_("No such user ID.\n"));
else if ( !(rc=openpgp_pk_test_algo2 (pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
if ( have_def_rec )
{
/* No validation for a default recipient. */
if (!key_present_in_pk_list(pk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info (_("skipped: public key "
"already set as default recipient\n") );
}
else
{
PK_LIST r = xmalloc (sizeof *r);
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing default ids. */
pk_list = r;
}
any_recipients = 1;
continue;
}
else
{ /* Check validity of this key. */
int trustlevel;
trustlevel =
get_validity (ctrl, NULL, pk, pk->user_id, NULL, 1);
if ( (trustlevel & TRUST_FLAG_DISABLED) )
{
tty_printf (_("Public key is disabled.\n") );
}
else if ( do_we_trust_pre (ctrl, pk, trustlevel) )
{
/* Skip the actual key if the key is already
* present in the list */
if (!key_present_in_pk_list(pk_list, pk))
{
free_public_key (pk);
pk = NULL;
log_info(_("skipped: public key already set\n") );
}
else
{
PK_LIST r;
r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing interactive ids. */
pk_list = r;
}
any_recipients = 1;
continue;
}
}
}
xfree(def_rec); def_rec = NULL;
have_def_rec = 0;
}
if ( pk )
{
free_public_key( pk );
pk = NULL;
}
}
else if ( !any_recipients && (def_rec = default_recipient(ctrl)) )
{
/* We are in batch mode and have only a default recipient. */
pk = xmalloc_clear( sizeof *pk );
pk->req_usage = PUBKEY_USAGE_ENC;
/* The default recipient is allowed to be disabled; thus pass 1
as second last argument. We also don't want an AKL. */
rc = get_pubkey_byname (ctrl, GET_PUBKEY_NO_AKL,
NULL, pk, def_rec, NULL, NULL, 1);
if (rc)
log_error(_("unknown default recipient \"%s\"\n"), def_rec );
else if ( !(rc=openpgp_pk_test_algo2(pk->pubkey_algo,
PUBKEY_USAGE_ENC)) )
{
/* Mark any_recipients here since the default recipient
would have been used if it wasn't already there. It
doesn't really matter if we got this key from the default
recipient or an encrypt-to. */
any_recipients = 1;
if (!key_present_in_pk_list(pk_list, pk))
log_info (_("skipped: public key already set "
"as default recipient\n"));
else
{
PK_LIST r = xmalloc( sizeof *r );
r->pk = pk; pk = NULL;
r->next = pk_list;
r->flags = 0; /* No throwing default ids. */
pk_list = r;
}
}
if ( pk )
{
free_public_key( pk );
pk = NULL;
}
xfree(def_rec); def_rec = NULL;
}
else
{
/* General case: Check all keys. */
any_recipients = 0;
for (; remusr; remusr = remusr->next )
{
if ( (remusr->flags & PK_LIST_ENCRYPT_TO) )
continue; /* encrypt-to keys are already handled. */
rc = find_and_check_key (ctrl, remusr->d, PUBKEY_USAGE_ENC,
!!(remusr->flags&PK_LIST_HIDDEN),
!!(remusr->flags&PK_LIST_FROM_FILE),
&pk_list);
if (rc)
goto fail;
any_recipients = 1;
}
}
if ( !rc && !any_recipients )
{
log_error(_("no valid addressees\n"));
write_status_text (STATUS_NO_RECP, "0");
rc = GPG_ERR_NO_USER_ID;
}
#ifdef USE_TOFU
if (! rc && (opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU))
{
PK_LIST iter;
for (iter = pk_list; iter; iter = iter->next)
{
int rc2;
/* Note: we already resolved any conflict when looking up
the key. Don't annoy the user again if she selected
accept once. */
rc2 = tofu_register_encryption (ctrl, iter->pk, NULL, 0);
if (rc2)
log_info ("WARNING: Failed to register encryption to %s"
" with TOFU engine\n",
keystr (pk_main_keyid (iter->pk)));
else if (DBG_TRUST)
log_debug ("Registered encryption to %s with TOFU DB.\n",
keystr (pk_main_keyid (iter->pk)));
}
}
#endif /*USE_TOFU*/
fail:
if ( rc )
release_pk_list( pk_list );
else
*ret_pk_list = pk_list;
if (opt.grouplist)
free_strlist(remusr);
return rc;
}
/* In pgp6 mode, disallow all ciphers except IDEA (1), 3DES (2), and
CAST5 (3), all hashes except MD5 (1), SHA1 (2), and RIPEMD160 (3),
and all compressions except none (0) and ZIP (1). pgp7 and pgp8
mode expands the cipher list to include AES128 (7), AES192 (8),
AES256 (9), and TWOFISH (10). pgp8 adds the SHA-256 hash (8). For
a true PGP key all of this is unneeded as they are the only items
present in the preferences subpacket, but checking here covers the
weird case of encrypting to a key that had preferences from a
different implementation which was then used with PGP. I am not
completely comfortable with this as the right thing to do, as it
slightly alters the list of what the user is supposedly requesting.
It is not against the RFC however, as the preference chosen will
never be one that the user didn't specify somewhere ("The
implementation may use any mechanism to pick an algorithm in the
intersection"), and PGP has no mechanism to fix such a broken
preference list, so I'm including it. -dms */
int
algo_available( preftype_t preftype, int algo, const struct pref_hint *hint)
{
if( preftype == PREFTYPE_SYM )
{
if (!opt.flags.allow_old_cipher_algos
&& openpgp_cipher_blocklen (algo) < 16)
return 0; /* We don't want this one. */
if(PGP7 && (algo != CIPHER_ALGO_IDEA
&& algo != CIPHER_ALGO_3DES
&& algo != CIPHER_ALGO_CAST5
&& algo != CIPHER_ALGO_AES
&& algo != CIPHER_ALGO_AES192
&& algo != CIPHER_ALGO_AES256
&& algo != CIPHER_ALGO_TWOFISH))
return 0;
/* PGP8 supports all the ciphers we do.. */
return algo && !openpgp_cipher_test_algo ( algo );
}
else if( preftype == PREFTYPE_HASH )
{
if (hint && hint->digest_length)
{
unsigned int n = gcry_md_get_algo_dlen (algo);
if (hint->exact)
{
/* For example ECDSA requires an exact hash value so
* that we do not truncate. For DSA we allow truncation
* and thus exact is not set. */
if (hint->digest_length != n)
return 0;
}
else if (hint->digest_length!=20 || opt.flags.dsa2)
{
/* If --enable-dsa2 is set or the hash isn't 160 bits
(which implies DSA2), then we'll accept a hash that
is larger than we need. Otherwise we won't accept
any hash that isn't exactly the right size. */
if (hint->digest_length > n)
return 0;
}
else if (hint->digest_length != n)
return 0;
}
if (PGP7 && (algo != DIGEST_ALGO_MD5
&& algo != DIGEST_ALGO_SHA1
&& algo != DIGEST_ALGO_RMD160))
return 0;
if(PGP8 && (algo != DIGEST_ALGO_MD5
&& algo != DIGEST_ALGO_SHA1
&& algo != DIGEST_ALGO_RMD160
&& algo != DIGEST_ALGO_SHA256))
return 0;
return algo && !openpgp_md_test_algo (algo);
}
else if( preftype == PREFTYPE_ZIP )
{
if (PGP7 && (algo != COMPRESS_ALGO_NONE
&& algo != COMPRESS_ALGO_ZIP))
return 0;
/* PGP8 supports all the compression algos we do */
return !check_compress_algo( algo );
}
else
return 0;
}
/****************
* Return -1 if we could not find an algorithm.
*/
int
select_algo_from_prefs(PK_LIST pk_list, int preftype,
int request, const struct pref_hint *hint)
{
PK_LIST pkr;
u32 bits[8];
const prefitem_t *prefs;
int result=-1,i;
u16 scores[256];
if( !pk_list )
return -1;
memset(bits,0xFF,sizeof(bits));
memset(scores,0,sizeof(scores));
for( pkr = pk_list; pkr; pkr = pkr->next )
{
u32 mask[8];
int rank=1,implicit=-1;
memset(mask,0,sizeof(mask));
switch(preftype)
{
case PREFTYPE_SYM:
/* Historical note: IDEA is implicitly there for v3 keys
with v3 selfsigs if --pgp2 mode is on. This was a 2440
thing that was dropped from 4880 but is still relevant to
GPG's 1991 support. All this doesn't mean IDEA is
actually available, of course. */
if (opt.flags.allow_old_cipher_algos)
implicit = CIPHER_ALGO_3DES;
else
implicit = CIPHER_ALGO_AES;
break;
case PREFTYPE_AEAD:
/* No implicit algo. */
break;
case PREFTYPE_HASH:
/* While I am including this code for completeness, note
that currently --pgp2 mode locks the hash at MD5, so this
code will never even be called. Even if the hash wasn't
locked at MD5, we don't support sign+encrypt in --pgp2
mode, and that's the only time PREFTYPE_HASH is used
anyway. -dms
Because "de-vs" compliance does not allow SHA-1 it does
not make sense to assign SHA-1 as implicit algorithm.
Instead it is better to use SHA-256 as implicit algorithm
(which will be the case for rfc4880bis anyway). */
if (opt.compliance == CO_DE_VS)
implicit = DIGEST_ALGO_SHA256;
else
implicit = DIGEST_ALGO_SHA1;
break;
case PREFTYPE_ZIP:
/* Uncompressed is always an option. */
implicit=COMPRESS_ALGO_NONE;
}
if (pkr->pk->user_id) /* selected by user ID */
prefs = pkr->pk->user_id->prefs;
else
prefs = pkr->pk->prefs;
if( prefs )
{
for (i=0; prefs[i].type; i++ )
{
if( prefs[i].type == preftype )
{
/* Make sure all scores don't add up past 0xFFFF
(and roll around) */
if(rank+scores[prefs[i].value]<=0xFFFF)
scores[prefs[i].value]+=rank;
else
scores[prefs[i].value]=0xFFFF;
mask[prefs[i].value/32] |= 1<<(prefs[i].value%32);
rank++;
/* We saw the implicit algorithm, so we don't need
tack it on the end ourselves. */
if(implicit==prefs[i].value)
implicit=-1;
}
}
}
if(rank==1 && preftype==PREFTYPE_ZIP)
{
/* If the compression preferences are not present, they are
assumed to be ZIP, Uncompressed (RFC4880:13.3.1) */
scores[1]=1; /* ZIP is first choice */
scores[0]=2; /* Uncompressed is second choice */
mask[0]|=3;
}
/* If the key didn't have the implicit algorithm listed
explicitly, add it here at the tail of the list. */
if(implicit>-1)
{
scores[implicit]+=rank;
mask[implicit/32] |= 1<<(implicit%32);
}
for(i=0;i<8;i++)
bits[i]&=mask[i];
}
/* We've now scored all of the algorithms, and the usable ones have
bits set. Let's pick the winner. */
/* The caller passed us a request. Can we use it? */
if(request>-1 && (bits[request/32] & (1<<(request%32))) &&
algo_available(preftype,request,hint))
result=request;
if(result==-1)
{
/* If we have personal prefs set, use them. */
prefs=NULL;
if(preftype==PREFTYPE_SYM && opt.personal_cipher_prefs)
prefs=opt.personal_cipher_prefs;
else if(preftype==PREFTYPE_HASH && opt.personal_digest_prefs)
prefs=opt.personal_digest_prefs;
else if(preftype==PREFTYPE_ZIP && opt.personal_compress_prefs)
prefs=opt.personal_compress_prefs;
if( prefs )
for(i=0; prefs[i].type; i++ )
{
if(bits[prefs[i].value/32] & (1<<(prefs[i].value%32))
&& algo_available( preftype, prefs[i].value, hint))
{
result = prefs[i].value;
break;
}
}
}
if(result==-1)
{
unsigned int best=-1;
/* At this point, we have not selected an algorithm due to a
special request or via personal prefs. Pick the highest
ranked algorithm (i.e. the one with the lowest score). */
if(preftype==PREFTYPE_HASH && scores[DIGEST_ALGO_MD5])
{
/* "If you are building an authentication system, the recipient
may specify a preferred signing algorithm. However, the
signer would be foolish to use a weak algorithm simply
because the recipient requests it." (RFC4880:14). If any
other hash algorithm is available, pretend that MD5 isn't.
Note that if the user intentionally chose MD5 by putting it
in their personal prefs, then we do what the user said (as we
never reach this code). */
for(i=DIGEST_ALGO_MD5+1;i<256;i++)
if(scores[i])
{
scores[DIGEST_ALGO_MD5]=0;
break;
}
}
for(i=0;i<256;i++)
{
/* Note the '<' here. This means in case of a tie, we will
favor the lower algorithm number. We have a choice
between the lower number (probably an older algorithm
with more time in use), or the higher number (probably a
newer algorithm with less time in use). Older is
probably safer here, even though the newer algorithms
tend to be "stronger". */
if(scores[i] && scores[i]<best
&& (bits[i/32] & (1<<(i%32)))
&& algo_available(preftype,i,hint))
{
best=scores[i];
result=i;
}
}
}
return result;
}
/*
* Select the MDC flag from the pk_list. We can only use MDC if all
* recipients support this feature.
*/
int
select_mdc_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
if ( !pk_list )
return 0;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
int mdc;
if (pkr->pk->user_id) /* selected by user ID */
mdc = pkr->pk->user_id->flags.mdc;
else
mdc = pkr->pk->flags.mdc;
if (!mdc)
return 0; /* At least one recipient does not support it. */
}
return 1; /* Can be used. */
}
/* Select the AEAD flag from the pk_list. We can only use AEAD if all
* recipients support this feature. Returns the AEAD to be used or 0
* if AEAD shall not be used. */
aead_algo_t
select_aead_from_pklist (PK_LIST pk_list)
{
pk_list_t pkr;
int aead;
if (!pk_list)
return 0;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
if (pkr->pk->user_id) /* selected by user ID */
aead = pkr->pk->user_id->flags.aead;
else
aead = pkr->pk->flags.aead;
if (!aead)
return 0; /* At least one recipient does not support it. */
}
return AEAD_ALGO_OCB; /* Yes, AEAD can be used. */
}
/* Print a warning for all keys in PK_LIST missing the AEAD feature
* flag or AEAD algorithms. */
void
warn_missing_aead_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
int mdc;
if (pkr->pk->user_id) /* selected by user ID */
mdc = pkr->pk->user_id->flags.aead;
else
mdc = pkr->pk->flags.aead;
if (!mdc)
log_info (_("Note: key %s has no %s feature\n"),
keystr_from_pk (pkr->pk), "AEAD");
}
}
void
warn_missing_aes_from_pklist (PK_LIST pk_list)
{
PK_LIST pkr;
for (pkr = pk_list; pkr; pkr = pkr->next)
{
const prefitem_t *prefs;
int i;
int gotit = 0;
prefs = pkr->pk->user_id? pkr->pk->user_id->prefs : pkr->pk->prefs;
if (prefs)
{
for (i=0; !gotit && prefs[i].type; i++ )
if (prefs[i].type == PREFTYPE_SYM
&& prefs[i].value == CIPHER_ALGO_AES)
gotit++;
}
if (!gotit)
log_info (_("Note: key %s has no preference for %s\n"),
keystr_from_pk (pkr->pk), "AES");
}
}
diff --git a/g10/pkglue.c b/g10/pkglue.c
index f4efa8fc5..9db8f46de 100644
--- a/g10/pkglue.c
+++ b/g10/pkglue.c
@@ -1,994 +1,994 @@
/* pkglue.c - public key operations glue code
* Copyright (C) 2000, 2003, 2010 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2024 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "../common/util.h"
#include "pkglue.h"
#include "main.h"
#include "options.h"
/* Maximum buffer sizes required for ECC KEM. */
#define ECC_POINT_LEN_MAX (1+2*64)
#define ECC_HASH_LEN_MAX 64
/* FIXME: Better change the function name because mpi_ is used by
gcrypt macros. */
gcry_mpi_t
get_mpi_from_sexp (gcry_sexp_t sexp, const char *item, int mpifmt)
{
gcry_sexp_t list;
gcry_mpi_t data;
list = gcry_sexp_find_token (sexp, item, 0);
log_assert (list);
data = gcry_sexp_nth_mpi (list, 1, mpifmt);
log_assert (data);
gcry_sexp_release (list);
return data;
}
/*
* SOS (Simply, Octet String) is an attempt to handle opaque octet
* string in OpenPGP, where well-formed MPI cannot represent octet
* string with leading zero octets.
*
* To retain maximum compatibility to existing MPI handling, SOS
* has same structure, but allows leading zero octets. When there
* is no leading zero octets, SOS representation is as same as MPI one.
* With leading zero octets, NBITS is 8*(length of octets), regardless
* of leading zero bits.
*/
/* Extract SOS representation from SEXP for PARAM, return the result
* in R_SOS. It is represented by opaque MPI with GCRYMPI_FLAG_USER2
* flag. */
gpg_error_t
sexp_extract_param_sos (gcry_sexp_t sexp, const char *param, gcry_mpi_t *r_sos)
{
gpg_error_t err;
gcry_sexp_t l2 = gcry_sexp_find_token (sexp, param, 0);
*r_sos = NULL;
if (!l2)
err = gpg_error (GPG_ERR_NO_OBJ);
else
{
size_t buflen;
void *p0 = gcry_sexp_nth_buffer (l2, 1, &buflen);
if (!p0)
err = gpg_error_from_syserror ();
else
{
gcry_mpi_t sos;
unsigned int nbits = buflen*8;
unsigned char *p = p0;
if (*p && nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
sos = gcry_mpi_set_opaque (NULL, p0, nbits);
if (sos)
{
gcry_mpi_set_flag (sos, GCRYMPI_FLAG_USER2);
*r_sos = sos;
err = 0;
}
else
err = gpg_error_from_syserror ();
}
gcry_sexp_release (l2);
}
return err;
}
/* "No leading zero octets" (nlz) version of the function above.
*
* This routine is used for backward compatibility to existing
* implementation with the weird handling of little endian integer
* representation with leading zero octets. For the sake of
* "well-fomed" MPI, which is designed for big endian integer, leading
* zero octets are removed when output, and they are recovered at
* input.
*
* Extract SOS representation from SEXP for PARAM, removing leading
* zeros, return the result in R_SOS. */
gpg_error_t
sexp_extract_param_sos_nlz (gcry_sexp_t sexp, const char *param,
gcry_mpi_t *r_sos)
{
gpg_error_t err;
gcry_sexp_t l2 = gcry_sexp_find_token (sexp, param, 0);
*r_sos = NULL;
if (!l2)
err = gpg_error (GPG_ERR_NO_OBJ);
else
{
size_t buflen;
const void *p0 = gcry_sexp_nth_data (l2, 1, &buflen);
if (!p0)
err = gpg_error_from_syserror ();
else
{
gcry_mpi_t sos;
unsigned int nbits = buflen*8;
const unsigned char *p = p0;
/* Strip leading zero bits. */
for (; nbits >= 8 && !*p; p++, nbits -= 8)
;
if (nbits >= 8 && !(*p & 0x80))
if (--nbits >= 7 && !(*p & 0x40))
if (--nbits >= 6 && !(*p & 0x20))
if (--nbits >= 5 && !(*p & 0x10))
if (--nbits >= 4 && !(*p & 0x08))
if (--nbits >= 3 && !(*p & 0x04))
if (--nbits >= 2 && !(*p & 0x02))
if (--nbits >= 1 && !(*p & 0x01))
--nbits;
sos = gcry_mpi_set_opaque_copy (NULL, p, nbits);
if (sos)
{
gcry_mpi_set_flag (sos, GCRYMPI_FLAG_USER2);
*r_sos = sos;
err = 0;
}
else
err = gpg_error_from_syserror ();
}
gcry_sexp_release (l2);
}
return err;
}
static byte *
get_data_from_sexp (gcry_sexp_t sexp, const char *item, size_t *r_size)
{
gcry_sexp_t list;
size_t valuelen;
const char *value;
byte *v;
if (DBG_CRYPTO)
log_printsexp ("get_data_from_sexp:", sexp);
list = gcry_sexp_find_token (sexp, item, 0);
log_assert (list);
value = gcry_sexp_nth_data (list, 1, &valuelen);
log_assert (value);
v = xtrymalloc (valuelen);
memcpy (v, value, valuelen);
gcry_sexp_release (list);
*r_size = valuelen;
return v;
}
/****************
* Emulate our old PK interface here - sometime in the future we might
* change the internal design to directly fit to libgcrypt.
*/
int
pk_verify (pubkey_algo_t pkalgo, gcry_mpi_t hash,
gcry_mpi_t *data, gcry_mpi_t *pkey)
{
gcry_sexp_t s_sig, s_hash, s_pkey;
int rc;
/* Make a sexp from pkey. */
if (pkalgo == PUBKEY_ALGO_DSA)
{
rc = 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]);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL_E || pkalgo == PUBKEY_ALGO_ELGAMAL)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
}
else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S)
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))", pkey[0], pkey[1]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA)
{
char *curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_pkey, NULL,
"(public-key(ecdsa(curve %s)(q%m)))",
curve, pkey[1]);
xfree (curve);
}
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
char *curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
const char *fmt;
if (openpgp_oid_is_ed25519 (pkey[0]))
fmt = "(public-key(ecc(curve %s)(flags eddsa)(q%m)))";
else
fmt = "(public-key(ecc(curve %s)(q%m)))";
rc = gcry_sexp_build (&s_pkey, NULL, fmt, curve, pkey[1]);
xfree (curve);
}
}
else
return GPG_ERR_PUBKEY_ALGO;
if (rc)
BUG (); /* gcry_sexp_build should never fail. */
/* Put hash into a S-Exp s_hash. */
if (pkalgo == PUBKEY_ALGO_EDDSA)
{
const char *fmt;
if (openpgp_oid_is_ed25519 (pkey[0]))
fmt = "(data(flags eddsa)(hash-algo sha512)(value %m))";
else
fmt = "(data(value %m))";
if (gcry_sexp_build (&s_hash, NULL, fmt, hash))
BUG (); /* gcry_sexp_build should never fail. */
}
else
{
if (gcry_sexp_build (&s_hash, NULL, "%m", hash))
BUG (); /* gcry_sexp_build should never fail. */
}
/* Put data into a S-Exp s_sig. */
s_sig = NULL;
if (pkalgo == PUBKEY_ALGO_DSA)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(dsa(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(ecdsa(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
gcry_mpi_t r = data[0];
gcry_mpi_t s = data[1];
if (openpgp_oid_is_ed25519 (pkey[0]))
{
size_t rlen, slen, n; /* (bytes) */
char buf[64];
unsigned int nbits;
unsigned int neededfixedlen = 256 / 8;
log_assert (neededfixedlen <= sizeof buf);
if (!r || !s)
rc = gpg_error (GPG_ERR_BAD_MPI);
else if ((rlen = (gcry_mpi_get_nbits (r)+7)/8) > neededfixedlen || !rlen)
rc = gpg_error (GPG_ERR_BAD_MPI);
else if ((slen = (gcry_mpi_get_nbits (s)+7)/8) > neededfixedlen || !slen)
rc = gpg_error (GPG_ERR_BAD_MPI);
else
{
/* We need to fixup the length in case of leading zeroes.
* OpenPGP does not allow leading zeroes and the parser for
* the signature packet has no information on the use curve,
* thus we need to do it here. We won't do it for opaque
* MPIs under the assumption that they are known to be fine;
* we won't see them here anyway but the check is anyway
* required. Fixme: A nifty feature for gcry_sexp_build
* would be a format to left pad the value (e.g. "%*M"). */
rc = 0;
if (rlen < neededfixedlen
&& !gcry_mpi_get_flag (r, GCRYMPI_FLAG_OPAQUE)
&& !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, r)))
{
log_assert (n < neededfixedlen);
memmove (buf + (neededfixedlen - n), buf, n);
memset (buf, 0, neededfixedlen - n);
r = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8);
}
else if (rlen < neededfixedlen
&& gcry_mpi_get_flag (r, GCRYMPI_FLAG_OPAQUE))
{
const unsigned char *p;
p = gcry_mpi_get_opaque (r, &nbits);
n = (nbits+7)/8;
memcpy (buf + (neededfixedlen - n), p, n);
memset (buf, 0, neededfixedlen - n);
gcry_mpi_set_opaque_copy (r, buf, neededfixedlen * 8);
}
if (slen < neededfixedlen
&& !gcry_mpi_get_flag (s, GCRYMPI_FLAG_OPAQUE)
&& !(rc=gcry_mpi_print (GCRYMPI_FMT_USG, buf, sizeof buf, &n, s)))
{
log_assert (n < neededfixedlen);
memmove (buf + (neededfixedlen - n), buf, n);
memset (buf, 0, neededfixedlen - n);
s = gcry_mpi_set_opaque_copy (NULL, buf, neededfixedlen * 8);
}
else if (slen < neededfixedlen
&& gcry_mpi_get_flag (s, GCRYMPI_FLAG_OPAQUE))
{
const unsigned char *p;
p = gcry_mpi_get_opaque (s, &nbits);
n = (nbits+7)/8;
memcpy (buf + (neededfixedlen - n), p, n);
memset (buf, 0, neededfixedlen - n);
gcry_mpi_set_opaque_copy (s, buf, neededfixedlen * 8);
}
}
}
else
rc = 0;
if (!rc)
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(eddsa(r%M)(s%M)))", r, s);
if (r != data[0])
gcry_mpi_release (r);
if (s != data[1])
gcry_mpi_release (s);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E)
{
if (!data[0] || !data[1])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL,
"(sig-val(elg(r%m)(s%m)))", data[0], data[1]);
}
else if (pkalgo == PUBKEY_ALGO_RSA || pkalgo == PUBKEY_ALGO_RSA_S)
{
if (!data[0])
rc = gpg_error (GPG_ERR_BAD_MPI);
else
rc = gcry_sexp_build (&s_sig, NULL, "(sig-val(rsa(s%m)))", data[0]);
}
else
BUG ();
if (!rc)
rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
gcry_sexp_release (s_sig);
gcry_sexp_release (s_hash);
gcry_sexp_release (s_pkey);
return rc;
}
#if GCRY_KEM_MLKEM1024_ENCAPS_LEN < GCRY_KEM_MLKEM768_ENCAPS_LEN \
|| GCRY_KEM_MLKEM1024_SHARED_LEN < GCRY_KEM_MLKEM768_SHARED_LEN
# error Bad Kyber constants in Libgcrypt
#endif
/* Core of the encryption for KEM algorithms. See pk_decrypt for a
* description of the arguments. */
static gpg_error_t
do_encrypt_kem (PKT_public_key *pk, gcry_mpi_t data, int seskey_algo,
gcry_mpi_t *resarr)
{
gpg_error_t err;
int i;
unsigned int nbits, n;
gcry_sexp_t s_data = NULL;
gcry_cipher_hd_t hd = NULL;
char *ecc_oid = NULL;
enum gcry_kem_algos kyber_algo, ecc_algo;
const unsigned char *ecc_pubkey;
size_t ecc_pubkey_len;
const unsigned char *kyber_pubkey;
size_t kyber_pubkey_len;
const unsigned char *seskey;
size_t seskey_len;
unsigned char *enc_seskey = NULL;
size_t enc_seskey_len;
int ecc_hash_algo;
unsigned char ecc_ct[ECC_POINT_LEN_MAX];
unsigned char ecc_ecdh[ECC_POINT_LEN_MAX];
unsigned char ecc_ss[ECC_HASH_LEN_MAX];
size_t ecc_ct_len, ecc_ecdh_len, ecc_ss_len;
unsigned char kyber_ct[GCRY_KEM_MLKEM1024_ENCAPS_LEN];
unsigned char kyber_ss[GCRY_KEM_MLKEM1024_SHARED_LEN];
size_t kyber_ct_len, kyber_ss_len;
char fixedinfo[1+MAX_FINGERPRINT_LEN];
int fixedlen;
unsigned char kek[32]; /* AES-256 is mandatory. */
size_t kek_len = 32;
/* For later error checking we make sure the array is cleared. */
resarr[0] = resarr[1] = resarr[2] = NULL;
/* As of now we use KEM only for the combined Kyber and thus a
* second public key is expected. Right now we take the keys
* directly from the PK->data elements. */
ecc_oid = openpgp_oid_to_str (pk->pkey[0]);
if (!ecc_oid)
{
err = gpg_error_from_syserror ();
log_error ("%s: error getting OID for ECC key\n", __func__);
goto leave;
}
ecc_algo = openpgp_oid_to_kem_algo (ecc_oid);
if (ecc_algo == GCRY_KEM_RAW_X25519)
{
if (!strcmp (ecc_oid, "1.3.6.1.4.1.3029.1.5.1"))
log_info ("Warning: "
- "legacy OID for cv25519 accepted during develpment\n");
+ "legacy OID for cv25519 accepted during development\n");
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
if (ecc_pubkey_len == 33 && *ecc_pubkey == 0x40)
{
ecc_pubkey++; /* Remove the 0x40 prefix. */
ecc_pubkey_len--;
}
if (ecc_pubkey_len != 32)
{
if (opt.verbose)
log_info ("%s: ECC public key length invalid (%zu)\n",
__func__, ecc_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
ecc_ct_len = ecc_ecdh_len = 32;
ecc_ss_len = 32;
ecc_hash_algo = GCRY_MD_SHA3_256;
}
else if (ecc_algo == GCRY_KEM_RAW_X448)
{
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
if (ecc_pubkey_len != 56)
{
if (opt.verbose)
log_info ("%s: ECC public key length invalid (%zu)\n",
__func__, ecc_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
ecc_ct_len = ecc_ecdh_len = 56;
ecc_ss_len = 64;
ecc_hash_algo = GCRY_MD_SHA3_512;
}
else if (ecc_algo == GCRY_KEM_RAW_BP256)
{
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
if (ecc_pubkey_len != 65)
{
if (opt.verbose)
log_info ("%s: ECC public key length invalid (%zu)\n",
__func__, ecc_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
ecc_ct_len = ecc_ecdh_len = 65;
ecc_ss_len = 32;
ecc_hash_algo = GCRY_MD_SHA3_256;
}
else if (ecc_algo == GCRY_KEM_RAW_BP384)
{
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
if (ecc_pubkey_len != 97)
{
if (opt.verbose)
log_info ("%s: ECC public key length invalid (%zu)\n",
__func__, ecc_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
ecc_ct_len = ecc_ecdh_len = 97;
ecc_ss_len = 64;
ecc_hash_algo = GCRY_MD_SHA3_512;
}
else if (ecc_algo == GCRY_KEM_RAW_BP512)
{
ecc_pubkey = gcry_mpi_get_opaque (pk->pkey[1], &nbits);
ecc_pubkey_len = (nbits+7)/8;
if (ecc_pubkey_len != 129)
{
if (opt.verbose)
log_info ("%s: ECC public key length invalid (%zu)\n",
__func__, ecc_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
ecc_ct_len = ecc_ecdh_len = 129;
ecc_ss_len = 64;
ecc_hash_algo = GCRY_MD_SHA3_512;
}
else
{
if (opt.verbose)
log_info ("%s: ECC curve %s not supported\n", __func__, ecc_oid);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
{
log_debug ("ECC curve: %s\n", ecc_oid);
log_printhex (ecc_pubkey, ecc_pubkey_len, "ECC pubkey:");
}
err = gcry_kem_encap (ecc_algo,
ecc_pubkey, ecc_pubkey_len,
ecc_ct, ecc_ct_len,
ecc_ecdh, ecc_ecdh_len,
NULL, 0);
if (err)
{
if (opt.verbose)
log_info ("%s: gcry_kem_encap for ECC (%s) failed\n",
__func__, ecc_oid);
goto leave;
}
if (DBG_CRYPTO)
{
log_printhex (ecc_ct, ecc_ct_len, "ECC ephem:");
log_printhex (ecc_ecdh, ecc_ecdh_len, "ECC ecdh:");
}
err = gnupg_ecc_kem_kdf (ecc_ss, ecc_ss_len,
ecc_hash_algo,
ecc_ecdh, ecc_ecdh_len,
ecc_ct, ecc_ct_len,
ecc_pubkey, ecc_pubkey_len);
if (err)
{
if (opt.verbose)
log_info ("%s: kdf for ECC failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (ecc_ss, ecc_ss_len, "ECC shared:");
kyber_pubkey = gcry_mpi_get_opaque (pk->pkey[2], &nbits);
kyber_pubkey_len = (nbits+7)/8;
if (kyber_pubkey_len == GCRY_KEM_MLKEM768_PUBKEY_LEN)
{
kyber_algo = GCRY_KEM_MLKEM768;
kyber_ct_len = GCRY_KEM_MLKEM768_ENCAPS_LEN;
kyber_ss_len = GCRY_KEM_MLKEM768_SHARED_LEN;
}
else if (kyber_pubkey_len == GCRY_KEM_MLKEM1024_PUBKEY_LEN)
{
kyber_algo = GCRY_KEM_MLKEM1024;
kyber_ct_len = GCRY_KEM_MLKEM1024_ENCAPS_LEN;
kyber_ss_len = GCRY_KEM_MLKEM1024_SHARED_LEN;
}
else
{
if (opt.verbose)
log_info ("%s: Kyber public key length invalid (%zu)\n",
__func__, kyber_pubkey_len);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (kyber_pubkey, kyber_pubkey_len, "|!trunc|Kyber pubkey:");
err = gcry_kem_encap (kyber_algo,
kyber_pubkey, kyber_pubkey_len,
kyber_ct, kyber_ct_len,
kyber_ss, kyber_ss_len,
NULL, 0);
if (err)
{
if (opt.verbose)
log_info ("%s: gcry_kem_encap for ECC failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
{
log_printhex (kyber_ct, kyber_ct_len, "|!trunc|Kyber ephem:");
log_printhex (kyber_ss, kyber_ss_len, "Kyber shared:");
}
fixedinfo[0] = seskey_algo;
v5_fingerprint_from_pk (pk, fixedinfo+1, NULL);
fixedlen = 33;
err = gnupg_kem_combiner (kek, kek_len,
ecc_ss, ecc_ss_len, ecc_ct, ecc_ct_len,
kyber_ss, kyber_ss_len, kyber_ct, kyber_ct_len,
fixedinfo, fixedlen);
if (err)
{
if (opt.verbose)
log_info ("%s: KEM combiner failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (kek, kek_len, "KEK:");
err = gcry_cipher_open (&hd, GCRY_CIPHER_AES256,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = gcry_cipher_setkey (hd, kek, kek_len);
if (err)
{
if (opt.verbose)
log_error ("%s: failed to initialize AESWRAP: %s\n", __func__,
gpg_strerror (err));
goto leave;
}
err = gcry_sexp_build (&s_data, NULL, "%m", data);
if (err)
goto leave;
n = gcry_cipher_get_algo_keylen (seskey_algo);
seskey = gcry_mpi_get_opaque (data, &nbits);
seskey_len = (nbits+7)/8;
if (seskey_len != n)
{
if (opt.verbose)
log_info ("%s: session key length %zu"
" does not match the length for algo %d\n",
__func__, seskey_len, seskey_algo);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (seskey, seskey_len, "seskey:");
enc_seskey_len = 1 + seskey_len + 8;
enc_seskey = xtrymalloc (enc_seskey_len);
if (!enc_seskey || enc_seskey_len > 254)
{
err = gpg_error_from_syserror ();
goto leave;
}
enc_seskey[0] = enc_seskey_len - 1;
err = gcry_cipher_encrypt (hd, enc_seskey+1, enc_seskey_len-1,
seskey, seskey_len);
if (err)
{
log_error ("%s: wrapping session key failed\n", __func__);
goto leave;
}
if (DBG_CRYPTO)
log_printhex (enc_seskey, enc_seskey_len, "enc_seskey:");
resarr[0] = gcry_mpi_set_opaque_copy (NULL, ecc_ct, 8 * ecc_ct_len);
if (resarr[0])
resarr[1] = gcry_mpi_set_opaque_copy (NULL, kyber_ct, 8 * kyber_ct_len);
if (resarr[1])
resarr[2] = gcry_mpi_set_opaque_copy (NULL, enc_seskey, 8 * enc_seskey_len);
if (!resarr[0] || !resarr[1] || !resarr[2])
{
err = gpg_error_from_syserror ();
for (i=0; i < 3; i++)
gcry_mpi_release (resarr[i]), resarr[i] = NULL;
}
leave:
wipememory (ecc_ct, sizeof ecc_ct);
wipememory (ecc_ecdh, sizeof ecc_ecdh);
wipememory (ecc_ss, sizeof ecc_ss);
wipememory (kyber_ct, sizeof kyber_ct);
wipememory (kyber_ss, sizeof kyber_ss);
wipememory (kek, kek_len);
xfree (enc_seskey);
gcry_cipher_close (hd);
xfree (ecc_oid);
return err;
}
/* Core of the encryption for the ECDH algorithms. See pk_decrypt for
* a description of the arguments. */
static gpg_error_t
do_encrypt_ecdh (PKT_public_key *pk, gcry_mpi_t data, gcry_mpi_t *resarr)
{
gcry_mpi_t *pkey = pk->pkey;
gcry_sexp_t s_ciph = NULL;
gcry_sexp_t s_data = NULL;
gcry_sexp_t s_pkey = NULL;
gpg_error_t err;
gcry_mpi_t k = NULL;
char *curve = NULL;
int with_djb_tweak_flag;
gcry_mpi_t public = NULL;
gcry_mpi_t result = NULL;
byte fp[MAX_FINGERPRINT_LEN];
byte *shared = NULL;
byte *p;
size_t nshared;
unsigned int nbits;
err = pk_ecdh_generate_ephemeral_key (pkey, &k);
if (err)
goto leave;
curve = openpgp_oid_to_str (pkey[0]);
if (!curve)
{
err = gpg_error_from_syserror ();
goto leave;
}
with_djb_tweak_flag = openpgp_oid_is_cv25519 (pkey[0]);
/* Now use the ephemeral secret to compute the shared point. */
err = gcry_sexp_build (&s_pkey, NULL,
with_djb_tweak_flag ?
"(public-key(ecdh(curve%s)(flags djb-tweak)(q%m)))"
: "(public-key(ecdh(curve%s)(q%m)))",
curve, pkey[1]);
if (err)
goto leave;
/* Put K into a simplified S-expression. */
err = gcry_sexp_build (&s_data, NULL, "%m", k);
if (err)
goto leave;
/* Run encryption. */
err = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
if (err)
goto leave;
gcry_sexp_release (s_data); s_data = NULL;
gcry_sexp_release (s_pkey); s_pkey = NULL;
/* Get the shared point and the ephemeral public key. */
shared = get_data_from_sexp (s_ciph, "s", &nshared);
if (!shared)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = sexp_extract_param_sos (s_ciph, "e", &public);
gcry_sexp_release (s_ciph); s_ciph = NULL;
if (DBG_CRYPTO)
{
log_debug ("ECDH ephemeral key:");
gcry_mpi_dump (public);
log_printf ("\n");
}
fingerprint_from_pk (pk, fp, NULL);
p = gcry_mpi_get_opaque (data, &nbits);
result = NULL;
err = pk_ecdh_encrypt_with_shared_point (shared, nshared, fp, p,
(nbits+7)/8, pkey, &result);
if (err)
goto leave;
resarr[0] = public; public = NULL;
resarr[1] = result; result = NULL;
leave:
gcry_mpi_release (public);
gcry_mpi_release (result);
xfree (shared);
gcry_sexp_release (s_ciph);
gcry_sexp_release (s_data);
gcry_sexp_release (s_pkey);
xfree (curve);
gcry_mpi_release (k);
return err;
}
/* Core of the encryption for RSA and Elgamal algorithms. See
* pk_decrypt for a description of the arguments. */
static gpg_error_t
do_encrypt_rsa_elg (PKT_public_key *pk, gcry_mpi_t data, gcry_mpi_t *resarr)
{
pubkey_algo_t algo = pk->pubkey_algo;
gcry_mpi_t *pkey = pk->pkey;
gcry_sexp_t s_ciph = NULL;
gcry_sexp_t s_data = NULL;
gcry_sexp_t s_pkey = NULL;
gpg_error_t err;
if (algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E)
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(elg(p%m)(g%m)(y%m)))",
pkey[0], pkey[1], pkey[2]);
else
err = gcry_sexp_build (&s_pkey, NULL,
"(public-key(rsa(n%m)(e%m)))",
pkey[0], pkey[1]);
if (err)
goto leave;
err = gcry_sexp_build (&s_data, NULL, "%m", data);
if (err)
goto leave;
err = gcry_pk_encrypt (&s_ciph, s_data, s_pkey);
if (err)
goto leave;
gcry_sexp_release (s_data); s_data = NULL;
gcry_sexp_release (s_pkey); s_pkey = NULL;
resarr[0] = get_mpi_from_sexp (s_ciph, "a", GCRYMPI_FMT_USG);
if (!is_RSA (algo))
resarr[1] = get_mpi_from_sexp (s_ciph, "b", GCRYMPI_FMT_USG);
leave:
gcry_sexp_release (s_data);
gcry_sexp_release (s_pkey);
gcry_sexp_release (s_ciph);
return err;
}
/*
* Emulate our old PK interface here - sometime in the future we might
* change the internal design to directly fit to libgcrypt. PK is is
* the OpenPGP public key packet, DATA is an MPI with the to be
* encrypted data, and RESARR receives the encrypted data. RESARRAY
* is expected to be an two item array which will be filled with newly
* allocated MPIs. SESKEY_ALGO is required for public key algorithms
* which do not encode it in DATA.
*/
gpg_error_t
pk_encrypt (PKT_public_key *pk, gcry_mpi_t data, int seskey_algo,
gcry_mpi_t *resarr)
{
pubkey_algo_t algo = pk->pubkey_algo;
if (algo == PUBKEY_ALGO_KYBER)
return do_encrypt_kem (pk, data, seskey_algo, resarr);
else if (algo == PUBKEY_ALGO_ECDH)
return do_encrypt_ecdh (pk, data, resarr);
else if (algo == PUBKEY_ALGO_ELGAMAL || algo == PUBKEY_ALGO_ELGAMAL_E)
return do_encrypt_rsa_elg (pk, data, resarr);
else if (algo == PUBKEY_ALGO_RSA || algo == PUBKEY_ALGO_RSA_E)
return do_encrypt_rsa_elg (pk, data, resarr);
else
return gpg_error (GPG_ERR_PUBKEY_ALGO);
}
/* Check whether SKEY is a suitable secret key. */
int
pk_check_secret_key (pubkey_algo_t pkalgo, gcry_mpi_t *skey)
{
gcry_sexp_t s_skey;
int rc;
if (pkalgo == PUBKEY_ALGO_DSA)
{
rc = 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]);
}
else if (pkalgo == PUBKEY_ALGO_ELGAMAL || pkalgo == PUBKEY_ALGO_ELGAMAL_E)
{
rc = 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]);
}
else if (is_RSA (pkalgo))
{
rc = 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]);
}
else if (pkalgo == PUBKEY_ALGO_ECDSA || pkalgo == PUBKEY_ALGO_ECDH)
{
char *curve = openpgp_oid_to_str (skey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
rc = gcry_sexp_build (&s_skey, NULL,
"(private-key(ecc(curve%s)(q%m)(d%m)))",
curve, skey[1], skey[2]);
xfree (curve);
}
}
else if (pkalgo == PUBKEY_ALGO_EDDSA)
{
char *curve = openpgp_oid_to_str (skey[0]);
if (!curve)
rc = gpg_error_from_syserror ();
else
{
const char *fmt;
if (openpgp_oid_is_ed25519 (skey[0]))
fmt = "(private-key(ecc(curve %s)(flags eddsa)(q%m)(d%m)))";
else
fmt = "(private-key(ecc(curve %s)(q%m)(d%m)))";
rc = gcry_sexp_build (&s_skey, NULL, fmt, curve, skey[1], skey[2]);
xfree (curve);
}
}
else
return GPG_ERR_PUBKEY_ALGO;
if (!rc)
{
rc = gcry_pk_testkey (s_skey);
gcry_sexp_release (s_skey);
}
return rc;
}
diff --git a/g10/sig-check.c b/g10/sig-check.c
index 06329f659..e85e9914f 100644
--- a/g10/sig-check.c
+++ b/g10/sig-check.c
@@ -1,1288 +1,1288 @@
/* sig-check.c - Check a signature
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003,
* 2004, 2006 Free Software Foundation, Inc.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "../common/util.h"
#include "packet.h"
#include "keydb.h"
#include "main.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "options.h"
#include "pkglue.h"
#include "../common/compliance.h"
static int check_signature_end (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
int *r_expired, int *r_revoked,
PKT_public_key *ret_pk);
static int check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash,
size_t extrahashlen);
/* Statistics for signature verification. */
struct
{
unsigned int total; /* Total number of verifications. */
unsigned int cached; /* Number of seen cache entries. */
unsigned int goodsig;/* Number of good verifications from the cache. */
unsigned int badsig; /* Number of bad verifications from the cache. */
} cache_stats;
/* Dump verification stats. */
void
sig_check_dump_stats (void)
{
log_info ("sig_cache: total=%u cached=%u good=%u bad=%u\n",
cache_stats.total, cache_stats.cached,
cache_stats.goodsig, cache_stats.badsig);
}
static gpg_error_t
check_key_verify_compliance (PKT_public_key *pk)
{
gpg_error_t err = 0;
if (!gnupg_pk_is_allowed (opt.compliance, PK_USE_VERIFICATION,
pk->pubkey_algo, 0, pk->pkey,
nbits_from_pk (pk),
NULL))
{
/* Compliance failure. */
log_error (_("key %s may not be used for signing in %s mode\n"),
keystr_from_pk (pk),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
}
return err;
}
/* Check a signature. This is shorthand for check_signature2 with
the unnamed arguments passed as NULL. */
int
check_signature (ctrl_t ctrl, PKT_signature *sig, gcry_md_hd_t digest)
{
return check_signature2 (ctrl, sig, digest, NULL, 0, NULL,
NULL, NULL, NULL, NULL);
}
/* Check a signature.
*
* Looks up the public key that created the signature (SIG->KEYID)
* from the key db. Makes sure that the signature is valid (it was
* not created prior to the key, the public key was created in the
* past, and the signature does not include any unsupported critical
* features), finishes computing the hash of the signature data, and
* checks that the signature verifies the digest. If the key that
* generated the signature is a subkey, this function also verifies
* that there is a valid backsig from the subkey to the primary key.
* Finally, if status fd is enabled and the signature class is 0x00 or
* 0x01, then a STATUS_SIG_ID is emitted on the status fd.
*
* SIG is the signature to check.
*
* DIGEST contains a valid hash context that already includes the
* signed data. This function adds the relevant meta-data from the
* signature packet to compute the final hash. (See Section 5.2 of
* RFC 4880: "The concatenation of the data being signed and the
* signature data from the version number through the hashed subpacket
* data (inclusive) is hashed.")
*
* EXTRAHASH and EXTRAHASHLEN is additional data which is hashed with
* v5 signatures. They may be NULL to use the default.
*
* If FORCED_PK is not NULL this public key is used to verify the
* signature and no other public key is looked up. This is used to
* verify against a key included in the signature.
*
* If R_EXPIREDATE is not NULL, R_EXPIREDATE is set to the key's
* expiry.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
* (0 otherwise). Note: PK being expired does not cause this function
* to fail.
*
* If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
* revoked (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
* If R_PK is not NULL, the public key is stored at that address if it
* was found; other wise NULL is stored.
*
* Returns 0 on success. An error code otherwise. */
gpg_error_t
check_signature2 (ctrl_t ctrl,
PKT_signature *sig, gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
PKT_public_key *forced_pk,
u32 *r_expiredate,
int *r_expired, int *r_revoked, PKT_public_key **r_pk)
{
int rc=0;
PKT_public_key *pk;
if (r_expiredate)
*r_expiredate = 0;
if (r_expired)
*r_expired = 0;
if (r_revoked)
*r_revoked = 0;
if (r_pk)
*r_pk = NULL;
pk = xtrycalloc (1, sizeof *pk);
if (!pk)
return gpg_error_from_syserror ();
if ((rc=openpgp_md_test_algo(sig->digest_algo)))
{
/* We don't have this digest. */
}
else if (!gnupg_digest_is_allowed (opt.compliance, 0, sig->digest_algo))
{
/* Compliance failure. */
log_info (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (sig->digest_algo),
gnupg_compliance_option_string (opt.compliance));
rc = gpg_error (GPG_ERR_DIGEST_ALGO);
}
else if ((rc=openpgp_pk_test_algo(sig->pubkey_algo)))
{
/* We don't have this pubkey algo. */
}
else if (!gcry_md_is_enabled (digest,sig->digest_algo))
{
/* Sanity check that the md has a context for the hash that the
* sig is expecting. This can happen if a onepass sig header
* does not match the actual sig, and also if the clearsign
* "Hash:" header is missing or does not match the actual sig. */
log_info(_("WARNING: signature digest conflict in message\n"));
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (get_pubkey_for_sig (ctrl, pk, sig, forced_pk))
rc = gpg_error (GPG_ERR_NO_PUBKEY);
else if ((rc = check_key_verify_compliance (pk)))
;/* Compliance failure. */
else if (!pk->flags.valid)
{
/* You cannot have a good sig from an invalid key. */
rc = gpg_error (GPG_ERR_BAD_PUBKEY);
}
else
{
if (r_expiredate)
*r_expiredate = pk->expiredate;
rc = check_signature_end (pk, sig, digest, extrahash, extrahashlen,
r_expired, r_revoked, NULL);
/* Check the backsig. This is a back signature (0x19) from
* the subkey on the primary key. The idea here is that it
* should not be possible for someone to "steal" subkeys and
* claim them as their own. The attacker couldn't actually
* use the subkey, but they could try and claim ownership of
* any signatures issued by it. */
if (!rc && !pk->flags.primary && pk->flags.backsig < 2)
{
if (!pk->flags.backsig)
{
log_info (_("WARNING: signing subkey %s is not"
" cross-certified\n"),keystr_from_pk(pk));
log_info (_("please see %s for more information\n"),
"https://gnupg.org/faq/subkey-cross-certify.html");
/* The default option --require-cross-certification
* makes this warning an error. */
if (opt.flags.require_cross_cert)
rc = gpg_error (GPG_ERR_GENERAL);
}
else if(pk->flags.backsig == 1)
{
log_info (_("WARNING: signing subkey %s has an invalid"
" cross-certification\n"), keystr_from_pk(pk));
rc = gpg_error (GPG_ERR_GENERAL);
}
}
}
if( !rc && sig->sig_class < 2 && is_status_enabled() ) {
/* This signature id works best with DLP algorithms because
* they use a random parameter for every signature. Instead of
* this sig-id we could have also used the hash of the document
* and the timestamp, but the drawback of this is, that it is
* not possible to sign more than one identical document within
* one second. Some remote batch processing applications might
* like this feature here.
*
* Note that before 2.0.10, we used RIPE-MD160 for the hash
* and accidentally didn't include the timestamp and algorithm
* information in the hash. Given that this feature is not
* commonly used and that a replay attacks detection should
* not solely be based on this feature (because it does not
* work with RSA), we take the freedom and switch to SHA-1
* with 2.0.10 to take advantage of hardware supported SHA-1
* implementations. We also include the missing information
* in the hash. Note also the SIG_ID as computed by gpg 1.x
* and gpg 2.x didn't matched either because 2.x used to print
* MPIs not in PGP format. */
u32 a = sig->timestamp;
int nsig = pubkey_get_nsig( sig->pubkey_algo );
unsigned char *p, *buffer;
size_t n, nbytes;
int i;
char hashbuf[20]; /* We use SHA-1 here. */
nbytes = 6;
for (i=0; i < nsig; i++ )
{
if (gcry_mpi_get_flag (sig->data[i], GCRYMPI_FLAG_OPAQUE))
{
unsigned int nbits;
gcry_mpi_get_opaque (sig->data[i], &nbits);
n = (nbits+7)/8 + 2;
}
else if (gcry_mpi_print (GCRYMPI_FMT_PGP, NULL, 0, &n, sig->data[i]))
BUG();
nbytes += n;
}
/* Make buffer large enough to be later used as output buffer. */
if (nbytes < 100)
nbytes = 100;
nbytes += 10; /* Safety margin. */
/* Fill and hash buffer. */
buffer = p = xmalloc (nbytes);
*p++ = sig->pubkey_algo;
*p++ = sig->digest_algo;
*p++ = (a >> 24) & 0xff;
*p++ = (a >> 16) & 0xff;
*p++ = (a >> 8) & 0xff;
*p++ = a & 0xff;
nbytes -= 6;
for (i=0; i < nsig; i++ )
{
if (gcry_mpi_get_flag (sig->data[i], GCRYMPI_FLAG_OPAQUE))
{
const byte *sigdata;
unsigned int nbits;
sigdata = gcry_mpi_get_opaque (sig->data[i], &nbits);
n = (nbits+7)/8;
p[0] = nbits >> 8;
p[1] = (nbits & 0xff);
memcpy (p+2, sigdata, n);
n += 2;
}
else if (gcry_mpi_print (GCRYMPI_FMT_PGP, p, nbytes, &n, sig->data[i]))
BUG();
p += n;
nbytes -= n;
}
gcry_md_hash_buffer (GCRY_MD_SHA1, hashbuf, buffer, p-buffer);
p = make_radix64_string (hashbuf, 20);
sprintf (buffer, "%s %s %lu",
p, strtimestamp (sig->timestamp), (ulong)sig->timestamp);
xfree (p);
write_status_text (STATUS_SIG_ID, buffer);
xfree (buffer);
}
if (r_pk)
*r_pk = pk;
else
{
release_public_key_parts (pk);
xfree (pk);
}
return rc;
}
/* The signature SIG was generated with the public key PK. Check
* whether the signature is valid in the following sense:
*
* - Make sure the public key was created before the signature was
* generated.
*
* - Make sure the public key was created in the past
*
* - Check whether PK has expired (set *R_EXPIRED to 1 if so and 0
* otherwise)
*
* - Check whether PK has been revoked (set *R_REVOKED to 1 if so
* and 0 otherwise).
*
* If either of the first two tests fail, returns an error code.
* Otherwise returns 0. (Thus, this function doesn't fail if the
* public key is expired or revoked.) */
static int
check_signature_metadata_validity (PKT_public_key *pk, PKT_signature *sig,
int *r_expired, int *r_revoked)
{
u32 cur_time;
if (r_expired)
*r_expired = 0;
if (r_revoked)
*r_revoked = 0;
if (pk->timestamp > sig->timestamp
&& !(parse_key_usage (sig) & PUBKEY_USAGE_RENC))
{
ulong d = pk->timestamp - sig->timestamp;
if ( d < 86400 )
{
log_info (ngettext
("public key %s is %lu second newer than the signature\n",
"public key %s is %lu seconds newer than the signature\n",
d), keystr_from_pk (pk), d);
}
else
{
d /= 86400;
log_info (ngettext
("public key %s is %lu day newer than the signature\n",
"public key %s is %lu days newer than the signature\n",
d), keystr_from_pk (pk), d);
}
if (!opt.ignore_time_conflict)
return GPG_ERR_TIME_CONFLICT; /* pubkey newer than signature. */
}
cur_time = make_timestamp ();
if (pk->timestamp > cur_time)
{
ulong d = pk->timestamp - cur_time;
if (d < 86400)
{
log_info (ngettext("key %s was created %lu second"
" in the future (time warp or clock problem)\n",
"key %s was created %lu seconds"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pk), d);
}
else
{
d /= 86400;
log_info (ngettext("key %s was created %lu day"
" in the future (time warp or clock problem)\n",
"key %s was created %lu days"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pk), d);
}
if (!opt.ignore_time_conflict)
return GPG_ERR_TIME_CONFLICT;
}
/* Check whether the key has expired. We check the has_expired
* flag which is set after a full evaluation of the key (getkey.c)
* as well as a simple compare to the current time in case the
* merge has for whatever reasons not been done. */
if (pk->has_expired || (pk->expiredate && pk->expiredate < cur_time))
{
char buf[11];
if (opt.verbose)
log_info (_("Note: signature key %s expired %s\n"),
keystr_from_pk(pk), isotimestamp( pk->expiredate ) );
snprintf (buf, sizeof buf, "%lu",(ulong)pk->expiredate);
write_status_text (STATUS_KEYEXPIRED, buf);
if (r_expired)
*r_expired = 1;
}
if (pk->flags.revoked)
{
if (opt.verbose)
log_info (_("Note: signature key %s has been revoked\n"),
keystr_from_pk(pk));
if (r_revoked)
*r_revoked=1;
}
return 0;
}
/* Finish generating a signature and check it. Concretely: make sure
* that the signature is valid (it was not created prior to the key,
* the public key was created in the past, and the signature does not
* include any unsupported critical features), finish computing the
* digest by adding the relevant data from the signature packet, and
* check that the signature verifies the digest.
*
* DIGEST contains a hash context, which has already hashed the signed
* data. This function adds the relevant meta-data from the signature
* packet to compute the final hash. (See Section 5.2 of RFC 4880:
* "The concatenation of the data being signed and the signature data
* from the version number through the hashed subpacket data
* (inclusive) is hashed.")
*
* SIG is the signature to check.
*
* PK is the public key used to generate the signature.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has expired
* (0 otherwise). Note: PK being expired does not cause this function
* to fail.
*
* If R_REVOKED is not NULL, *R_REVOKED is set to 1 if PK has been
* revoked (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
* If RET_PK is not NULL, PK is copied into RET_PK on success.
*
* Returns 0 on success. An error code other. */
static int
check_signature_end (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen,
int *r_expired, int *r_revoked, PKT_public_key *ret_pk)
{
int rc = 0;
if ((rc = check_signature_metadata_validity (pk, sig,
r_expired, r_revoked)))
return rc;
if ((rc = check_signature_end_simple (pk, sig, digest,
extrahash, extrahashlen)))
return rc;
if (!rc && ret_pk)
copy_public_key(ret_pk,pk);
return rc;
}
/* This function is similar to check_signature_end, but it only checks
* whether the signature was generated by PK. It does not check
* expiration, revocation, etc. */
static int
check_signature_end_simple (PKT_public_key *pk, PKT_signature *sig,
gcry_md_hd_t digest,
const void *extrahash, size_t extrahashlen)
{
gcry_mpi_t result = NULL;
int rc = 0;
if (!opt.flags.allow_weak_digest_algos)
{
if (is_weak_digest (sig->digest_algo))
{
print_digest_rejected_note (sig->digest_algo);
return GPG_ERR_DIGEST_ALGO;
}
}
/* For key signatures check that the key has a cert usage. We may
* do this only for subkeys because the primary may always issue key
* signature. The latter may not be reflected in the pubkey_usage
* field because we need to check the key signatures to extract the
* key usage. */
if (!pk->flags.primary
&& IS_CERT (sig) && !(pk->pubkey_usage & PUBKEY_USAGE_CERT))
{
rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE);
if (!opt.quiet)
log_info (_("bad key signature from key %s: %s (0x%02x, 0x%x)\n"),
keystr_from_pk (pk), gpg_strerror (rc),
sig->sig_class, pk->pubkey_usage);
return rc;
}
/* For data signatures check that the key has sign usage. */
if (!IS_BACK_SIG (sig) && IS_SIG (sig)
&& !(pk->pubkey_usage & PUBKEY_USAGE_SIG))
{
rc = gpg_error (GPG_ERR_WRONG_KEY_USAGE);
if (!opt.quiet)
log_info (_("bad data signature from key %s: %s (0x%02x, 0x%x)\n"),
keystr_from_pk (pk), gpg_strerror (rc),
sig->sig_class, pk->pubkey_usage);
return rc;
}
/* Make sure the digest algo is enabled (in case of a detached
* signature). */
gcry_md_enable (digest, sig->digest_algo);
/* Complete the digest. */
if (sig->version >= 4)
gcry_md_putc (digest, sig->version);
gcry_md_putc( digest, sig->sig_class );
if (sig->version < 4)
{
u32 a = sig->timestamp;
gcry_md_putc (digest, ((a >> 24) & 0xff));
gcry_md_putc (digest, ((a >> 16) & 0xff));
gcry_md_putc (digest, ((a >> 8) & 0xff));
gcry_md_putc (digest, ( a & 0xff));
}
else
{
byte buf[10];
int i;
size_t n;
gcry_md_putc (digest, sig->pubkey_algo);
gcry_md_putc (digest, sig->digest_algo);
if (sig->hashed)
{
n = sig->hashed->len;
gcry_md_putc (digest, (n >> 8) );
gcry_md_putc (digest, n );
gcry_md_write (digest, sig->hashed->data, n);
n += 6;
}
else
{
/* Two octets for the (empty) length of the hashed
* section. */
gcry_md_putc (digest, 0);
gcry_md_putc (digest, 0);
n = 6;
}
/* Hash data from the literal data packet. */
if (sig->version >= 5
&& (sig->sig_class == 0x00 || sig->sig_class == 0x01))
{
/* - One octet content format
* - File name (one octet length followed by the name)
* - Four octet timestamp */
if (extrahash && extrahashlen)
gcry_md_write (digest, extrahash, extrahashlen);
else /* Detached signature. */
{
memset (buf, 0, 6);
gcry_md_write (digest, buf, 6);
}
}
/* Add some magic per Section 5.2.4 of RFC 4880. */
i = 0;
buf[i++] = sig->version;
buf[i++] = 0xff;
if (sig->version >= 5)
{
#if SIZEOF_SIZE_T > 4
buf[i++] = n >> 56;
buf[i++] = n >> 48;
buf[i++] = n >> 40;
buf[i++] = n >> 32;
#else
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
#endif
}
buf[i++] = n >> 24;
buf[i++] = n >> 16;
buf[i++] = n >> 8;
buf[i++] = n;
gcry_md_write (digest, buf, i);
}
gcry_md_final( digest );
/* Convert the digest to an MPI. */
result = encode_md_value (pk, digest, sig->digest_algo );
if (!result)
return GPG_ERR_GENERAL;
/* Verify the signature. */
if (DBG_CLOCK && sig->sig_class <= 0x01)
log_clock ("enter pk_verify");
rc = pk_verify( pk->pubkey_algo, result, sig->data, pk->pkey );
if (DBG_CLOCK && sig->sig_class <= 0x01)
log_clock ("leave pk_verify");
gcry_mpi_release (result);
if (!rc && sig->flags.unknown_critical)
{
log_info(_("assuming bad signature from key %s"
" due to an unknown critical bit\n"),keystr_from_pk(pk));
rc = GPG_ERR_BAD_SIGNATURE;
}
return rc;
}
/* Add a uid node to a hash context. See section 5.2.4, paragraph 4
* of RFC 4880. */
static void
hash_uid_packet (PKT_user_id *uid, gcry_md_hd_t md, PKT_signature *sig )
{
if (uid->attrib_data)
{
if (sig->version >= 4)
{
byte buf[5];
buf[0] = 0xd1; /* packet of type 17 */
buf[1] = uid->attrib_len >> 24; /* always use 4 length bytes */
buf[2] = uid->attrib_len >> 16;
buf[3] = uid->attrib_len >> 8;
buf[4] = uid->attrib_len;
gcry_md_write( md, buf, 5 );
}
gcry_md_write( md, uid->attrib_data, uid->attrib_len );
}
else
{
if (sig->version >= 4)
{
byte buf[5];
buf[0] = 0xb4; /* indicates a userid packet */
buf[1] = uid->len >> 24; /* always use 4 length bytes */
buf[2] = uid->len >> 16;
buf[3] = uid->len >> 8;
buf[4] = uid->len;
gcry_md_write( md, buf, 5 );
}
gcry_md_write( md, uid->name, uid->len );
}
}
static void
cache_sig_result ( PKT_signature *sig, int result )
{
if (!result)
{
sig->flags.checked = 1;
sig->flags.valid = 1;
}
else if (gpg_err_code (result) == GPG_ERR_BAD_SIGNATURE)
{
sig->flags.checked = 1;
sig->flags.valid = 0;
}
else
{
sig->flags.checked = 0;
sig->flags.valid = 0;
}
}
/* SIG is a key revocation signature. Check if this signature was
* generated by any of the public key PK's designated revokers.
*
* PK is the public key that SIG allegedly revokes.
*
* SIG is the revocation signature to check.
*
* This function avoids infinite recursion, which can happen if two
* keys are designed revokers for each other and they revoke each
* other. This is done by observing that if a key A is revoked by key
* B we still consider the revocation to be valid even if B is
* revoked. Thus, we don't need to determine whether B is revoked to
* determine whether A has been revoked by B, we just need to check
* the signature.
*
* Returns 0 if sig is valid (i.e. pk is revoked), non-0 if not
* revoked. We are careful to make sure that GPG_ERR_NO_PUBKEY is
* only returned when a revocation signature is from a valid
* revocation key designated in a revkey subpacket, but the revocation
* key itself isn't present.
*
* XXX: This code will need to be modified if gpg ever becomes
* multi-threaded. Note that this guarantees that a designated
* revocation sig will never be considered valid unless it is actually
* valid, as well as being issued by a revocation key in a valid
* direct signature. Note also that this is written so that a revoked
* revoker can still issue revocations: i.e. If A revokes B, but A is
* revoked, B is still revoked. I'm not completely convinced this is
* the proper behavior, but it matches how PGP does it. -dms */
int
check_revocation_keys (ctrl_t ctrl, PKT_public_key *pk, PKT_signature *sig)
{
static int busy=0;
int i;
int rc = GPG_ERR_GENERAL;
log_assert (IS_KEY_REV(sig));
log_assert ((sig->keyid[0]!=pk->keyid[0]) || (sig->keyid[0]!=pk->keyid[1]));
/* Avoid infinite recursion. Consider the following:
*
* - We want to check if A is revoked.
*
* - C is a designated revoker for B and has revoked B.
*
* - B is a designated revoker for A and has revoked A.
*
* When checking if A is revoked (in merge_selfsigs_main), we
* observe that A has a designed revoker. As such, we call this
* function. This function sees that there is a valid revocation
* signature, which is signed by B. It then calls check_signature()
* to verify that the signature is good. To check the sig, we need
* to lookup B. Looking up B means calling merge_selfsigs_main,
* which checks whether B is revoked, which calls this function to
* see if B was revoked by some key.
*
* In this case, the added level of indirection doesn't hurt. It
* just means a bit more work. However, if C == A, then we'd end up
* in a loop. But, it doesn't make sense to look up C anyways: even
* if B is revoked, we conservatively consider a valid revocation
* signed by B to revoke A. Since this is the only place where this
* type of recursion can occur, we simply cause this function to
* fail if it is entered recursively. */
if (busy)
{
/* Return an error (i.e. not revoked), but mark the pk as
uncacheable as we don't really know its revocation status
until it is checked directly. */
pk->flags.dont_cache = 1;
return rc;
}
busy=1;
/* es_printf("looking at %08lX with a sig from %08lX\n",(ulong)pk->keyid[1],
(ulong)sig->keyid[1]); */
/* is the issuer of the sig one of our revokers? */
if( !pk->revkey && pk->numrevkeys )
BUG();
else
for(i=0;i<pk->numrevkeys;i++)
{
/* The revoker's keyid. */
u32 keyid[2];
keyid_from_fingerprint (ctrl, pk->revkey[i].fpr, pk->revkey[i].fprlen,
keyid);
if(keyid[0]==sig->keyid[0] && keyid[1]==sig->keyid[1])
/* The signature was generated by a designated revoker.
Verify the signature. */
{
gcry_md_hd_t md;
if (gcry_md_open (&md, sig->digest_algo, 0))
BUG ();
hash_public_key(md,pk);
/* Note: check_signature only checks that the signature
is good. It does not fail if the key is revoked. */
rc = check_signature (ctrl, sig, md);
cache_sig_result(sig,rc);
gcry_md_close (md);
break;
}
}
busy=0;
return rc;
}
/* Check that the backsig BACKSIG from the subkey SUB_PK to its
* primary key MAIN_PK is valid.
*
* Backsigs (0x19) have the same format as binding sigs (0x18), but
* this function is simpler than check_key_signature in a few ways.
* For example, there is no support for expiring backsigs since it is
* questionable what such a thing actually means. Note also that the
* sig cache check here, unlike other sig caches in GnuPG, is not
* persistent. */
int
check_backsig (PKT_public_key *main_pk,PKT_public_key *sub_pk,
PKT_signature *backsig)
{
gcry_md_hd_t md;
int rc;
/* Always check whether the algorithm is available. Although
gcry_md_open would throw an error, some libgcrypt versions will
print a debug message in that case too. */
if ((rc=openpgp_md_test_algo (backsig->digest_algo)))
return rc;
if(!opt.no_sig_cache && backsig->flags.checked)
return backsig->flags.valid? 0 : gpg_error (GPG_ERR_BAD_SIGNATURE);
rc = gcry_md_open (&md, backsig->digest_algo,0);
if (!rc)
{
hash_public_key(md,main_pk);
hash_public_key(md,sub_pk);
rc = check_signature_end (sub_pk, backsig, md, NULL, 0, NULL, NULL, NULL);
cache_sig_result(backsig,rc);
gcry_md_close(md);
}
return rc;
}
/* Check that a signature over a key is valid. This is a
* specialization of check_key_signature2 with the unnamed parameters
* passed as NULL. See the documentation for that function for more
* details. */
int
check_key_signature (ctrl_t ctrl, kbnode_t root, kbnode_t node,
int *is_selfsig)
{
return check_key_signature2 (ctrl, root, node, NULL, NULL,
is_selfsig, NULL, NULL);
}
/* Returns whether SIGNER generated the signature SIG over the packet
* PACKET, which is a key, subkey or uid, and comes from the key block
* KB. (KB is PACKET's corresponding keyblock; we don't assume that
* SIG has been added to the keyblock.)
*
* If SIGNER is set, then checks whether SIGNER generated the
* signature. Otherwise, uses SIG->KEYID to find the alleged signer.
* This parameter can be used to effectively override the alleged
* signer that is stored in SIG.
*
* KB may be NULL if SIGNER is set.
*
* Unlike check_key_signature, this function ignores any cached
* results! That is, it does not consider SIG->FLAGS.CHECKED and
* SIG->FLAGS.VALID nor does it set them.
*
* This doesn't check the signature's semantic mean. Concretely, it
* doesn't check whether a non-self signed revocation signature was
* created by a designated revoker. In fact, it doesn't return an
* error for a binding generated by a completely different key!
*
* Returns 0 if the signature is valid. Returns GPG_ERR_SIG_CLASS if
* this signature can't be over PACKET. Returns GPG_ERR_NOT_FOUND if
* the key that generated the signature (according to SIG) could not
* be found. Returns GPG_ERR_BAD_SIGNATURE if the signature is bad.
* Other errors codes may be returned if something else goes wrong.
*
* IF IS_SELFSIG is not NULL, sets *IS_SELFSIG to 1 if this is a
* self-signature (by the key's primary key) or 0 if not.
*
* If RET_PK is not NULL, returns a copy of the public key that
* generated the signature (i.e., the signer) on success. This must
* be released by the caller using release_public_key_parts (). */
gpg_error_t
check_signature_over_key_or_uid (ctrl_t ctrl, PKT_public_key *signer,
PKT_signature *sig, KBNODE kb, PACKET *packet,
int *is_selfsig, PKT_public_key *ret_pk)
{
int rc;
PKT_public_key *pripk = kb->pkt->pkt.public_key;
gcry_md_hd_t md;
int signer_alloced = 0;
int stub_is_selfsig;
if (!is_selfsig)
is_selfsig = &stub_is_selfsig;
rc = openpgp_pk_test_algo (sig->pubkey_algo);
if (rc)
return rc;
rc = openpgp_md_test_algo (sig->digest_algo);
if (rc)
return rc;
/* A signature's class indicates the type of packet that it
signs. */
if (IS_BACK_SIG (sig) || IS_KEY_SIG (sig) || IS_KEY_REV (sig))
{
/* Key revocations can only be over primary keys. */
if (packet->pkttype != PKT_PUBLIC_KEY)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig))
{
if (packet->pkttype != PKT_PUBLIC_SUBKEY)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else if (IS_UID_SIG (sig) || IS_UID_REV (sig))
{
if (packet->pkttype != PKT_USER_ID)
return gpg_error (GPG_ERR_SIG_CLASS);
}
else
return gpg_error (GPG_ERR_SIG_CLASS);
/* PACKET is the right type for SIG. */
if (signer)
{
if (signer->keyid[0] == pripk->keyid[0]
&& signer->keyid[1] == pripk->keyid[1])
*is_selfsig = 1;
else
*is_selfsig = 0;
}
else
{
/* Get the signer. If possible, avoid a look up. */
if (sig->keyid[0] == pripk->keyid[0]
&& sig->keyid[1] == pripk->keyid[1])
{
/* Issued by the primary key. */
signer = pripk;
*is_selfsig = 1;
}
else
{
/* See if one of the subkeys was the signer (although this
* is extremely unlikely). */
kbnode_t ctx = NULL;
kbnode_t n;
while ((n = walk_kbnode (kb, &ctx, 0)))
{
PKT_public_key *subk;
if (n->pkt->pkttype != PKT_PUBLIC_SUBKEY)
continue;
subk = n->pkt->pkt.public_key;
if (sig->keyid[0] == subk->keyid[0]
&& sig->keyid[1] == subk->keyid[1])
{
/* Issued by a subkey. */
signer = subk;
break;
}
}
if (! signer)
{
/* Signer by some other key. */
*is_selfsig = 0;
if (ret_pk)
{
signer = ret_pk;
- /* FIXME: Using memset here is probematic because it
+ /* FIXME: Using memset here is problematic because it
* assumes that there are no allocated fields in
* SIGNER. */
memset (signer, 0, sizeof (*signer));
signer_alloced = 1;
}
else
{
signer = xmalloc_clear (sizeof (*signer));
signer_alloced = 2;
}
if (IS_CERT (sig))
signer->req_usage = PUBKEY_USAGE_CERT;
rc = get_pubkey_for_sig (ctrl, signer, sig, NULL);
if (rc)
{
xfree (signer);
signer = NULL;
signer_alloced = 0;
goto leave;
}
}
}
}
/* We checked above that we supported this algo, so an error here is
* a bug. */
if (gcry_md_open (&md, sig->digest_algo, 0))
BUG ();
/* Hash the relevant data. */
if (IS_KEY_SIG (sig) || IS_KEY_REV (sig))
{
log_assert (packet->pkttype == PKT_PUBLIC_KEY);
hash_public_key (md, packet->pkt.public_key);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
else if (IS_BACK_SIG (sig))
{
log_assert (packet->pkttype == PKT_PUBLIC_KEY);
hash_public_key (md, packet->pkt.public_key);
hash_public_key (md, signer);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
else if (IS_SUBKEY_SIG (sig) || IS_SUBKEY_REV (sig))
{
log_assert (packet->pkttype == PKT_PUBLIC_SUBKEY);
hash_public_key (md, pripk);
hash_public_key (md, packet->pkt.public_key);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
else if (IS_UID_SIG (sig) || IS_UID_REV (sig))
{
log_assert (packet->pkttype == PKT_USER_ID);
if (sig->digest_algo == DIGEST_ALGO_SHA1 && !*is_selfsig
&& !opt.flags.allow_weak_key_signatures)
{
/* If the signature was created using SHA-1 we consider this
* signature invalid because it makes it possible to mount a
* chosen-prefix collision. We don't do this for
* self-signatures, though. */
print_sha1_keysig_rejected_note ();
rc = gpg_error (GPG_ERR_DIGEST_ALGO);
}
else
{
hash_public_key (md, pripk);
hash_uid_packet (packet->pkt.user_id, md, sig);
rc = check_signature_end_simple (signer, sig, md, NULL, 0);
}
}
else
{
/* We should never get here. (The first if above should have
* already caught this error.) */
BUG ();
}
gcry_md_close (md);
leave:
if (! rc && ret_pk && ret_pk != signer)
copy_public_key (ret_pk, signer);
if (signer_alloced)
{
/* We looked up SIGNER; it is not a pointer into KB. */
release_public_key_parts (signer);
/* Free if we also allocated the memory. */
if (signer_alloced == 2)
xfree (signer);
}
return rc;
}
/* Check that a signature over a key (e.g., a key revocation, key
* binding, user id certification, etc.) is valid. If the function
* detects a self-signature, it uses the public key from the specified
* key block and does not bother looking up the key specified in the
* signature packet.
*
* ROOT is a keyblock.
*
* NODE references a signature packet that appears in the keyblock
* that should be verified.
*
* If CHECK_PK is set, the specified key is sometimes preferred for
* verifying signatures. See the implementation for details.
*
* If RET_PK is not NULL, the public key that successfully verified
* the signature is copied into *RET_PK.
*
* If IS_SELFSIG is not NULL, *IS_SELFSIG is set to 1 if NODE is a
* self-signature.
*
* If R_EXPIREDATE is not NULL, *R_EXPIREDATE is set to the expiry
* date.
*
* If R_EXPIRED is not NULL, *R_EXPIRED is set to 1 if PK has been
* expired (0 otherwise). Note: PK being revoked does not cause this
* function to fail.
*
*
* If OPT.NO_SIG_CACHE is not set, this function will first check if
* the result of a previous verification is already cached in the
* signature packet's data structure.
*
* TODO: add r_revoked here as well. It has the same problems as
* r_expiredate and r_expired and the cache [nw]. Which problems [wk]? */
int
check_key_signature2 (ctrl_t ctrl,
kbnode_t root, kbnode_t node, PKT_public_key *check_pk,
PKT_public_key *ret_pk, int *is_selfsig,
u32 *r_expiredate, int *r_expired )
{
PKT_public_key *pk;
PKT_signature *sig;
int algo;
int rc;
if (is_selfsig)
*is_selfsig = 0;
if (r_expiredate)
*r_expiredate = 0;
if (r_expired)
*r_expired = 0;
log_assert (node->pkt->pkttype == PKT_SIGNATURE);
log_assert (root->pkt->pkttype == PKT_PUBLIC_KEY);
pk = root->pkt->pkt.public_key;
sig = node->pkt->pkt.signature;
algo = sig->digest_algo;
/* Check whether we have cached the result of a previous signature
* check. Note that we may no longer have the pubkey or hash
* needed to verify a sig, but can still use the cached value. A
* cache refresh detects and clears these cases. */
if ( !opt.no_sig_cache )
{
cache_stats.total++;
if (sig->flags.checked) /* Cached status available. */
{
cache_stats.cached++;
if (is_selfsig)
{
u32 keyid[2];
keyid_from_pk (pk, keyid);
if (keyid[0] == sig->keyid[0] && keyid[1] == sig->keyid[1])
*is_selfsig = 1;
}
/* BUG: This is wrong for non-self-sigs... needs to be the
* actual pk. */
rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
if (rc)
return rc;
if (sig->flags.valid)
{
cache_stats.goodsig++;
return 0;
}
cache_stats.badsig++;
return gpg_error (GPG_ERR_BAD_SIGNATURE);
}
}
rc = openpgp_pk_test_algo(sig->pubkey_algo);
if (rc)
return rc;
rc = openpgp_md_test_algo(algo);
if (rc)
return rc;
if (IS_KEY_REV (sig))
{
u32 keyid[2];
keyid_from_pk( pk, keyid );
/* Is it a designated revoker? */
if (keyid[0] != sig->keyid[0] || keyid[1] != sig->keyid[1])
rc = check_revocation_keys (ctrl, pk, sig);
else
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
rc = check_signature_over_key_or_uid (ctrl, pk, sig,
root, root->pkt,
is_selfsig, ret_pk);
}
}
else if (IS_SUBKEY_REV (sig) || IS_SUBKEY_SIG (sig))
{
kbnode_t snode = find_prev_kbnode (root, node, PKT_PUBLIC_SUBKEY);
if (snode)
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
{
/* A subkey revocation (0x28) must be a self-sig, but a
* subkey signature (0x18) needn't be. */
rc = check_signature_over_key_or_uid (ctrl,
IS_SUBKEY_SIG (sig)
? NULL : pk,
sig, root, snode->pkt,
is_selfsig, ret_pk);
}
}
else
{
if (opt.verbose)
{
if (IS_SUBKEY_REV (sig))
log_info (_("key %s: no subkey for subkey"
" revocation signature\n"), keystr_from_pk(pk));
else if (sig->sig_class == 0x18)
log_info(_("key %s: no subkey for subkey"
" binding signature\n"), keystr_from_pk(pk));
}
rc = GPG_ERR_SIG_CLASS;
}
}
else if (IS_KEY_SIG (sig)) /* direct key signature */
{
rc = check_signature_metadata_validity (pk, sig,
r_expired, NULL);
if (! rc)
rc = check_signature_over_key_or_uid (ctrl, pk, sig, root, root->pkt,
is_selfsig, ret_pk);
}
else if (IS_UID_SIG (sig) || IS_UID_REV (sig))
{
kbnode_t unode = find_prev_kbnode (root, node, PKT_USER_ID);
if (unode)
{
rc = check_signature_metadata_validity (pk, sig, r_expired, NULL);
if (! rc)
{
/* If this is a self-sig, ignore check_pk. */
rc = check_signature_over_key_or_uid
(ctrl,
keyid_cmp (pk_keyid (pk), sig->keyid) == 0 ? pk : check_pk,
sig, root, unode->pkt, NULL, ret_pk);
}
}
else
{
if (!opt.quiet)
log_info ("key %s: no user ID for key signature packet"
" of class %02x\n",keystr_from_pk(pk),sig->sig_class);
rc = GPG_ERR_SIG_CLASS;
}
}
else
{
log_info ("sig issued by %s with class %d (digest: %02x %02x)"
" is not valid over a user id or a key id, ignoring.\n",
keystr (sig->keyid), sig->sig_class,
sig->digest_start[0], sig->digest_start[1]);
rc = gpg_error (GPG_ERR_BAD_SIGNATURE);
}
cache_sig_result (sig, rc);
return rc;
}
diff --git a/g10/sign.c b/g10/sign.c
index 67ea5a038..399d5d76d 100644
--- a/g10/sign.c
+++ b/g10/sign.c
@@ -1,2038 +1,2038 @@
/* sign.c - sign data
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006,
* 2007, 2010, 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "gpg.h"
#include "options.h"
#include "packet.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "keydb.h"
#include "../common/util.h"
#include "main.h"
#include "filter.h"
#include "../common/ttyio.h"
#include "trustdb.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "pkglue.h"
#include "../common/sysutils.h"
#include "call-agent.h"
#include "../common/mbox-util.h"
#include "../common/compliance.h"
#ifdef HAVE_DOSISH_SYSTEM
#define LF "\r\n"
#else
#define LF "\n"
#endif
/* Hack */
static int recipient_digest_algo;
/* A type for the extra data we hash into v5 signature packets. */
struct pt_extra_hash_data_s
{
unsigned char mode;
u32 timestamp;
unsigned char namelen;
char name[1];
};
typedef struct pt_extra_hash_data_s *pt_extra_hash_data_t;
/*
* Create notations and other stuff. It is assumed that the strings in
* STRLIST are already checked to contain only printable data and have
* a valid NAME=VALUE format.
*/
static void
mk_notation_policy_etc (ctrl_t ctrl, PKT_signature *sig,
PKT_public_key *pk, PKT_public_key *pksk)
{
const char *string;
char *p = NULL;
strlist_t pu = NULL;
struct notation *nd = NULL;
struct expando_args args;
log_assert (sig->version >= 4);
memset (&args, 0, sizeof(args));
args.pk = pk;
args.pksk = pksk;
/* Notation data. */
if (IS_ATTST_SIGS(sig))
;
else if (IS_SIG(sig) && opt.sig_notations)
nd = opt.sig_notations;
else if (IS_CERT(sig) && opt.cert_notations)
nd = opt.cert_notations;
if (nd)
{
struct notation *item;
for (item = nd; item; item = item->next)
{
item->altvalue = pct_expando (ctrl, item->value,&args);
if (!item->altvalue)
log_error (_("WARNING: unable to %%-expand notation "
"(too large). Using unexpanded.\n"));
}
keygen_add_notations (sig, nd);
for (item = nd; item; item = item->next)
{
xfree (item->altvalue);
item->altvalue = NULL;
}
}
/* Set policy URL. */
if (IS_ATTST_SIGS(sig))
;
else if (IS_SIG(sig) && opt.sig_policy_url)
pu = opt.sig_policy_url;
else if (IS_CERT(sig) && opt.cert_policy_url)
pu = opt.cert_policy_url;
for (; pu; pu = pu->next)
{
string = pu->d;
p = pct_expando (ctrl, string, &args);
if (!p)
{
log_error(_("WARNING: unable to %%-expand policy URL "
"(too large). Using unexpanded.\n"));
p = xstrdup(string);
}
build_sig_subpkt (sig, (SIGSUBPKT_POLICY
| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)),
p, strlen (p));
xfree (p);
}
/* Preferred keyserver URL. */
if (IS_SIG(sig) && opt.sig_keyserver_url)
pu = opt.sig_keyserver_url;
for (; pu; pu = pu->next)
{
string = pu->d;
p = pct_expando (ctrl, string, &args);
if (!p)
{
log_error (_("WARNING: unable to %%-expand preferred keyserver URL"
" (too large). Using unexpanded.\n"));
p = xstrdup (string);
}
build_sig_subpkt (sig, (SIGSUBPKT_PREF_KS
| ((pu->flags & 1)?SIGSUBPKT_FLAG_CRITICAL:0)),
p, strlen (p));
xfree (p);
}
/* Set signer's user id. */
if (IS_SIG (sig) && !opt.flags.disable_signer_uid)
{
char *mbox;
/* For now we use the uid which was used to locate the key. */
if (pksk->user_id
&& (mbox = mailbox_from_userid (pksk->user_id->name, 0)))
{
if (DBG_LOOKUP)
log_debug ("setting Signer's UID to '%s'\n", mbox);
build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID, mbox, strlen (mbox));
xfree (mbox);
}
else if (opt.sender_list)
{
/* If a list of --sender was given we scan that list and use
* the first one matching a user id of the current key. */
/* FIXME: We need to get the list of user ids for the PKSK
* packet. That requires either a function to look it up
* again or we need to extend the key packet struct to link
* to the primary key which in turn could link to the user
* ids. Too much of a change right now. Let's take just
* one from the supplied list and hope that the caller
* passed a matching one. */
build_sig_subpkt (sig, SIGSUBPKT_SIGNERS_UID,
opt.sender_list->d, strlen (opt.sender_list->d));
}
}
}
/*
* Put the Key Block subpacket into SIG for key PKSK. Returns an
* error code on failure.
*/
static gpg_error_t
mk_sig_subpkt_key_block (ctrl_t ctrl, PKT_signature *sig, PKT_public_key *pksk)
{
gpg_error_t err;
char *mbox;
char *filterexp = NULL;
int save_opt_armor = opt.armor;
int save_opt_verbose = opt.verbose;
char hexfpr[2*MAX_FINGERPRINT_LEN + 1];
void *data = NULL;
size_t datalen;
kbnode_t keyblock = NULL;
push_export_filters ();
opt.armor = 0;
hexfingerprint (pksk, hexfpr, sizeof hexfpr);
/* Get the user id so that we know which one to insert into the
* key. */
if (pksk->user_id
&& (mbox = mailbox_from_userid (pksk->user_id->name, 0)))
{
if (DBG_LOOKUP)
log_debug ("including key with UID '%s' (specified)\n", mbox);
filterexp = xasprintf ("keep-uid= -- mbox = %s", mbox);
xfree (mbox);
}
else if (opt.sender_list)
{
/* If --sender was given we use the first one from that list. */
if (DBG_LOOKUP)
log_debug ("including key with UID '%s' (--sender)\n",
opt.sender_list->d);
filterexp = xasprintf ("keep-uid= -- mbox = %s", opt.sender_list->d);
}
else /* Use the primary user id. */
{
if (DBG_LOOKUP)
log_debug ("including key with primary UID\n");
filterexp = xstrdup ("keep-uid= primary -t");
}
if (DBG_LOOKUP)
log_debug ("export filter expression: %s\n", filterexp);
err = parse_and_set_export_filter (filterexp);
if (err)
goto leave;
xfree (filterexp);
filterexp = xasprintf ("drop-subkey= fpr <> %s && usage !~ e", hexfpr);
if (DBG_LOOKUP)
log_debug ("export filter expression: %s\n", filterexp);
err = parse_and_set_export_filter (filterexp);
if (err)
goto leave;
opt.verbose = 0;
err = export_pubkey_buffer (ctrl, hexfpr, EXPORT_MINIMAL|EXPORT_CLEAN,
"", 1, /* Prefix with the reserved byte. */
NULL, &keyblock, &data, &datalen);
opt.verbose = save_opt_verbose;
if (err)
{
log_error ("failed to get to be included key: %s\n", gpg_strerror (err));
goto leave;
}
build_sig_subpkt (sig, SIGSUBPKT_KEY_BLOCK, data, datalen);
leave:
xfree (data);
release_kbnode (keyblock);
xfree (filterexp);
opt.armor = save_opt_armor;
pop_export_filters ();
return err;
}
/*
* Helper to hash a user ID packet.
*/
static void
hash_uid (gcry_md_hd_t md, int sigversion, const PKT_user_id *uid)
{
byte buf[5];
(void)sigversion;
if (uid->attrib_data)
{
buf[0] = 0xd1; /* Indicates an attribute packet. */
buf[1] = uid->attrib_len >> 24; /* Always use 4 length bytes. */
buf[2] = uid->attrib_len >> 16;
buf[3] = uid->attrib_len >> 8;
buf[4] = uid->attrib_len;
}
else
{
buf[0] = 0xb4; /* Indicates a userid packet. */
buf[1] = uid->len >> 24; /* Always use 4 length bytes. */
buf[2] = uid->len >> 16;
buf[3] = uid->len >> 8;
buf[4] = uid->len;
}
gcry_md_write( md, buf, 5 );
if (uid->attrib_data)
gcry_md_write (md, uid->attrib_data, uid->attrib_len );
else
gcry_md_write (md, uid->name, uid->len );
}
/*
* Helper to hash some parts from the signature. EXTRAHASH gives the
* extra data to be hashed into v5 signatures; it may by NULL for
* detached signatures.
*/
static void
hash_sigversion_to_magic (gcry_md_hd_t md, const PKT_signature *sig,
pt_extra_hash_data_t extrahash)
{
byte buf[10];
int i;
size_t n;
gcry_md_putc (md, sig->version);
gcry_md_putc (md, sig->sig_class);
gcry_md_putc (md, sig->pubkey_algo);
gcry_md_putc (md, sig->digest_algo);
if (sig->hashed)
{
n = sig->hashed->len;
gcry_md_putc (md, (n >> 8) );
gcry_md_putc (md, n );
gcry_md_write (md, sig->hashed->data, n );
n += 6;
}
else
{
gcry_md_putc (md, 0); /* Always hash the length of the subpacket. */
gcry_md_putc (md, 0);
n = 6;
}
/* Hash data from the literal data packet. */
if (sig->version >= 5 && (sig->sig_class == 0x00 || sig->sig_class == 0x01))
{
/* - One octet content format
* - File name (one octet length followed by the name)
* - Four octet timestamp */
if (extrahash)
{
buf[0] = extrahash->mode;
buf[1] = extrahash->namelen;
gcry_md_write (md, buf, 2);
if (extrahash->namelen)
gcry_md_write (md, extrahash->name, extrahash->namelen);
buf[0] = extrahash->timestamp >> 24;
buf[1] = extrahash->timestamp >> 16;
buf[2] = extrahash->timestamp >> 8;
buf[3] = extrahash->timestamp;
gcry_md_write (md, buf, 4);
}
else /* Detached signatures */
{
memset (buf, 0, 6);
gcry_md_write (md, buf, 6);
}
}
/* Add some magic aka known as postscript. The idea was to make it
* impossible to make up a document with a v3 signature and then
* turn this into a v4 signature for another document. The last
* hashed 5 bytes of a v4 signature should never look like a the
* last 5 bytes of a v3 signature. The length can be used to parse
* from the end. */
i = 0;
buf[i++] = sig->version; /* Hash convention version. */
buf[i++] = 0xff; /* Not any sig type value. */
if (sig->version >= 5)
{
/* Note: We don't hashed any data larger than 2^32 and thus we
* can always use 0 here. See also note below. */
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
buf[i++] = 0;
}
buf[i++] = n >> 24; /* (n is only 16 bit, so this is always 0) */
buf[i++] = n >> 16;
buf[i++] = n >> 8;
buf[i++] = n;
gcry_md_write (md, buf, i);
}
/* Perform the sign operation. If CACHE_NONCE is given the agent is
* advised to use that cached passphrase for the key. SIGNHINTS has
* hints so that we can do some additional checks. */
static int
do_sign (ctrl_t ctrl, PKT_public_key *pksk, PKT_signature *sig,
gcry_md_hd_t md, int mdalgo,
const char *cache_nonce, unsigned int signhints)
{
gpg_error_t err;
byte *dp;
char *hexgrip;
/* An ADSK key commonly has a creation date older than the primary
* key. For example because the ADSK is used as an archive key for
* a group of users. */
if (pksk->timestamp > sig->timestamp && !(signhints & SIGNHINT_ADSK))
{
ulong d = pksk->timestamp - sig->timestamp;
log_info (ngettext("key %s was created %lu second"
" in the future (time warp or clock problem)\n",
"key %s was created %lu seconds"
" in the future (time warp or clock problem)\n",
d), keystr_from_pk (pksk), d);
if (!opt.ignore_time_conflict)
return gpg_error (GPG_ERR_TIME_CONFLICT);
}
print_pubkey_algo_note (pksk->pubkey_algo);
if (!mdalgo)
mdalgo = gcry_md_get_algo (md);
if ((signhints & SIGNHINT_KEYSIG) && !(signhints & SIGNHINT_SELFSIG)
&& mdalgo == GCRY_MD_SHA1
&& !opt.flags.allow_weak_key_signatures)
{
/* We do not allow the creation of third-party key signatures
* using SHA-1 because we also reject them when verifying. Note
* that this will render dsa1024 keys unsuitable for such
* keysigs and in turn the WoT. */
print_sha1_keysig_rejected_note ();
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto leave;
}
/* Check compliance but always allow for key revocations. */
if (!IS_KEY_REV (sig)
&& ! gnupg_digest_is_allowed (opt.compliance, 1, mdalgo))
{
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
gcry_md_algo_name (mdalgo),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto leave;
}
if (!IS_KEY_REV (sig)
&& ! gnupg_pk_is_allowed (opt.compliance, PK_USE_SIGNING,
pksk->pubkey_algo, 0,
pksk->pkey, nbits_from_pk (pksk), NULL))
{
log_error (_("key %s may not be used for signing in %s mode\n"),
keystr_from_pk (pksk),
gnupg_compliance_option_string (opt.compliance));
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
if (!gnupg_rng_is_compliant (opt.compliance))
{
err = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
"RNG",
gnupg_compliance_option_string (opt.compliance));
write_status_error ("random-compliance", err);
goto leave;
}
print_digest_algo_note (mdalgo);
dp = gcry_md_read (md, mdalgo);
sig->digest_algo = mdalgo;
sig->digest_start[0] = dp[0];
sig->digest_start[1] = dp[1];
mpi_release (sig->data[0]);
sig->data[0] = NULL;
mpi_release (sig->data[1]);
sig->data[1] = NULL;
err = hexkeygrip_from_pk (pksk, &hexgrip);
if (!err)
{
char *desc;
gcry_sexp_t s_sigval;
desc = gpg_format_keydesc (ctrl, pksk, FORMAT_KEYDESC_NORMAL, 1);
/* FIXME: Eventually support dual keys. */
err = agent_pksign (NULL/*ctrl*/, cache_nonce, hexgrip, desc,
pksk->keyid, pksk->main_keyid, pksk->pubkey_algo,
dp, gcry_md_get_algo_dlen (mdalgo), mdalgo,
&s_sigval);
xfree (desc);
if (err)
;
else if (pksk->pubkey_algo == GCRY_PK_RSA
|| pksk->pubkey_algo == GCRY_PK_RSA_S)
sig->data[0] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG);
else if (pksk->pubkey_algo == PUBKEY_ALGO_EDDSA
&& openpgp_oid_is_ed25519 (pksk->pkey[0]))
{
err = sexp_extract_param_sos_nlz (s_sigval, "r", &sig->data[0]);
if (!err)
err = sexp_extract_param_sos_nlz (s_sigval, "s", &sig->data[1]);
}
else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
{
err = sexp_extract_param_sos (s_sigval, "r", &sig->data[0]);
if (!err)
err = sexp_extract_param_sos (s_sigval, "s", &sig->data[1]);
}
else
{
sig->data[0] = get_mpi_from_sexp (s_sigval, "r", GCRYMPI_FMT_USG);
sig->data[1] = get_mpi_from_sexp (s_sigval, "s", GCRYMPI_FMT_USG);
}
gcry_sexp_release (s_sigval);
}
xfree (hexgrip);
leave:
if (err)
log_error (_("signing failed: %s\n"), gpg_strerror (err));
else
{
if (opt.verbose)
{
char *ustr = get_user_id_string_native (ctrl, sig->keyid);
log_info (_("%s/%s signature from: \"%s\"\n"),
openpgp_pk_algo_name (pksk->pubkey_algo),
openpgp_md_algo_name (sig->digest_algo),
ustr);
xfree (ustr);
}
}
return err;
}
static int
complete_sig (ctrl_t ctrl,
PKT_signature *sig, PKT_public_key *pksk, gcry_md_hd_t md,
const char *cache_nonce, unsigned int signhints)
{
int rc;
/* if (!(rc = check_secret_key (pksk, 0))) */
rc = do_sign (ctrl, pksk, sig, md, 0, cache_nonce, signhints);
return rc;
}
/* Return true if the key seems to be on a version 1 OpenPGP card.
This works by asking the agent and may fail if the card has not yet
been used with the agent. */
static int
openpgp_card_v1_p (PKT_public_key *pk)
{
gpg_error_t err;
int result;
/* Shortcut if we are not using RSA: The v1 cards only support RSA
thus there is no point in looking any further. */
if (!is_RSA (pk->pubkey_algo))
return 0;
if (!pk->flags.serialno_valid)
{
char *hexgrip;
/* Note: No need to care about dual keys for non-RSA keys. */
err = hexkeygrip_from_pk (pk, &hexgrip);
if (err)
{
log_error ("error computing a keygrip: %s\n", gpg_strerror (err));
return 0; /* Ooops. */
}
xfree (pk->serialno);
agent_get_keyinfo (NULL, hexgrip, &pk->serialno, NULL);
xfree (hexgrip);
pk->flags.serialno_valid = 1;
}
if (!pk->serialno)
result = 0; /* Error from a past agent_get_keyinfo or no card. */
else
{
/* The version number of the card is included in the serialno. */
result = !strncmp (pk->serialno, "D2760001240101", 14);
}
return result;
}
/* Get a matching hash algorithm for DSA and ECDSA. */
static int
match_dsa_hash (unsigned int qbytes)
{
if (qbytes <= 20)
return DIGEST_ALGO_SHA1;
if (qbytes <= 28)
return DIGEST_ALGO_SHA224;
if (qbytes <= 32)
return DIGEST_ALGO_SHA256;
if (qbytes <= 48)
return DIGEST_ALGO_SHA384;
if (qbytes <= 66 ) /* 66 corresponds to 521 (64 to 512) */
return DIGEST_ALGO_SHA512;
return DEFAULT_DIGEST_ALGO;
/* DEFAULT_DIGEST_ALGO will certainly fail, but it's the best wrong
answer we have if a digest larger than 512 bits is requested. */
}
/*
First try --digest-algo. If that isn't set, see if the recipient
has a preferred algorithm (which is also filtered through
--personal-digest-prefs). If we're making a signature without a
particular recipient (i.e. signing, rather than signing+encrypting)
then take the first algorithm in --personal-digest-prefs that is
usable for the pubkey algorithm. If --personal-digest-prefs isn't
set, then take the OpenPGP default (i.e. SHA-1).
Note that EdDSA takes an input of arbitrary length and thus
we don't enforce any particular algorithm like we do for standard
ECDSA. However, we use SHA256 as the default algorithm.
Possible improvement: Use the highest-ranked usable algorithm from
the signing key prefs either before or after using the personal
list?
*/
static int
hash_for (PKT_public_key *pk)
{
if (opt.def_digest_algo)
{
return opt.def_digest_algo;
}
else if (recipient_digest_algo && !is_weak_digest (recipient_digest_algo))
{
return recipient_digest_algo;
}
else if (pk->pubkey_algo == PUBKEY_ALGO_EDDSA)
{
if (opt.personal_digest_prefs)
return opt.personal_digest_prefs[0].value;
else
if (gcry_mpi_get_nbits (pk->pkey[1]) > 256)
return DIGEST_ALGO_SHA512;
else
return DIGEST_ALGO_SHA256;
}
else if (pk->pubkey_algo == PUBKEY_ALGO_DSA
|| pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
unsigned int qbytes = gcry_mpi_get_nbits (pk->pkey[1]);
if (pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
qbytes = ecdsa_qbits_from_Q (qbytes);
qbytes = qbytes/8;
/* It's a DSA key, so find a hash that is the same size as q or
larger. If q is 160, assume it is an old DSA key and use a
160-bit hash unless --enable-dsa2 is set, in which case act
like a new DSA key that just happens to have a 160-bit q
(i.e. allow truncation). If q is not 160, by definition it
must be a new DSA key. We ignore the personal_digest_prefs
- for ECDSA because they should always macth the curve and
+ for ECDSA because they should always match the curve and
truncated hashes are not useful either. Even worse,
smartcards may reject non matching hash lengths for curves
(e.g. using SHA-512 with brainpooolP385r1 on a Yubikey). */
if (pk->pubkey_algo == PUBKEY_ALGO_DSA && opt.personal_digest_prefs)
{
prefitem_t *prefs;
if (qbytes != 20 || opt.flags.dsa2)
{
for (prefs=opt.personal_digest_prefs; prefs->type; prefs++)
if (gcry_md_get_algo_dlen (prefs->value) >= qbytes)
return prefs->value;
}
else
{
for (prefs=opt.personal_digest_prefs; prefs->type; prefs++)
if (gcry_md_get_algo_dlen (prefs->value) == qbytes)
return prefs->value;
}
}
return match_dsa_hash(qbytes);
}
else if (openpgp_card_v1_p (pk))
{
/* The sk lives on a smartcard, and old smartcards only handle
SHA-1 and RIPEMD/160. Newer smartcards (v2.0) don't have
this restriction anymore. Fortunately the serial number
encodes the version of the card and thus we know that this
key is on a v1 card. */
if(opt.personal_digest_prefs)
{
prefitem_t *prefs;
for (prefs=opt.personal_digest_prefs;prefs->type;prefs++)
if (prefs->value==DIGEST_ALGO_SHA1
|| prefs->value==DIGEST_ALGO_RMD160)
return prefs->value;
}
return DIGEST_ALGO_SHA1;
}
else if (opt.personal_digest_prefs)
{
/* It's not DSA, so we can use whatever the first hash algorithm
is in the pref list */
return opt.personal_digest_prefs[0].value;
}
else
return DEFAULT_DIGEST_ALGO;
}
static void
print_status_sig_created (PKT_public_key *pk, PKT_signature *sig, int what)
{
byte array[MAX_FINGERPRINT_LEN];
char buf[100+MAX_FINGERPRINT_LEN*2];
size_t n;
snprintf (buf, sizeof buf - 2*MAX_FINGERPRINT_LEN, "%c %d %d %02x %lu ",
what, sig->pubkey_algo, sig->digest_algo, sig->sig_class,
(ulong)sig->timestamp );
fingerprint_from_pk (pk, array, &n);
bin2hex (array, n, buf + strlen (buf));
write_status_text( STATUS_SIG_CREATED, buf );
}
/*
* Loop over the secret certificates in SK_LIST and build the one pass
* signature packets. OpenPGP says that the data should be bracket by
* the onepass-sig and signature-packet; so we build these onepass
* packet here in reverse order.
*/
static int
write_onepass_sig_packets (SK_LIST sk_list, IOBUF out, int sigclass )
{
int skcount;
SK_LIST sk_rover;
for (skcount=0, sk_rover=sk_list; sk_rover; sk_rover = sk_rover->next)
skcount++;
for (; skcount; skcount--)
{
PKT_public_key *pk;
PKT_onepass_sig *ops;
PACKET pkt;
int i, rc;
for (i=0, sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
if (++i == skcount)
break;
pk = sk_rover->pk;
ops = xmalloc_clear (sizeof *ops);
ops->sig_class = sigclass;
ops->digest_algo = hash_for (pk);
ops->pubkey_algo = pk->pubkey_algo;
keyid_from_pk (pk, ops->keyid);
ops->last = (skcount == 1);
init_packet (&pkt);
pkt.pkttype = PKT_ONEPASS_SIG;
pkt.pkt.onepass_sig = ops;
rc = build_packet (out, &pkt);
free_packet (&pkt, NULL);
if (rc)
{
log_error ("build onepass_sig packet failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
return 0;
}
/*
* Helper to write the plaintext (literal data) packet. At
* R_EXTRAHASH a malloced object with the with the extra data hashed
* into v5 signatures is stored.
*/
static int
write_plaintext_packet (iobuf_t out, iobuf_t inp,
const char *fname, int ptmode,
pt_extra_hash_data_t *r_extrahash)
{
PKT_plaintext *pt = NULL;
u32 filesize;
int rc = 0;
if (!opt.no_literal)
pt = setup_plaintext_name (fname, inp);
/* Try to calculate the length of the data. */
if ( !iobuf_is_pipe_filename (fname) && *fname)
{
uint64_t tmpsize;
tmpsize = iobuf_get_filelength (inp);
if (!tmpsize && opt.verbose)
log_info (_("WARNING: '%s' is an empty file\n"), fname);
/* We can't encode the length of very large files because
* OpenPGP uses only 32 bit for file sizes. So if the size of a
* file is larger than 2^32 minus some bytes for packet headers,
* we switch to partial length encoding. */
if (tmpsize < (IOBUF_FILELENGTH_LIMIT - 65536))
filesize = tmpsize;
else
filesize = 0;
/* Because the text_filter modifies the length of the
* data, it is not possible to know the used length
* without a double read of the file - to avoid that
* we simple use partial length packets. */
if (ptmode == 't' || ptmode == 'u' || ptmode == 'm')
filesize = 0;
}
else
filesize = opt.set_filesize? opt.set_filesize : 0; /* stdin */
if (!opt.no_literal)
{
PACKET pkt;
/* Note that PT has been initialized above in no_literal mode. */
pt->timestamp = make_timestamp ();
pt->mode = ptmode;
pt->len = filesize;
pt->new_ctb = !pt->len;
pt->buf = inp;
init_packet (&pkt);
pkt.pkttype = PKT_PLAINTEXT;
pkt.pkt.plaintext = pt;
/*cfx.datalen = filesize? calc_packet_length( &pkt ) : 0;*/
if ((rc = build_packet (out, &pkt)))
log_error ("build_packet(PLAINTEXT) failed: %s\n",
gpg_strerror (rc) );
*r_extrahash = xtrymalloc (sizeof **r_extrahash + pt->namelen);
if (!*r_extrahash)
rc = gpg_error_from_syserror ();
else
{
(*r_extrahash)->mode = pt->mode;
(*r_extrahash)->timestamp = pt->timestamp;
(*r_extrahash)->namelen = pt->namelen;
/* Note that the last byte of NAME won't be initialized
* because we don't need it. */
memcpy ((*r_extrahash)->name, pt->name, pt->namelen);
}
pt->buf = NULL;
free_packet (&pkt, NULL);
}
else
{
byte copy_buffer[4096];
int bytes_copied;
*r_extrahash = xtrymalloc (sizeof **r_extrahash);
if (!*r_extrahash)
{
rc = gpg_error_from_syserror ();
goto leave;
}
/* FIXME: We need to parse INP to get the to be hashed data from
* it. */
(*r_extrahash)->mode = 0;
(*r_extrahash)->timestamp = 0;
(*r_extrahash)->namelen = 0;
while ((bytes_copied = iobuf_read (inp, copy_buffer, 4096)) != -1)
if ((rc = iobuf_write (out, copy_buffer, bytes_copied)))
{
log_error ("copying input to output failed: %s\n",
gpg_strerror (rc));
break;
}
wipememory (copy_buffer, 4096); /* burn buffer */
}
leave:
return rc;
}
/*
* Write the signatures from the SK_LIST to OUT. HASH must be a
* non-finalized hash which will not be changes here. EXTRAHASH is
* either NULL or the extra data tro be hashed into v5 signatures.
*/
static int
write_signature_packets (ctrl_t ctrl,
SK_LIST sk_list, IOBUF out, gcry_md_hd_t hash,
pt_extra_hash_data_t extrahash,
int sigclass, u32 timestamp, u32 duration,
int status_letter, const char *cache_nonce)
{
SK_LIST sk_rover;
/* Loop over the certificates with secret keys. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
PKT_public_key *pk;
PKT_signature *sig;
gcry_md_hd_t md;
gpg_error_t err;
pk = sk_rover->pk;
/* Build the signature packet. */
sig = xtrycalloc (1, sizeof *sig);
if (!sig)
return gpg_error_from_syserror ();
if (pk->version >= 5)
sig->version = 5; /* Required for v5 keys. */
else
sig->version = 4; /* Required. */
keyid_from_pk (pk, sig->keyid);
sig->digest_algo = hash_for (pk);
sig->pubkey_algo = pk->pubkey_algo;
if (timestamp)
sig->timestamp = timestamp;
else
sig->timestamp = make_timestamp();
if (duration)
sig->expiredate = sig->timestamp + duration;
sig->sig_class = sigclass;
if (gcry_md_copy (&md, hash))
BUG ();
build_sig_subpkt_from_sig (sig, pk, 0);
mk_notation_policy_etc (ctrl, sig, NULL, pk);
if (opt.flags.include_key_block && IS_SIG (sig))
err = mk_sig_subpkt_key_block (ctrl, sig, pk);
else
err = 0;
hash_sigversion_to_magic (md, sig, extrahash);
gcry_md_final (md);
if (!err)
err = do_sign (ctrl, pk, sig, md, hash_for (pk), cache_nonce, 0);
gcry_md_close (md);
if (!err)
{
/* Write the packet. */
PACKET pkt;
init_packet (&pkt);
pkt.pkttype = PKT_SIGNATURE;
pkt.pkt.signature = sig;
err = build_packet (out, &pkt);
if (!err && is_status_enabled())
print_status_sig_created (pk, sig, status_letter);
free_packet (&pkt, NULL);
if (err)
log_error ("build signature packet failed: %s\n",
gpg_strerror (err));
}
else
free_seckey_enc (sig);
if (err)
return err;
}
return 0;
}
/*
* Sign the files whose names are in FILENAME using all secret keys
* which can be taken from LOCUSR, if this is NULL, use the default
* secret key.
* If DETACHED has the value true, make a detached signature.
* If ENCRYPTFLAG is true, use REMUSER (or ask if it is NULL) to encrypt the
* signed data for these users. If ENCRYPTFLAG is 2 symmetric encryption
* is also used.
* If FILENAMES->d is NULL read from stdin and ignore the detached mode.
* If OUTFILE is not NULL; this file is used for output and the function
* does not ask for overwrite permission; output is then always
* uncompressed, non-armored and in binary mode.
*/
int
sign_file (ctrl_t ctrl, strlist_t filenames, int detached, strlist_t locusr,
int encryptflag, strlist_t remusr, const char *outfile )
{
const char *fname;
armor_filter_context_t *afx;
compress_filter_context_t zfx;
gcry_md_hd_t md = NULL;
md_filter_context_t mfx;
md_thd_filter_context_t mfx2 = NULL;
text_filter_context_t tfx;
progress_filter_context_t *pfx;
encrypt_filter_context_t efx;
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
int rc = 0;
PK_LIST pk_list = NULL;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
int multifile = 0;
u32 duration=0;
pt_extra_hash_data_t extrahash = NULL;
pfx = new_progress_context ();
afx = new_armor_context ();
memset (&zfx, 0, sizeof zfx);
memset (&mfx, 0, sizeof mfx);
memset (&efx, 0, sizeof efx);
efx.ctrl = ctrl;
init_packet (&pkt);
if (filenames)
{
fname = filenames->d;
multifile = !!filenames->next;
}
else
fname = NULL;
if (fname && filenames->next && (!detached || encryptflag))
log_bug ("multiple files can only be detached signed");
if (encryptflag == 2
&& (rc = setup_symkey (&efx.symkey_s2k, &efx.symkey_dek)))
goto leave;
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval(1,opt.def_sig_expire);
else
duration = parse_expire_string(opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
* unprotect the secret key. This is now done on demand by the agent. */
if ((rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG )))
goto leave;
if (encryptflag
&& (rc = build_pk_list (ctrl, remusr, &pk_list)))
goto leave;
/* Prepare iobufs. */
if (multifile) /* have list of filenames */
inp = NULL; /* we do it later */
else
{
inp = iobuf_open(fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname? fname: "[stdin]",
strerror (errno));
goto leave;
}
handle_progress (pfx, inp, fname);
}
if (outfile)
{
if (is_secured_filename (outfile))
{
out = NULL;
gpg_err_set_errno (EPERM);
}
else
out = iobuf_create (outfile, 0);
if (!out)
{
rc = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc));
goto leave;
}
else if (opt.verbose)
log_info (_("writing to '%s'\n"), outfile);
}
else if ((rc = open_outfile (GNUPG_INVALID_FD, fname,
opt.armor? 1 : detached? 2 : 0, 0, &out)))
{
goto leave;
}
/* Prepare to calculate the MD over the input. */
if (opt.textmode && !outfile && !multifile)
{
memset (&tfx, 0, sizeof tfx);
iobuf_push_filter (inp, text_filter, &tfx);
}
if (gcry_md_open (&md, 0, 0))
BUG ();
if (DBG_HASHING)
gcry_md_debug (md, "sign");
/* If we're encrypting and signing, it is reasonable to pick the
* hash algorithm to use out of the recipient key prefs. This is
* best effort only, as in a DSA2 and smartcard world there are
* cases where we cannot please everyone with a single hash (DSA2
* wants >160 and smartcards want =160). In the future this could
* be more complex with different hashes for each sk, but the
* current design requires a single hash for all SKs. */
if (pk_list)
{
if (opt.def_digest_algo)
{
if (!opt.expert
&& select_algo_from_prefs (pk_list,PREFTYPE_HASH,
opt.def_digest_algo,
NULL) != opt.def_digest_algo)
{
log_info (_("WARNING: forcing digest algorithm %s (%d)"
" violates recipient preferences\n"),
gcry_md_algo_name (opt.def_digest_algo),
opt.def_digest_algo);
}
}
else
{
int algo;
int conflict = 0;
struct pref_hint hint = { 0 };
hint.digest_length = 0;
/* Of course, if the recipient asks for something
* unreasonable (like the wrong hash for a DSA key) then
* don't do it. Check all sk's - if any are DSA or live
* on a smartcard, then the hash has restrictions and we
* may not be able to give the recipient what they want.
* For DSA, pass a hint for the largest q we have. Note
* that this means that a q>160 key will override a q=160
* key and force the use of truncation for the q=160 key.
* The alternative would be to ignore the recipient prefs
* completely and get a different hash for each DSA key in
* hash_for(). The override behavior here is more or less
* reasonable as it is under the control of the user which
* keys they sign with for a given message and the fact
* that the message with multiple signatures won't be
* usable on an implementation that doesn't understand
* DSA2 anyway. */
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next )
{
if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_DSA
|| sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
int temp_hashlen = gcry_mpi_get_nbits (sk_rover->pk->pkey[1]);
if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
temp_hashlen = ecdsa_qbits_from_Q (temp_hashlen);
if (!temp_hashlen)
conflict = 1; /* Better don't use the prefs. */
temp_hashlen = (temp_hashlen+7)/8;
/* Fixup for that funny nistp521 (yes, 521) were
* we need to use a 512 bit hash algo. */
if (temp_hashlen == 66)
temp_hashlen = 64;
}
else
temp_hashlen = (temp_hashlen+7)/8;
/* Pick a hash that is large enough for our largest
* Q or matches our Q. If there are several of them
* we run into a conflict and don't use the
* preferences. */
if (hint.digest_length < temp_hashlen)
{
if (sk_rover->pk->pubkey_algo == PUBKEY_ALGO_ECDSA)
{
if (hint.exact)
conflict = 1;
hint.exact = 1;
}
hint.digest_length = temp_hashlen;
}
}
}
if (!conflict
&& (algo = select_algo_from_prefs (pk_list, PREFTYPE_HASH,
-1, &hint)) > 0)
{
/* Note that we later check that the algo is not weak. */
recipient_digest_algo = algo;
}
}
}
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (md, hash_for (sk_rover->pk));
if (!multifile)
{
if (encryptflag && (opt.compat_flags & COMPAT_PARALLELIZED))
{
iobuf_push_filter (inp, md_thd_filter, &mfx2);
md_thd_filter_set_md (mfx2, md);
}
else
{
iobuf_push_filter (inp, md_filter, &mfx);
mfx.md = md;
}
}
if (detached && !encryptflag)
afx->what = 2;
if (opt.armor && !outfile)
push_armor_filter (afx, out);
if (encryptflag)
{
efx.pk_list = pk_list;
/* fixme: set efx.cfx.datalen if known */
iobuf_push_filter (out, encrypt_filter, &efx);
}
if (opt.compress_algo && !outfile && !detached)
{
int compr_algo = opt.compress_algo;
if (!opt.explicit_compress_option
&& is_file_compressed (inp))
{
if (opt.verbose)
log_info(_("'%s' already compressed\n"), fname? fname: "[stdin]");
compr_algo = 0;
}
else if (compr_algo==-1)
{
/* If we're not encrypting, then select_algo_from_prefs
* will fail and we'll end up with the default. If we are
* encrypting, select_algo_from_prefs cannot fail since
* there is an assumed preference for uncompressed data.
* Still, if it did fail, we'll also end up with the
* default. */
if ((compr_algo = select_algo_from_prefs (pk_list, PREFTYPE_ZIP,
-1, NULL)) == -1)
{
compr_algo = default_compress_algo();
}
}
else if (!opt.expert && pk_list
&& select_algo_from_prefs (pk_list, PREFTYPE_ZIP,
compr_algo, NULL) != compr_algo)
{
log_info (_("WARNING: forcing compression algorithm %s (%d)"
" violates recipient preferences\n"),
compress_algo_to_string (compr_algo), compr_algo);
}
/* Algo 0 means no compression. */
if (compr_algo)
push_compress_filter (out, &zfx, compr_algo);
}
/* Write the one-pass signature packets if needed */
if (!detached)
{
rc = write_onepass_sig_packets (sk_list, out,
opt.textmode && !outfile ? 0x01:0x00);
if (rc)
goto leave;
}
write_status_begin_signing (md);
/* Setup the inner packet. */
if (detached)
{
size_t iobuf_size = iobuf_set_buffer_size(0) * 1024;
if (multifile)
{
strlist_t sl;
if (opt.verbose)
log_info (_("signing:") );
- /* Must walk reverse trough this list. */
+ /* Must walk reverse through this list. */
for (sl = strlist_last(filenames);
sl;
sl = strlist_prev( filenames, sl))
{
inp = iobuf_open (sl->d);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
sl->d, gpg_strerror (rc));
goto leave;
}
handle_progress (pfx, inp, sl->d);
if (opt.verbose)
log_printf (" '%s'", sl->d );
if (opt.textmode)
{
memset (&tfx, 0, sizeof tfx);
iobuf_push_filter (inp, text_filter, &tfx);
}
if (encryptflag && (opt.compat_flags & COMPAT_PARALLELIZED))
{
iobuf_push_filter (inp, md_thd_filter, &mfx2);
md_thd_filter_set_md (mfx2, md);
}
else
{
iobuf_push_filter (inp, md_filter, &mfx);
mfx.md = md;
}
while (iobuf_read (inp, NULL, iobuf_size) != -1)
;
iobuf_close (inp);
inp = NULL;
}
if (opt.verbose)
log_printf ("\n");
}
else
{
/* Read, so that the filter can calculate the digest. */
while (iobuf_read (inp, NULL, iobuf_size) != -1)
;
}
}
else
{
rc = write_plaintext_packet (out, inp, fname,
(opt.textmode && !outfile) ?
(opt.mimemode? 'm' : 't') : 'b',
&extrahash);
}
/* Catch errors from above. */
if (rc)
goto leave;
/* Write the signatures. */
rc = write_signature_packets (ctrl, sk_list, out, md, extrahash,
opt.textmode && !outfile? 0x01 : 0x00,
0, duration, detached ? 'D':'S', NULL);
if (rc)
goto leave;
leave:
if (rc)
iobuf_cancel (out);
else
{
iobuf_close (out);
if (encryptflag)
write_status (STATUS_END_ENCRYPTION);
}
iobuf_close (inp);
gcry_md_close (md);
release_sk_list (sk_list);
release_pk_list (pk_list);
recipient_digest_algo = 0;
release_progress_context (pfx);
release_armor_context (afx);
xfree (extrahash);
return rc;
}
/*
* Make a clear signature. Note that opt.armor is not needed.
*/
int
clearsign_file (ctrl_t ctrl,
const char *fname, strlist_t locusr, const char *outfile)
{
armor_filter_context_t *afx;
progress_filter_context_t *pfx;
gcry_md_hd_t textmd = NULL;
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
u32 duration = 0;
pt_extra_hash_data_t extrahash = NULL;
pfx = new_progress_context ();
afx = new_armor_context ();
init_packet( &pkt );
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval (1, opt.def_sig_expire);
else
duration = parse_expire_string (opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
* unprotect the secret key. This is now done on demand by the agent. */
if ((rc=build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG)))
goto leave;
/* Prepare iobufs. */
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
fname? fname: "[stdin]", gpg_strerror (rc));
goto leave;
}
handle_progress (pfx, inp, fname);
if (outfile)
{
if (is_secured_filename (outfile))
{
outfile = NULL;
gpg_err_set_errno (EPERM);
}
else
out = iobuf_create (outfile, 0);
if (!out)
{
rc = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), outfile, gpg_strerror (rc));
goto leave;
}
else if (opt.verbose)
log_info (_("writing to '%s'\n"), outfile);
}
else if ((rc = open_outfile (GNUPG_INVALID_FD, fname, 1, 0, &out)))
{
goto leave;
}
iobuf_writestr (out, "-----BEGIN PGP SIGNED MESSAGE-----" LF);
{
const char *s;
int any = 0;
byte hashs_seen[256];
memset (hashs_seen, 0, sizeof hashs_seen);
iobuf_writestr (out, "Hash: " );
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
{
int i = hash_for (sk_rover->pk);
if (!hashs_seen[ i & 0xff ])
{
s = gcry_md_algo_name (i);
if (s)
{
hashs_seen[ i & 0xff ] = 1;
if (any)
iobuf_put (out, ',');
iobuf_writestr (out, s);
any = 1;
}
}
}
log_assert (any);
iobuf_writestr (out, LF);
}
if (opt.not_dash_escaped)
iobuf_writestr (out,
"NotDashEscaped: You need "GPG_NAME
" to verify this message" LF);
iobuf_writestr (out, LF );
if (gcry_md_open (&textmd, 0, 0))
BUG ();
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (textmd, hash_for(sk_rover->pk));
if (DBG_HASHING)
gcry_md_debug (textmd, "clearsign");
copy_clearsig_text (out, inp, textmd, !opt.not_dash_escaped, opt.escape_from);
/* fixme: check for read errors */
/* Now write the armor. */
afx->what = 2;
push_armor_filter (afx, out);
/* Prepare EXTRAHASH, so that it can be used for v5 signature. */
extrahash = xtrymalloc (sizeof *extrahash);
if (!extrahash)
{
rc = gpg_error_from_syserror ();
goto leave;
}
else
{
extrahash->mode = 't';
extrahash->timestamp = 0;
extrahash->namelen = 0;
}
/* Write the signatures. */
rc = write_signature_packets (ctrl, sk_list, out, textmd, extrahash,
0x01, 0, duration, 'C', NULL);
if (rc)
goto leave;
leave:
if (rc)
iobuf_cancel (out);
else
iobuf_close (out);
iobuf_close (inp);
gcry_md_close (textmd);
release_sk_list (sk_list);
release_progress_context (pfx);
release_armor_context (afx);
xfree (extrahash);
return rc;
}
/*
* Sign and conventionally encrypt the given file.
* FIXME: Far too much code is duplicated - revamp the whole file.
*/
int
sign_symencrypt_file (ctrl_t ctrl, const char *fname, strlist_t locusr)
{
armor_filter_context_t *afx;
progress_filter_context_t *pfx;
compress_filter_context_t zfx;
md_filter_context_t mfx;
md_thd_filter_context_t mfx2 = NULL;
gcry_md_hd_t md = NULL;
text_filter_context_t tfx;
cipher_filter_context_t cfx;
iobuf_t inp = NULL;
iobuf_t out = NULL;
PACKET pkt;
STRING2KEY *s2k = NULL;
int rc = 0;
SK_LIST sk_list = NULL;
SK_LIST sk_rover = NULL;
int algo;
u32 duration = 0;
int canceled;
pt_extra_hash_data_t extrahash = NULL;
pfx = new_progress_context ();
afx = new_armor_context ();
memset (&zfx, 0, sizeof zfx);
memset (&mfx, 0, sizeof mfx);
memset (&tfx, 0, sizeof tfx);
memset (&cfx, 0, sizeof cfx);
init_packet (&pkt);
if (opt.ask_sig_expire && !opt.batch)
duration = ask_expire_interval (1, opt.def_sig_expire);
else
duration = parse_expire_string (opt.def_sig_expire);
/* Note: In the old non-agent version the following call used to
* unprotect the secret key. This is now done on demand by the agent. */
rc = build_sk_list (ctrl, locusr, &sk_list, PUBKEY_USAGE_SIG);
if (rc)
goto leave;
/* Prepare iobufs. */
inp = iobuf_open (fname);
if (inp && is_secured_file (iobuf_get_fd (inp)))
{
iobuf_close (inp);
inp = NULL;
gpg_err_set_errno (EPERM);
}
if (!inp)
{
rc = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"),
fname? fname: "[stdin]", gpg_strerror (rc));
goto leave;
}
handle_progress (pfx, inp, fname);
/* Prepare key. */
s2k = xmalloc_clear (sizeof *s2k);
s2k->mode = opt.s2k_mode;
s2k->hash_algo = S2K_DIGEST_ALGO;
algo = default_cipher_algo ();
cfx.dek = passphrase_to_dek (algo, s2k, 1, 1, NULL, 0, &canceled);
if (!cfx.dek || !cfx.dek->keylen)
{
rc = gpg_error (canceled?GPG_ERR_CANCELED:GPG_ERR_BAD_PASSPHRASE);
log_error (_("error creating passphrase: %s\n"), gpg_strerror (rc));
goto leave;
}
cfx.dek->use_aead = use_aead (NULL, cfx.dek->algo);
if (!cfx.dek->use_aead)
cfx.dek->use_mdc = !!use_mdc (NULL, cfx.dek->algo);
if (!opt.quiet || !opt.batch)
log_info (_("%s.%s encryption will be used\n"),
openpgp_cipher_algo_name (algo),
cfx.dek->use_aead? openpgp_aead_algo_name (cfx.dek->use_aead)
/**/ : "CFB");
/* Now create the outfile. */
rc = open_outfile (GNUPG_INVALID_FD, fname, opt.armor? 1:0, 0, &out);
if (rc)
goto leave;
/* Prepare to calculate the MD over the input. */
if (opt.textmode)
iobuf_push_filter (inp, text_filter, &tfx);
if (gcry_md_open (&md, 0, 0))
BUG ();
if (DBG_HASHING)
gcry_md_debug (md, "symc-sign");
for (sk_rover = sk_list; sk_rover; sk_rover = sk_rover->next)
gcry_md_enable (md, hash_for (sk_rover->pk));
if ((opt.compat_flags & COMPAT_PARALLELIZED))
{
iobuf_push_filter (inp, md_thd_filter, &mfx2);
md_thd_filter_set_md (mfx2, md);
}
else
{
iobuf_push_filter (inp, md_filter, &mfx);
mfx.md = md;
}
/* Push armor output filter */
if (opt.armor)
push_armor_filter (afx, out);
/* Write the symmetric key packet */
/* (current filters: armor)*/
{
PKT_symkey_enc *enc = xmalloc_clear( sizeof *enc );
enc->version = cfx.dek->use_aead ? 5 : 4;
enc->cipher_algo = cfx.dek->algo;
enc->aead_algo = cfx.dek->use_aead;
enc->s2k = *s2k;
pkt.pkttype = PKT_SYMKEY_ENC;
pkt.pkt.symkey_enc = enc;
if ((rc = build_packet (out, &pkt)))
log_error ("build symkey packet failed: %s\n", gpg_strerror (rc));
xfree (enc);
}
/* Push the encryption filter */
iobuf_push_filter (out,
cfx.dek->use_aead? cipher_filter_aead
/**/ : cipher_filter_cfb,
&cfx);
/* Push the compress filter */
if (default_compress_algo())
{
if (cfx.dek && (cfx.dek->use_mdc || cfx.dek->use_aead))
zfx.new_ctb = 1;
push_compress_filter (out, &zfx,default_compress_algo() );
}
/* Write the one-pass signature packets */
/* (current filters: zip - encrypt - armor) */
rc = write_onepass_sig_packets (sk_list, out, opt.textmode? 0x01:0x00);
if (rc)
goto leave;
write_status_begin_signing (md);
/* Pipe data through all filters; i.e. write the signed stuff. */
/* (current filters: zip - encrypt - armor) */
rc = write_plaintext_packet (out, inp, fname,
opt.textmode ? (opt.mimemode?'m':'t'):'b',
&extrahash);
if (rc)
goto leave;
/* Write the signatures. */
/* (current filters: zip - encrypt - armor) */
rc = write_signature_packets (ctrl, sk_list, out, md, extrahash,
opt.textmode? 0x01 : 0x00,
0, duration, 'S', NULL);
if (rc)
goto leave;
leave:
if (rc)
iobuf_cancel (out);
else
{
iobuf_close (out);
write_status (STATUS_END_ENCRYPTION);
}
iobuf_close (inp);
release_sk_list (sk_list);
gcry_md_close (md);
xfree (cfx.dek);
xfree (s2k);
release_progress_context (pfx);
release_armor_context (afx);
xfree (extrahash);
return rc;
}
/*
* Create a v4 signature in *RET_SIG.
*
* PK is the primary key to sign (required for all sigs)
* UID is the user id to sign (required for 0x10..0x13, 0x30)
* SUBPK is subkey to sign (required for 0x18, 0x19, 0x28)
*
* PKSK is the signing key
*
* SIGCLASS is the type of signature to create.
*
* TIMESTAMP is the timestamp to use for the signature. 0 means "now"
*
* DURATION is the amount of time (in seconds) until the signature
* expires.
*
* If CACHED_NONCE is not NULL the agent may use it to avoid
* additional pinnetry popups for the same keyblock.
*
* This function creates the following subpackets: issuer, created,
* and expire (if duration is not 0). Additional subpackets can be
* added using MKSUBPKT, which is called after these subpackets are
* added and before the signature is generated. OPAQUE is passed to
* MKSUBPKT.
*/
int
make_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig, PKT_public_key *pk,
PKT_user_id *uid, PKT_public_key *subpk,
PKT_public_key *pksk,
int sigclass,
u32 timestamp, u32 duration,
int (*mksubpkt)(PKT_signature *, void *), void *opaque,
const char *cache_nonce)
{
PKT_signature *sig;
int rc = 0;
int sigversion;
int digest_algo;
gcry_md_hd_t md;
u32 pk_keyid[2], pksk_keyid[2];
unsigned int signhints;
log_assert ((sigclass >= 0x10 && sigclass <= 0x13) || sigclass == 0x1F
|| sigclass == 0x20 || sigclass == 0x18 || sigclass == 0x19
|| sigclass == 0x30 || sigclass == 0x28 );
if (pksk->version >= 5)
sigversion = 5;
else
sigversion = 4;
/* Select the digest algo to use. */
if (opt.cert_digest_algo) /* Forceful override by the user. */
digest_algo = opt.cert_digest_algo;
else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA) /* Meet DSA requirements. */
digest_algo = match_dsa_hash (gcry_mpi_get_nbits (pksk->pkey[1])/8);
else if (pksk->pubkey_algo == PUBKEY_ALGO_ECDSA) /* Meet ECDSA requirements. */
digest_algo = match_dsa_hash
(ecdsa_qbits_from_Q (gcry_mpi_get_nbits (pksk->pkey[1]))/8);
else if (pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
{
if (gcry_mpi_get_nbits (pksk->pkey[1]) > 256)
digest_algo = DIGEST_ALGO_SHA512;
else
digest_algo = DIGEST_ALGO_SHA256;
}
else /* Use the default. */
digest_algo = DEFAULT_DIGEST_ALGO;
signhints = SIGNHINT_KEYSIG;
keyid_from_pk (pk, pk_keyid);
keyid_from_pk (pksk, pksk_keyid);
if (pk_keyid[0] == pksk_keyid[0] && pk_keyid[1] == pksk_keyid[1])
signhints |= SIGNHINT_SELFSIG;
if (gcry_md_open (&md, digest_algo, 0))
BUG ();
/* Hash the public key certificate. */
hash_public_key (md, pk);
if (sigclass == 0x18 || sigclass == 0x19 || sigclass == 0x28)
{
/* Hash the subkey binding/backsig/revocation. */
hash_public_key (md, subpk);
if ((subpk->pubkey_usage & PUBKEY_USAGE_RENC))
signhints |= SIGNHINT_ADSK;
}
else if (sigclass != 0x1F && sigclass != 0x20)
{
/* Hash the user id. */
hash_uid (md, sigversion, uid);
}
/* Make the signature packet. */
sig = xmalloc_clear (sizeof *sig);
sig->version = sigversion;
sig->flags.exportable = 1;
sig->flags.revocable = 1;
keyid_from_pk (pksk, sig->keyid);
sig->pubkey_algo = pksk->pubkey_algo;
sig->digest_algo = digest_algo;
sig->timestamp = timestamp? timestamp : make_timestamp ();
if (duration)
sig->expiredate = sig->timestamp + duration;
sig->sig_class = sigclass;
build_sig_subpkt_from_sig (sig, pksk, signhints);
mk_notation_policy_etc (ctrl, sig, pk, pksk);
/* Crucial that the call to mksubpkt comes LAST before the calls
* to finalize the sig as that makes it possible for the mksubpkt
* function to get a reliable pointer to the subpacket area. */
if (mksubpkt)
rc = (*mksubpkt)(sig, opaque);
if (!rc)
{
hash_sigversion_to_magic (md, sig, NULL);
gcry_md_final (md);
rc = complete_sig (ctrl, sig, pksk, md, cache_nonce, signhints);
}
gcry_md_close (md);
if (rc)
free_seckey_enc (sig);
else
*ret_sig = sig;
return rc;
}
/*
* Create a new signature packet based on an existing one.
* Only user ID signatures are supported for now.
* PK is the public key to work on.
* PKSK is the key used to make the signature.
*
* TODO: Merge this with make_keysig_packet.
*/
gpg_error_t
update_keysig_packet (ctrl_t ctrl,
PKT_signature **ret_sig,
PKT_signature *orig_sig,
PKT_public_key *pk,
PKT_user_id *uid,
PKT_public_key *subpk,
PKT_public_key *pksk,
int (*mksubpkt)(PKT_signature *, void *),
void *opaque)
{
PKT_signature *sig;
gpg_error_t rc = 0;
int digest_algo;
gcry_md_hd_t md;
u32 pk_keyid[2], pksk_keyid[2];
unsigned int signhints = 0;
if ((!orig_sig || !pk || !pksk)
|| (orig_sig->sig_class >= 0x10 && orig_sig->sig_class <= 0x13 && !uid)
|| (orig_sig->sig_class == 0x18 && !subpk))
return GPG_ERR_GENERAL;
/* Either use the override digest algo or in the normal case the
* original digest algorithm. However, iff the original digest
* algorithms is SHA-1 and we are in gnupg or de-vs compliance mode
* we switch to SHA-256 (done by the macro). */
if (opt.cert_digest_algo)
digest_algo = opt.cert_digest_algo;
else if (pksk->pubkey_algo == PUBKEY_ALGO_DSA
|| pksk->pubkey_algo == PUBKEY_ALGO_ECDSA
|| pksk->pubkey_algo == PUBKEY_ALGO_EDDSA)
digest_algo = orig_sig->digest_algo;
else if (orig_sig->digest_algo == DIGEST_ALGO_SHA1
|| orig_sig->digest_algo == DIGEST_ALGO_RMD160)
digest_algo = DEFAULT_DIGEST_ALGO;
else
digest_algo = orig_sig->digest_algo;
signhints = SIGNHINT_KEYSIG;
keyid_from_pk (pk, pk_keyid);
keyid_from_pk (pksk, pksk_keyid);
if (pk_keyid[0] == pksk_keyid[0] && pk_keyid[1] == pksk_keyid[1])
signhints |= SIGNHINT_SELFSIG;
if (gcry_md_open (&md, digest_algo, 0))
BUG ();
/* Hash the public key certificate and the user id. */
hash_public_key (md, pk);
if (orig_sig->sig_class == 0x18)
hash_public_key (md, subpk);
else
hash_uid (md, orig_sig->version, uid);
/* Create a new signature packet. */
sig = copy_signature (NULL, orig_sig);
/* Don't generate version 3 signature, but newer. */
if (sig->version == 3)
{
if (pk->version > 3)
sig->version = pk->version;
else
sig->version = 4;
}
sig->digest_algo = digest_algo;
/* We need to create a new timestamp so that new sig expiration
* calculations are done correctly... */
sig->timestamp = make_timestamp();
/* ... but we won't make a timestamp earlier than the existing
* one. */
{
int tmout = 0;
while (sig->timestamp <= orig_sig->timestamp)
{
if (++tmout > 5 && !opt.ignore_time_conflict)
{
rc = gpg_error (GPG_ERR_TIME_CONFLICT);
goto leave;
}
gnupg_sleep (1);
sig->timestamp = make_timestamp();
}
}
/* Detect an ADSK key binding signature. */
if ((sig->sig_class == 0x18
|| sig->sig_class == 0x19 || sig->sig_class == 0x28)
&& (pk->pubkey_usage & PUBKEY_USAGE_RENC))
signhints |= SIGNHINT_ADSK;
/* Note that already expired sigs will remain expired (with a
* duration of 1) since build-packet.c:build_sig_subpkt_from_sig
* detects this case. */
/* Put the updated timestamp into the sig. Note that this will
* automagically lower any sig expiration dates to correctly
* correspond to the differences in the timestamps (i.e. the
* duration will shrink). */
build_sig_subpkt_from_sig (sig, pksk, signhints);
if (mksubpkt)
rc = (*mksubpkt)(sig, opaque);
if (!rc)
{
hash_sigversion_to_magic (md, sig, NULL);
gcry_md_final (md);
rc = complete_sig (ctrl, sig, pksk, md, NULL, signhints);
}
leave:
gcry_md_close (md);
if (rc)
free_seckey_enc (sig);
else
*ret_sig = sig;
return rc;
}
diff --git a/g10/tofu.c b/g10/tofu.c
index 83786a08d..fff911293 100644
--- a/g10/tofu.c
+++ b/g10/tofu.c
@@ -1,4041 +1,4041 @@
/* tofu.c - TOFU trust model.
* Copyright (C) 2015, 2016 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* TODO:
- Format the fingerprints nicely when printing (similar to gpg
--list-keys)
*/
#include <config.h>
#include <stdio.h>
#include <sys/stat.h>
#include <stdarg.h>
#include <sqlite3.h>
#include <time.h>
#include "gpg.h"
#include "../common/types.h"
#include "../common/logging.h"
#include "../common/stringhelp.h"
#include "options.h"
#include "../common/mbox-util.h"
#include "../common/i18n.h"
#include "../common/ttyio.h"
#include "trustdb.h"
#include "../common/mkdir_p.h"
#include "gpgsql.h"
#include "../common/status.h"
#include "tofu.h"
#define CONTROL_L ('L' - 'A' + 1)
/* Number of days with signed / ecnrypted messages required to
* indicate that enough history is available for basic trust. */
#define BASIC_TRUST_THRESHOLD 4
/* Number of days with signed / encrypted messages required to
* indicate that a lot of history is available. */
#define FULL_TRUST_THRESHOLD 21
/* A struct with data pertaining to the tofu DB. There is one such
struct per session and it is cached in session's ctrl structure.
To initialize this or get the current singleton, call opendbs().
There is no need to explicitly release it; cleanup is done when the
CTRL object is released. */
struct tofu_dbs_s
{
sqlite3 *db;
char *want_lock_file;
time_t want_lock_file_ctime;
struct
{
sqlite3_stmt *savepoint_batch;
sqlite3_stmt *savepoint_batch_commit;
sqlite3_stmt *record_binding_get_old_policy;
sqlite3_stmt *record_binding_update;
sqlite3_stmt *get_policy_select_policy_and_conflict;
sqlite3_stmt *get_trust_bindings_with_this_email;
sqlite3_stmt *get_trust_gather_other_user_ids;
sqlite3_stmt *get_trust_gather_signature_stats;
sqlite3_stmt *get_trust_gather_encryption_stats;
sqlite3_stmt *register_already_seen;
sqlite3_stmt *register_signature;
sqlite3_stmt *register_encryption;
} s;
int in_batch_transaction;
int in_transaction;
time_t batch_update_started;
};
#define STRINGIFY(s) STRINGIFY2(s)
#define STRINGIFY2(s) #s
/* The grouping parameters when collecting signature statistics. */
/* If a message is signed a couple of hours in the future, just assume
some clock skew. */
#define TIME_AGO_FUTURE_IGNORE (2 * 60 * 60)
/* Days. */
#define TIME_AGO_UNIT_SMALL (24 * 60 * 60)
#define TIME_AGO_SMALL_THRESHOLD (7 * TIME_AGO_UNIT_SMALL)
/* Months. */
#define TIME_AGO_UNIT_MEDIUM (30 * 24 * 60 * 60)
#define TIME_AGO_MEDIUM_THRESHOLD (2 * TIME_AGO_UNIT_MEDIUM)
/* Years. */
#define TIME_AGO_UNIT_LARGE (365 * 24 * 60 * 60)
#define TIME_AGO_LARGE_THRESHOLD (2 * TIME_AGO_UNIT_LARGE)
/* Local prototypes. */
static gpg_error_t end_transaction (ctrl_t ctrl, int only_batch);
static char *email_from_user_id (const char *user_id);
static int show_statistics (tofu_dbs_t dbs,
const char *fingerprint, const char *email,
enum tofu_policy policy,
estream_t outfp, int only_status_fd, time_t now);
const char *
tofu_policy_str (enum tofu_policy policy)
{
switch (policy)
{
case TOFU_POLICY_NONE: return "none";
case TOFU_POLICY_AUTO: return "auto";
case TOFU_POLICY_GOOD: return "good";
case TOFU_POLICY_UNKNOWN: return "unknown";
case TOFU_POLICY_BAD: return "bad";
case TOFU_POLICY_ASK: return "ask";
default: return "???";
}
}
/* Convert a binding policy (e.g., TOFU_POLICY_BAD) to a trust level
(e.g., TRUST_BAD) in light of the current configuration. */
int
tofu_policy_to_trust_level (enum tofu_policy policy)
{
if (policy == TOFU_POLICY_AUTO)
/* If POLICY is AUTO, fallback to OPT.TOFU_DEFAULT_POLICY. */
policy = opt.tofu_default_policy;
switch (policy)
{
case TOFU_POLICY_AUTO:
/* If POLICY and OPT.TOFU_DEFAULT_POLICY are both AUTO, default
to marginal trust. */
return TRUST_MARGINAL;
case TOFU_POLICY_GOOD:
return TRUST_FULLY;
case TOFU_POLICY_UNKNOWN:
return TRUST_UNKNOWN;
case TOFU_POLICY_BAD:
return TRUST_NEVER;
case TOFU_POLICY_ASK:
return TRUST_UNKNOWN;
default:
log_bug ("Bad value for trust policy: %d\n",
opt.tofu_default_policy);
return 0;
}
}
/* Start a transaction on DB. If ONLY_BATCH is set, then this will
start a batch transaction if we haven't started a batch transaction
and one has been requested. */
static gpg_error_t
begin_transaction (ctrl_t ctrl, int only_batch)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
log_assert (dbs);
/* If we've been in batch update mode for a while (on average, more
* than 500 ms), to prevent starving other gpg processes, we drop
* and retake the batch lock.
*
* Note: gnupg_get_time has a one second resolution, if we wanted a
* higher resolution, we could use npth_clock_gettime. */
if (/* No real transactions. */
dbs->in_transaction == 0
/* There is an open batch transaction. */
&& dbs->in_batch_transaction
/* And some time has gone by since it was started. */
&& dbs->batch_update_started != gnupg_get_time ())
{
struct stat statbuf;
/* If we are in a batch update, then batch updates better have
been enabled. */
log_assert (ctrl->tofu.batch_updated_wanted);
/* Check if another process wants to run. (We just ignore any
* stat failure. A waiter might have to wait a bit longer, but
* otherwise there should be no impact.) */
if (gnupg_stat (dbs->want_lock_file, &statbuf) == 0
&& statbuf.st_ctime != dbs->want_lock_file_ctime)
{
end_transaction (ctrl, 2);
/* Yield to allow another process a chance to run. Note:
* testing suggests that anything less than a 100ms tends to
* not result in the other process getting the lock. */
gnupg_usleep (100000);
}
else
dbs->batch_update_started = gnupg_get_time ();
}
if (/* We don't have an open batch transaction. */
!dbs->in_batch_transaction
&& (/* Batch mode is enabled or we are starting a new transaction. */
ctrl->tofu.batch_updated_wanted || dbs->in_transaction == 0))
{
struct stat statbuf;
/* We are in batch mode, but we don't have an open batch
* transaction. Since the batch save point must be the outer
* save point, it must be taken before the inner save point. */
log_assert (dbs->in_transaction == 0);
rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch,
NULL, NULL, &err,
"begin immediate transaction;", GPGSQL_ARG_END);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
dbs->in_batch_transaction = 1;
dbs->batch_update_started = gnupg_get_time ();
if (gnupg_stat (dbs->want_lock_file, &statbuf) == 0)
dbs->want_lock_file_ctime = statbuf.st_ctime;
}
if (only_batch)
return 0;
log_assert (dbs->in_transaction >= 0);
dbs->in_transaction ++;
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"savepoint inner%d;",
dbs->in_transaction);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
/* Commit a transaction. If ONLY_BATCH is 1, then this only ends the
* batch transaction if we have left batch mode. If ONLY_BATCH is 2,
* this commits any open batch transaction even if we are still in
* batch mode. */
static gpg_error_t
end_transaction (ctrl_t ctrl, int only_batch)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
if (only_batch || (! only_batch && dbs->in_transaction == 1))
{
if (!dbs)
return 0; /* Shortcut to allow for easier cleanup code. */
/* If we are releasing the batch transaction, then we better not
be in a normal transaction. */
if (only_batch)
log_assert (dbs->in_transaction == 0);
if (/* Batch mode disabled? */
(!ctrl->tofu.batch_updated_wanted || only_batch == 2)
/* But, we still have an open batch transaction? */
&& dbs->in_batch_transaction)
{
/* The batch transaction is still in open, but we've left
* batch mode. */
dbs->in_batch_transaction = 0;
dbs->in_transaction = 0;
rc = gpgsql_stepx (dbs->db, &dbs->s.savepoint_batch_commit,
NULL, NULL, &err,
"commit transaction;", GPGSQL_ARG_END);
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
if (only_batch)
return 0;
}
log_assert (dbs);
log_assert (dbs->in_transaction > 0);
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"release inner%d;", dbs->in_transaction);
dbs->in_transaction --;
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
static gpg_error_t
rollback_transaction (ctrl_t ctrl)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int rc;
char *err = NULL;
log_assert (dbs);
log_assert (dbs->in_transaction > 0);
/* Be careful to not undo any progress made by closed transactions in
batch mode. */
rc = gpgsql_exec_printf (dbs->db, NULL, NULL, &err,
"rollback to inner%d;",
dbs->in_transaction);
dbs->in_transaction --;
if (rc)
{
log_error (_("error rolling back transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return gpg_error (GPG_ERR_GENERAL);
}
return 0;
}
void
tofu_begin_batch_update (ctrl_t ctrl)
{
ctrl->tofu.batch_updated_wanted ++;
}
void
tofu_end_batch_update (ctrl_t ctrl)
{
log_assert (ctrl->tofu.batch_updated_wanted > 0);
ctrl->tofu.batch_updated_wanted --;
end_transaction (ctrl, 1);
}
/* Suspend any extant batch transaction (it is safe to call this even
no batch transaction has been started). Note: you cannot suspend a
batch transaction if you are in a normal transaction. The batch
transaction can be resumed explicitly by calling
tofu_resume_batch_transaction or implicitly by starting a normal
transaction. */
static void
tofu_suspend_batch_transaction (ctrl_t ctrl)
{
end_transaction (ctrl, 2);
}
/* Resume a batch transaction if there is no extant batch transaction
and one has been requested using tofu_begin_batch_transaction. */
static void
tofu_resume_batch_transaction (ctrl_t ctrl)
{
begin_transaction (ctrl, 1);
}
/* Wrapper around strtol which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static gpg_error_t
string_to_long (long *r_value, const char *string, long fallback, int line)
{
gpg_error_t err;
char *tail = NULL;
gpg_err_set_errno (0);
*r_value = strtol (string, &tail, 0);
if (errno || !(!strcmp (tail, ".0") || !*tail))
{
err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
log_debug ("%s:%d: strtol failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s\n",
__FILE__, line,
string, string && strlen(string) > 10 ? "..." : "",
tail, tail && strlen(tail) > 10 ? "..." : "",
gpg_strerror (err));
*r_value = fallback;
}
else
err = 0;
return err;
}
/* Wrapper around strtoul which prints a warning in case of a
* conversion error. On success the converted value is stored at
* R_VALUE and 0 is returned; on error FALLBACK is stored at R_VALUE
* and an error code is returned. */
static gpg_error_t
string_to_ulong (unsigned long *r_value, const char *string,
unsigned long fallback, int line)
{
gpg_error_t err;
char *tail = NULL;
gpg_err_set_errno (0);
*r_value = strtoul (string, &tail, 0);
if (errno || !(!strcmp (tail, ".0") || !*tail))
{
err = errno? gpg_error_from_errno (errno) : gpg_error (GPG_ERR_BAD_DATA);
log_debug ("%s:%d: strtoul failed for TOFU DB data; returned string"
" (string='%.10s%s'; tail='%.10s%s'): %s\n",
__FILE__, line,
string, string && strlen(string) > 10 ? "..." : "",
tail, tail && strlen(tail) > 10 ? "..." : "",
gpg_strerror (err));
*r_value = fallback;
}
else
err = 0;
return err;
}
/* Collect results of a select count (*) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static int
get_single_unsigned_long_cb (void *cookie, int argc, char **argv,
char **azColName)
{
unsigned long int *count = cookie;
(void) azColName;
log_assert (argc == 1);
if (string_to_ulong (count, argv[0], 0, __LINE__))
return 1; /* Abort. */
return 0;
}
static int
get_single_unsigned_long_cb2 (void *cookie, int argc, char **argv,
char **azColName, sqlite3_stmt *stmt)
{
(void) stmt;
return get_single_unsigned_long_cb (cookie, argc, argv, azColName);
}
/* We expect a single integer column whose name is "version". COOKIE
must point to an int. This function always aborts. On error or a
if the version is bad, sets *VERSION to -1. */
static int
version_check_cb (void *cookie, int argc, char **argv, char **azColName)
{
int *version = cookie;
if (argc != 1 || strcmp (azColName[0], "version") != 0)
{
*version = -1;
return 1;
}
if (strcmp (argv[0], "1") == 0)
*version = 1;
else
{
log_error (_("unsupported TOFU database version: %s\n"), argv[0]);
*version = -1;
}
/* Don't run again. */
return 1;
}
static int
check_utks (sqlite3 *db)
{
int rc;
char *err = NULL;
struct key_item *utks;
struct key_item *ki;
int utk_count;
char *utks_string = NULL;
char keyid_str[16+1];
long utks_unchanged = 0;
/* An early version of the v1 format did not include the list of
* known ultimately trusted keys.
*
* This list is used to detect when the set of ultimately trusted
* keys changes. We need to detect this to invalidate the effective
* policy, which can change if an ultimately trusted key is added or
* removed. */
rc = sqlite3_exec (db,
"create table if not exists ultimately_trusted_keys"
" (keyid);\n",
NULL, NULL, &err);
if (rc)
{
log_error ("error creating 'ultimately_trusted_keys' TOFU table: %s\n",
err);
sqlite3_free (err);
goto out;
}
utks = tdb_utks ();
for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
;
if (utk_count)
{
/* Build a list of keyids of the form "XXX","YYY","ZZZ". */
int len = (1 + 16 + 1 + 1) * utk_count;
int o = 0;
utks_string = xmalloc (len);
*utks_string = 0;
for (ki = utks, utk_count = 0; ki; ki = ki->next, utk_count ++)
{
utks_string[o ++] = '\'';
format_keyid (ki->kid, KF_LONG,
keyid_str, sizeof (keyid_str));
memcpy (&utks_string[o], keyid_str, 16);
o += 16;
utks_string[o ++] = '\'';
utks_string[o ++] = ',';
}
utks_string[o - 1] = 0;
log_assert (o == len);
}
rc = gpgsql_exec_printf
(db, get_single_unsigned_long_cb, &utks_unchanged, &err,
"select"
/* Removed UTKs? (Known UTKs in current UTKs.) */
" ((select count(*) from ultimately_trusted_keys"
" where (keyid in (%s))) == %d)"
" and"
/* New UTKs? */
" ((select count(*) from ultimately_trusted_keys"
" where keyid not in (%s)) == 0);",
utks_string ? utks_string : "",
utk_count,
utks_string ? utks_string : "");
xfree (utks_string);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("checking if ultimately trusted keys changed: %s",
err);
sqlite3_free (err);
goto out;
}
if (utks_unchanged)
goto out;
if (DBG_TRUST)
log_debug ("TOFU: ultimately trusted keys changed.\n");
/* Given that the set of ultimately trusted keys
* changed, clear any cached policies. */
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"update bindings set effective_policy = %d;",
TOFU_POLICY_NONE);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("clearing cached policies: %s", err);
sqlite3_free (err);
goto out;
}
/* Now, update the UTK table. */
rc = sqlite3_exec (db,
"drop table ultimately_trusted_keys;",
NULL, NULL, &err);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("dropping ultimately_trusted_keys: %s", err);
sqlite3_free (err);
goto out;
}
rc = sqlite3_exec (db,
"create table if not exists"
" ultimately_trusted_keys (keyid);\n",
NULL, NULL, &err);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("creating ultimately_trusted_keys: %s", err);
sqlite3_free (err);
goto out;
}
for (ki = utks; ki; ki = ki->next)
{
format_keyid (ki->kid, KF_LONG,
keyid_str, sizeof (keyid_str));
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"insert into ultimately_trusted_keys values ('%s');",
keyid_str);
if (rc)
{
log_error (_("TOFU DB error"));
print_further_info ("updating ultimately_trusted_keys: %s",
err);
sqlite3_free (err);
goto out;
}
}
out:
return rc;
}
/* If the DB is new, initialize it. Otherwise, check the DB's
version.
Return 0 if the database is okay and 1 otherwise. */
static int
initdb (sqlite3 *db)
{
char *err = NULL;
int rc;
unsigned long int count;
int version = -1;
rc = sqlite3_exec (db, "begin transaction;", NULL, NULL, &err);
if (rc)
{
log_error (_("error beginning transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return 1;
}
/* If the DB has no tables, then assume this is a new DB that needs
to be initialized. */
rc = sqlite3_exec (db,
"select count(*) from sqlite_master where type='table';",
get_single_unsigned_long_cb, &count, &err);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("query available tables");
sqlite3_free (err);
goto out;
}
else if (count != 0)
/* Assume that the DB is already initialized. Make sure the
version is okay. */
{
rc = sqlite3_exec (db, "select version from version;", version_check_cb,
&version, &err);
if (rc == SQLITE_ABORT && version == 1)
/* Happy, happy, joy, joy. */
{
sqlite3_free (err);
rc = 0;
goto out;
}
else if (rc == SQLITE_ABORT && version == -1)
/* Unsupported version. */
{
/* An error message was already displayed. */
sqlite3_free (err);
goto out;
}
else if (rc)
/* Some error. */
{
log_error (_("error determining TOFU database's version: %s\n"), err);
sqlite3_free (err);
goto out;
}
else
{
/* Unexpected success. This can only happen if there are no
rows. (select returned 0, but expected ABORT.) */
log_error (_("error determining TOFU database's version: %s\n"),
gpg_strerror (GPG_ERR_NO_DATA));
rc = 1;
goto out;
}
}
/* Create the version table. */
rc = sqlite3_exec (db,
"create table version (version INTEGER);",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create version");
sqlite3_free (err);
goto out;
}
/* Initialize the version table, which contains a single integer
value. */
rc = sqlite3_exec (db,
"insert into version values (1);",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("insert version");
sqlite3_free (err);
goto out;
}
/* The list of <fingerprint, email> bindings and auxiliary data.
*
* OID is a unique ID identifying this binding (and used by the
* signatures table, see below). Note: OIDs will never be
* reused.
*
* FINGERPRINT: The key's fingerprint.
*
* EMAIL: The normalized email address.
*
* USER_ID: The unmodified user id from which EMAIL was extracted.
*
* TIME: The time this binding was first observed.
*
* POLICY: The trust policy (TOFU_POLICY_BAD, etc. as an integer).
*
* CONFLICT is either NULL or a fingerprint. Assume that we have
* a binding <0xdeadbeef, foo@example.com> and then we observe
* <0xbaddecaf, foo@example.com>. There two bindings conflict
* (they have the same email address). When we observe the
* latter binding, we warn the user about the conflict and ask
* for a policy decision about the new binding. We also change
* the old binding's policy to ask if it was auto. So that we
* know why this occurred, we also set conflict to 0xbaddecaf.
*/
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"create table bindings\n"
" (oid INTEGER PRIMARY KEY AUTOINCREMENT,\n"
" fingerprint TEXT, email TEXT, user_id TEXT, time INTEGER,\n"
" policy INTEGER CHECK (policy in (%d, %d, %d, %d, %d)),\n"
" conflict STRING,\n"
" unique (fingerprint, email));\n"
"create index bindings_fingerprint_email\n"
" on bindings (fingerprint, email);\n"
"create index bindings_email on bindings (email);\n",
TOFU_POLICY_AUTO, TOFU_POLICY_GOOD, TOFU_POLICY_UNKNOWN,
TOFU_POLICY_BAD, TOFU_POLICY_ASK);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create bindings");
sqlite3_free (err);
goto out;
}
/* The signatures that we have observed.
*
* BINDING refers to a record in the bindings table, which
* describes the binding (i.e., this is a foreign key that
* references bindings.oid).
*
* SIG_DIGEST is the digest stored in the signature.
*
* SIG_TIME is the timestamp stored in the signature.
*
* ORIGIN is a free-form string that describes who fed this
* signature to GnuPG (e.g., email:claws).
*
* TIME is the time this signature was registered. */
rc = sqlite3_exec (db,
"create table signatures "
" (binding INTEGER NOT NULL, sig_digest TEXT,"
" origin TEXT, sig_time INTEGER, time INTEGER,"
" primary key (binding, sig_digest, origin));",
NULL, NULL, &err);
if (rc)
{
log_error (_("error initializing TOFU database: %s\n"), err);
print_further_info ("create signatures");
sqlite3_free (err);
goto out;
}
out:
if (! rc)
{
/* Early version of the v1 format did not include the encryption
table. Add it. */
rc = sqlite3_exec (db,
"create table if not exists encryptions"
" (binding INTEGER NOT NULL,"
" time INTEGER);"
"create index if not exists encryptions_binding"
" on encryptions (binding);\n",
NULL, NULL, &err);
if (rc)
{
log_error ("error creating 'encryptions' TOFU table: %s\n",
err);
sqlite3_free (err);
}
}
if (! rc)
{
/* The effective policy for a binding. If a key is ultimately
* trusted, then the effective policy of all of its bindings is
* good. Likewise if a key is signed by an ultimately trusted
* key, etc. If the effective policy is NONE, then we need to
* recompute the effective policy. Otherwise, the effective
* policy is considered to be up to date, i.e., effective_policy
* is a cache of the computed policy. */
rc = gpgsql_exec_printf
(db, NULL, NULL, &err,
"alter table bindings"
" add column effective_policy INTEGER"
" DEFAULT %d"
" CHECK (effective_policy in (%d, %d, %d, %d, %d, %d));",
TOFU_POLICY_NONE,
TOFU_POLICY_NONE, TOFU_POLICY_AUTO, TOFU_POLICY_GOOD,
TOFU_POLICY_UNKNOWN, TOFU_POLICY_BAD, TOFU_POLICY_ASK);
if (rc)
{
if (rc == SQLITE_ERROR)
/* Almost certainly "duplicate column name", which we can
* safely ignore. */
rc = 0;
else
log_error ("adding column effective_policy to bindings DB: %s\n",
err);
sqlite3_free (err);
}
}
if (! rc)
rc = check_utks (db);
if (rc)
{
rc = sqlite3_exec (db, "rollback;", NULL, NULL, &err);
if (rc)
{
log_error (_("error rolling back transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
}
return 1;
}
else
{
rc = sqlite3_exec (db, "end transaction;", NULL, NULL, &err);
if (rc)
{
log_error (_("error committing transaction on TOFU database: %s\n"),
err);
sqlite3_free (err);
return 1;
}
return 0;
}
}
static int
busy_handler (void *cookie, int call_count)
{
ctrl_t ctrl = cookie;
tofu_dbs_t dbs = ctrl->tofu.dbs;
(void) call_count;
/* Update the want-lock-file time stamp (specifically, the ctime) so
* that the current owner knows that we (well, someone) want the
* lock. */
if (dbs)
{
/* Note: we don't fail if we can't create the lock file: this
* process will have to wait a bit longer, but otherwise nothing
* horrible should happen. */
estream_t fp;
fp = es_fopen (dbs->want_lock_file, "w");
if (! fp)
log_debug ("TOFU: Error opening '%s': %s\n",
dbs->want_lock_file, strerror (errno));
else
es_fclose (fp);
}
/* Call again. */
return 1;
}
/* Create a new DB handle. Returns NULL on error. */
/* FIXME: Change to return an error code for better reporting by the
caller. */
static tofu_dbs_t
opendbs (ctrl_t ctrl)
{
char *filename;
sqlite3 *db;
int rc;
if (!ctrl->tofu.dbs)
{
filename = make_filename (gnupg_homedir (), "tofu.db", NULL);
rc = sqlite3_open (filename, &db);
if (rc)
{
log_error (_("error opening TOFU database '%s': %s\n"),
filename, sqlite3_errmsg (db));
/* Even if an error occurs, DB is guaranteed to be valid. */
sqlite3_close (db);
db = NULL;
}
/* If a DB is locked wait up to 5 seconds for the lock to be cleared
before failing. */
if (db)
{
sqlite3_busy_timeout (db, 5 * 1000);
sqlite3_busy_handler (db, busy_handler, ctrl);
}
if (db && initdb (db))
{
sqlite3_close (db);
db = NULL;
}
if (db)
{
ctrl->tofu.dbs = xmalloc_clear (sizeof *ctrl->tofu.dbs);
ctrl->tofu.dbs->db = db;
ctrl->tofu.dbs->want_lock_file = xasprintf ("%s-want-lock", filename);
}
xfree (filename);
}
else
log_assert (ctrl->tofu.dbs->db);
return ctrl->tofu.dbs;
}
/* Release all of the resources associated with the DB handle. */
void
tofu_closedbs (ctrl_t ctrl)
{
tofu_dbs_t dbs;
sqlite3_stmt **statements;
dbs = ctrl->tofu.dbs;
if (!dbs)
return; /* Not initialized. */
log_assert (dbs->in_transaction == 0);
end_transaction (ctrl, 2);
/* Arghh, that is a surprising use of the struct. */
for (statements = (void *) &dbs->s;
(void *) statements < (void *) &(&dbs->s)[1];
statements ++)
sqlite3_finalize (*statements);
sqlite3_close (dbs->db);
xfree (dbs->want_lock_file);
xfree (dbs);
ctrl->tofu.dbs = NULL;
}
/* Collect results of a select min (foo) ...; style query. Aborts if
the argument is not a valid integer (or real of the form X.0). */
static int
get_single_long_cb (void *cookie, int argc, char **argv, char **azColName)
{
long *count = cookie;
(void) azColName;
log_assert (argc == 1);
if (string_to_long (count, argv[0], 0, __LINE__))
return 1; /* Abort. */
return 0;
}
static int
get_single_long_cb2 (void *cookie, int argc, char **argv, char **azColName,
sqlite3_stmt *stmt)
{
(void) stmt;
return get_single_long_cb (cookie, argc, argv, azColName);
}
/* Record (or update) a trust policy about a (possibly new)
binding.
If SHOW_OLD is set, the binding's old policy is displayed. */
static gpg_error_t
record_binding (tofu_dbs_t dbs, const char *fingerprint, const char *email,
const char *user_id,
enum tofu_policy policy, enum tofu_policy effective_policy,
const char *conflict, int set_conflict,
int show_old, time_t now)
{
char *fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
gpg_error_t rc;
char *err = NULL;
if (! (policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK))
log_bug ("%s: Bad value for policy (%d)!\n", __func__, policy);
if (DBG_TRUST || show_old)
{
/* Get the old policy. Since this is just for informational
* purposes, there is no need to start a transaction or to die
* if there is a failure. */
/* policy_old needs to be a long and not an enum tofu_policy,
because we pass it by reference to get_single_long_cb2, which
expects a long. */
long policy_old = TOFU_POLICY_NONE;
rc = gpgsql_stepx
(dbs->db, &dbs->s.record_binding_get_old_policy,
get_single_long_cb2, &policy_old, &err,
"select policy from bindings where fingerprint = ? and email = ?",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_END);
if (rc)
{
log_debug ("TOFU: Error reading from binding database"
" (reading policy for <key: %s, user id: %s>): %s\n",
fingerprint, email, err);
sqlite3_free (err);
}
if (policy_old != TOFU_POLICY_NONE)
(show_old ? log_info : log_debug)
("Changing TOFU trust policy for binding"
" <key: %s, user id: %s> from %s to %s.\n",
fingerprint, show_old ? user_id : email,
tofu_policy_str (policy_old),
tofu_policy_str (policy));
else
(show_old ? log_info : log_debug)
("Setting TOFU trust policy for new binding"
" <key: %s, user id: %s> to %s.\n",
fingerprint, show_old ? user_id : email,
tofu_policy_str (policy));
}
if (opt.dry_run)
{
log_info ("TOFU database update skipped due to --dry-run\n");
rc = 0;
goto leave;
}
rc = gpgsql_stepx
(dbs->db, &dbs->s.record_binding_update, NULL, NULL, &err,
"insert or replace into bindings\n"
" (oid, fingerprint, email, user_id, time,"
" policy, conflict, effective_policy)\n"
" values (\n"
/* If we don't explicitly reuse the OID, then SQLite will
* reallocate a new one. We just need to search for the OID
* based on the fingerprint and email since they are unique. */
" (select oid from bindings where fingerprint = ? and email = ?),\n"
" ?, ?, ?, ?, ?,"
/* If SET_CONFLICT is 0, then preserve conflict's current value. */
" case ?"
" when 0 then"
" (select conflict from bindings where fingerprint = ? and email = ?)"
" else ?"
" end,"
" ?);",
/* oid subquery. */
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
/* values 2 through 6. */
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, user_id,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_INT, (int) policy,
/* conflict subquery. */
GPGSQL_ARG_INT, set_conflict ? 1 : 0,
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, conflict ? conflict : "",
GPGSQL_ARG_INT, (int) effective_policy,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), err);
print_further_info (" insert bindings <key: %s, user id: %s> = %s",
fingerprint, email, tofu_policy_str (policy));
sqlite3_free (err);
goto leave;
}
leave:
xfree (fingerprint_pp);
return rc;
}
/* Collect the strings returned by a query in a simple string list.
Any NULL values are converted to the empty string.
If a result has 3 rows and each row contains two columns, then the
results are added to the list as follows (the value is parentheses
is the 1-based index in the final list):
row 1, col 2 (6)
row 1, col 1 (5)
row 2, col 2 (4)
row 2, col 1 (3)
row 3, col 2 (2)
row 3, col 1 (1)
This is because add_to_strlist pushes the results onto the front of
the list. The end result is that the rows are backwards, but the
columns are in the expected order. */
static int
strings_collect_cb (void *cookie, int argc, char **argv, char **azColName)
{
int i;
strlist_t *strlist = cookie;
(void) azColName;
for (i = argc - 1; i >= 0; i --)
add_to_strlist (strlist, argv[i] ? argv[i] : "");
return 0;
}
static int
strings_collect_cb2 (void *cookie, int argc, char **argv, char **azColName,
sqlite3_stmt *stmt)
{
(void) stmt;
return strings_collect_cb (cookie, argc, argv, azColName);
}
/* Auxiliary data structure to collect statistics about
signatures. */
struct signature_stats
{
struct signature_stats *next;
/* The user-assigned policy for this binding. */
enum tofu_policy policy;
/* How long ago the signature was created (rounded to a multiple of
TIME_AGO_UNIT_SMALL, etc.). */
long time_ago;
/* Number of signatures during this time. */
unsigned long count;
/* If the corresponding key/user id has been expired / revoked. */
int is_expired;
int is_revoked;
/* The key that generated this signature. */
char fingerprint[1];
};
static void
signature_stats_free (struct signature_stats *stats)
{
while (stats)
{
struct signature_stats *next = stats->next;
xfree (stats);
stats = next;
}
}
static void
signature_stats_prepend (struct signature_stats **statsp,
const char *fingerprint,
enum tofu_policy policy,
long time_ago,
unsigned long count)
{
struct signature_stats *stats =
xmalloc_clear (sizeof (*stats) + strlen (fingerprint));
stats->next = *statsp;
*statsp = stats;
strcpy (stats->fingerprint, fingerprint);
stats->policy = policy;
stats->time_ago = time_ago;
stats->count = count;
}
/* Process rows that contain the four columns:
<fingerprint, policy, time ago, count>. */
static int
signature_stats_collect_cb (void *cookie, int argc, char **argv,
char **azColName, sqlite3_stmt *stmt)
{
struct signature_stats **statsp = cookie;
int i = 0;
enum tofu_policy policy;
long time_ago;
unsigned long count;
long along;
(void) azColName;
(void) stmt;
i ++;
if (string_to_long (&along, argv[i], 0, __LINE__))
return 1; /* Abort */
policy = along;
i ++;
if (! argv[i])
time_ago = 0;
else
{
if (string_to_long (&time_ago, argv[i], 0, __LINE__))
return 1; /* Abort. */
}
i ++;
/* If time_ago is NULL, then we had no messages, but we still have a
single row, which count(*) turns into 1. */
if (! argv[i - 1])
count = 0;
else
{
if (string_to_ulong (&count, argv[i], 0, __LINE__))
return 1; /* Abort */
}
i ++;
log_assert (argc == i);
signature_stats_prepend (statsp, argv[0], policy, time_ago, count);
return 0;
}
/* Format the first part of a conflict message and return that as a
* malloced string. Returns NULL on error. */
static char *
format_conflict_msg_part1 (int policy, strlist_t conflict_set,
const char *email)
{
estream_t fp;
char *fingerprint;
char *tmpstr, *text;
log_assert (conflict_set);
fingerprint = conflict_set->d;
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (policy == TOFU_POLICY_NONE)
{
es_fprintf (fp,
_("This is the first time the email address \"%s\" is "
"being used with key %s."),
email, fingerprint);
es_fputs (" ", fp);
}
else if (policy == TOFU_POLICY_ASK && conflict_set->next)
{
int conflicts = strlist_length (conflict_set);
es_fprintf
(fp, ngettext("The email address \"%s\" is associated with %d key!",
"The email address \"%s\" is associated with %d keys!",
conflicts),
email, conflicts);
if (opt.verbose)
es_fprintf (fp,
_(" Since this binding's policy was 'auto', it has been "
"changed to 'ask'."));
es_fputs (" ", fp);
}
es_fprintf (fp,
_("Please indicate whether this email address should"
" be associated with key %s or whether you think someone"
" is impersonating \"%s\"."),
fingerprint, email);
es_fputc ('\n', fp);
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **)&tmpstr, NULL))
log_fatal ("error snatching memory stream\n");
text = format_text (tmpstr, 72, 80);
es_free (tmpstr);
return text;
}
/* Return 1 if A signed B and B signed A. */
static int
cross_sigs (const char *email, kbnode_t a, kbnode_t b)
{
int i;
PKT_public_key *a_pk = a->pkt->pkt.public_key;
PKT_public_key *b_pk = b->pkt->pkt.public_key;
char a_keyid[33];
char b_keyid[33];
if (DBG_TRUST)
{
format_keyid (pk_main_keyid (a_pk),
KF_LONG, a_keyid, sizeof (a_keyid));
format_keyid (pk_main_keyid (b_pk),
KF_LONG, b_keyid, sizeof (b_keyid));
}
for (i = 0; i < 2; i ++)
{
/* See if SIGNER signed SIGNEE. */
kbnode_t signer = i == 0 ? a : b;
kbnode_t signee = i == 0 ? b : a;
PKT_public_key *signer_pk = signer->pkt->pkt.public_key;
u32 *signer_kid = pk_main_keyid (signer_pk);
kbnode_t n;
int saw_email = 0;
/* Iterate over SIGNEE's keyblock and see if there is a valid
signature from SIGNER. */
for (n = signee; n; n = n->next)
{
PKT_signature *sig;
if (n->pkt->pkttype == PKT_USER_ID)
{
if (saw_email)
/* We're done: we've processed all signatures on the
user id. */
break;
else
{
/* See if this is the matching user id. */
PKT_user_id *user_id = n->pkt->pkt.user_id;
char *email2 = email_from_user_id (user_id->name);
if (strcmp (email, email2) == 0)
saw_email = 1;
xfree (email2);
}
}
if (! saw_email)
continue;
if (n->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = n->pkt->pkt.signature;
if (! (sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13))
/* Not a signature over a user id. */
continue;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if (keyid_cmp (sig->keyid, signer_kid) == 0)
/* Match! */
break;
}
if (! n)
/* We didn't find a signature from signer over signee. */
{
if (DBG_TRUST)
log_debug ("No cross sig between %s and %s\n",
a_keyid, b_keyid);
return 0;
}
}
/* A signed B and B signed A. */
if (DBG_TRUST)
log_debug ("Cross sig between %s and %s\n",
a_keyid, b_keyid);
return 1;
}
/* Return whether the key was signed by an ultimately trusted key. */
static int
signed_by_utk (const char *email, kbnode_t a)
{
kbnode_t n;
int saw_email = 0;
for (n = a; n; n = n->next)
{
PKT_signature *sig;
if (n->pkt->pkttype == PKT_USER_ID)
{
if (saw_email)
/* We're done: we've processed all signatures on the
user id. */
break;
else
{
/* See if this is the matching user id. */
PKT_user_id *user_id = n->pkt->pkt.user_id;
char *email2 = email_from_user_id (user_id->name);
if (strcmp (email, email2) == 0)
saw_email = 1;
xfree (email2);
}
}
if (! saw_email)
continue;
if (n->pkt->pkttype != PKT_SIGNATURE)
continue;
sig = n->pkt->pkt.signature;
if (! (sig->sig_class == 0x10
|| sig->sig_class == 0x11
|| sig->sig_class == 0x12
|| sig->sig_class == 0x13))
/* Not a signature over a user id. */
continue;
/* SIG is on SIGNEE's keyblock. If SIG was generated by the
signer, then it's a match. */
if (tdb_keyid_is_utk (sig->keyid))
{
/* Match! */
if (DBG_TRUST)
log_debug ("TOFU: %s is signed by an ultimately trusted key.\n",
pk_keyid_str (a->pkt->pkt.public_key));
return 1;
}
}
if (DBG_TRUST)
log_debug ("TOFU: %s is NOT signed by an ultimately trusted key.\n",
pk_keyid_str (a->pkt->pkt.public_key));
return 0;
}
enum
{
BINDING_NEW = 1 << 0,
BINDING_CONFLICT = 1 << 1,
BINDING_EXPIRED = 1 << 2,
BINDING_REVOKED = 1 << 3
};
/* Ask the user about the binding. There are three ways we could end
* up here:
*
* - This is a new binding and there is a conflict
* (policy == TOFU_POLICY_NONE && conflict_set_count > 1),
*
* - This is a new binding and opt.tofu_default_policy is set to
* ask. (policy == TOFU_POLICY_NONE && opt.tofu_default_policy ==
* TOFU_POLICY_ASK), or,
*
* - The policy is ask (the user deferred last time) (policy ==
* TOFU_POLICY_ASK).
*
* Note: this function must not be called while in a transaction!
*
* CONFLICT_SET includes all of the conflicting bindings
* with FINGERPRINT first. FLAGS is a bit-wise or of
* BINDING_NEW, etc.
*/
static void
ask_about_binding (ctrl_t ctrl,
enum tofu_policy *policy,
int *trust_level,
strlist_t conflict_set,
const char *fingerprint,
const char *email,
const char *user_id,
time_t now)
{
tofu_dbs_t dbs;
strlist_t iter;
int conflict_set_count = strlist_length (conflict_set);
char *sqerr = NULL;
int rc;
estream_t fp;
strlist_t other_user_ids = NULL;
struct signature_stats *stats = NULL;
struct signature_stats *stats_iter = NULL;
char *prompt = NULL;
const char *choices;
dbs = ctrl->tofu.dbs;
log_assert (dbs);
log_assert (dbs->in_transaction == 0);
fp = es_fopenmem (0, "rw,samethread");
if (!fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
{
char *text = format_conflict_msg_part1 (*policy, conflict_set, email);
if (!text) /* FIXME: Return the error all the way up. */
log_fatal ("format failed: %s\n",
gpg_strerror (gpg_error_from_syserror()));
es_fputs (text, fp);
es_fputc ('\n', fp);
xfree (text);
}
begin_transaction (ctrl, 0);
/* Find other user ids associated with this key and whether the
* bindings are marked as good or bad. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_other_user_ids,
strings_collect_cb2, &other_user_ids, &sqerr,
"select user_id, policy from bindings where fingerprint = ?;",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_END);
if (rc)
{
log_error (_("error gathering other user IDs: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = NULL;
rc = gpg_error (GPG_ERR_GENERAL);
}
if (other_user_ids)
{
strlist_t strlist_iter;
es_fprintf (fp, _("This key's user IDs:\n"));
for (strlist_iter = other_user_ids;
strlist_iter;
strlist_iter = strlist_iter->next)
{
char *other_user_id = strlist_iter->d;
char *other_thing;
enum tofu_policy other_policy;
log_assert (strlist_iter->next);
strlist_iter = strlist_iter->next;
other_thing = strlist_iter->d;
other_policy = atoi (other_thing);
es_fprintf (fp, " %s (", other_user_id);
es_fprintf (fp, _("policy: %s"), tofu_policy_str (other_policy));
es_fprintf (fp, ")\n");
}
es_fprintf (fp, "\n");
free_strlist (other_user_ids);
}
/* Get the stats for all the keys in CONFLICT_SET. */
strlist_rev (&conflict_set);
for (iter = conflict_set; iter && ! rc; iter = iter->next)
{
#define STATS_SQL(table, time, sign) \
"select fingerprint, policy, time_ago, count(*)\n" \
" from\n" \
" (select bindings.*,\n" \
" "sign" case\n" \
" when delta ISNULL then 1\n" \
/* From the future (but if its just a couple of hours in the \
* future don't turn it into a warning)? Or should we use \
* small, medium or large units? (Note: whatever we do, we \
* keep the value in seconds. Then when we group, everything \
* that rounds to the same number of seconds is grouped.) */ \
" when delta < -("STRINGIFY (TIME_AGO_FUTURE_IGNORE)") then 2\n" \
" when delta < ("STRINGIFY (TIME_AGO_SMALL_THRESHOLD)")\n" \
" then 3\n" \
" when delta < ("STRINGIFY (TIME_AGO_MEDIUM_THRESHOLD)")\n" \
" then 4\n" \
" when delta < ("STRINGIFY (TIME_AGO_LARGE_THRESHOLD)")\n" \
" then 5\n" \
" else 6\n" \
" end time_ago,\n" \
" delta time_ago_raw\n" \
" from bindings\n" \
" left join\n" \
" (select *,\n" \
" cast(? - " time " as real) delta\n" \
" from " table ") ss\n" \
" on ss.binding = bindings.oid)\n" \
" where email = ? and fingerprint = ?\n" \
" group by time_ago\n" \
/* Make sure the current key is first. */ \
" order by time_ago desc;\n"
/* Use the time when we saw the signature, not when the
signature was created as that can be forged. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_signature_stats,
signature_stats_collect_cb, &stats, &sqerr,
STATS_SQL ("signatures", "time", ""),
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END);
if (rc)
{
sqlite3_free (sqerr);
sqerr = NULL;
rc = gpg_error (GPG_ERR_GENERAL);
break;
}
if (!stats || strcmp (iter->d, stats->fingerprint) != 0)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, 1, 1);
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_gather_encryption_stats,
signature_stats_collect_cb, &stats, &sqerr,
STATS_SQL ("encryptions", "time", "-"),
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, iter->d,
GPGSQL_ARG_END);
if (rc)
{
rc = gpg_error (GPG_ERR_GENERAL);
break;
}
#undef STATS_SQL
if (!stats || strcmp (iter->d, stats->fingerprint) != 0
|| stats->time_ago > 0)
/* No stats for this binding. Add a dummy entry. */
signature_stats_prepend (&stats, iter->d, TOFU_POLICY_AUTO, -1, 1);
}
end_transaction (ctrl, 0);
strlist_rev (&conflict_set);
if (rc)
{
strlist_t strlist_iter;
log_error (_("error gathering signature stats: %s\n"), sqerr);
sqlite3_free (sqerr);
sqerr = NULL;
es_fprintf (fp, ngettext("The email address \"%s\" is"
" associated with %d key:\n",
"The email address \"%s\" is"
" associated with %d keys:\n",
conflict_set_count),
email, conflict_set_count);
for (strlist_iter = conflict_set;
strlist_iter;
strlist_iter = strlist_iter->next)
es_fprintf (fp, " %s\n", strlist_iter->d);
}
else
{
char *key = NULL;
strlist_t binding;
int seen_in_past = 0;
int encrypted = 1;
es_fprintf (fp, _("Statistics for keys"
" with the email address \"%s\":\n"),
email);
for (stats_iter = stats; stats_iter; stats_iter = stats_iter->next)
{
#if 0
log_debug ("%s: time_ago: %ld; count: %ld\n",
stats_iter->fingerprint,
stats_iter->time_ago,
stats_iter->count);
#endif
if (stats_iter->time_ago > 0 && encrypted)
{
/* We've change from the encrypted stats to the verified
* stats. Reset SEEN_IN_PAST. */
encrypted = 0;
seen_in_past = 0;
}
if (! key || strcmp (key, stats_iter->fingerprint))
{
int this_key;
char *key_pp;
key = stats_iter->fingerprint;
this_key = strcmp (key, fingerprint) == 0;
key_pp = format_hexfingerprint (key, NULL, 0);
es_fprintf (fp, " %s (", key_pp);
/* Find the associated binding. */
for (binding = conflict_set;
binding;
binding = binding->next)
if (strcmp (key, binding->d) == 0)
break;
log_assert (binding);
if ((binding->flags & BINDING_REVOKED))
{
es_fprintf (fp, _("revoked"));
es_fprintf (fp, ", ");
}
else if ((binding->flags & BINDING_EXPIRED))
{
es_fprintf (fp, _("expired"));
es_fprintf (fp, ", ");
}
if (this_key)
es_fprintf (fp, _("this key"));
else
es_fprintf (fp, _("policy: %s"),
tofu_policy_str (stats_iter->policy));
es_fputs ("):\n", fp);
xfree (key_pp);
seen_in_past = 0;
show_statistics (dbs, stats_iter->fingerprint, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
if (labs(stats_iter->time_ago) == 1)
{
/* The 1 in this case is the NULL entry. */
log_assert (stats_iter->count == 1);
stats_iter->count = 0;
}
seen_in_past += stats_iter->count;
es_fputs (" ", fp);
if (!stats_iter->count)
{
if (stats_iter->time_ago > 0)
es_fprintf (fp, ngettext("Verified %d message.",
"Verified %d messages.",
seen_in_past), seen_in_past);
else
es_fprintf (fp, ngettext("Encrypted %d message.",
"Encrypted %d messages.",
seen_in_past), seen_in_past);
}
else if (labs(stats_iter->time_ago) == 2)
{
if (stats_iter->time_ago > 0)
es_fprintf (fp, ngettext("Verified %d message in the future.",
"Verified %d messages in the future.",
seen_in_past), seen_in_past);
else
es_fprintf (fp, ngettext("Encrypted %d message in the future.",
"Encrypted %d messages in the future.",
seen_in_past), seen_in_past);
/* Reset it. */
seen_in_past = 0;
}
else
{
if (labs(stats_iter->time_ago) == 3)
{
int days = 1 + stats_iter->time_ago / TIME_AGO_UNIT_SMALL;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d day: %d.",
"Messages verified over the past %d days: %d.",
days), days, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d day: %d.",
"Messages encrypted over the past %d days: %d.",
days), days, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 4)
{
int months = 1 + stats_iter->time_ago / TIME_AGO_UNIT_MEDIUM;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d month: %d.",
"Messages verified over the past %d months: %d.",
months), months, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d month: %d.",
"Messages encrypted over the past %d months: %d.",
months), months, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 5)
{
int years = 1 + stats_iter->time_ago / TIME_AGO_UNIT_LARGE;
if (stats_iter->time_ago > 0)
es_fprintf
(fp,
ngettext("Messages verified over the past %d year: %d.",
"Messages verified over the past %d years: %d.",
years), years, seen_in_past);
else
es_fprintf
(fp,
ngettext("Messages encrypted over the past %d year: %d.",
"Messages encrypted over the past %d years: %d.",
years), years, seen_in_past);
}
else if (labs(stats_iter->time_ago) == 6)
{
if (stats_iter->time_ago > 0)
es_fprintf
(fp, _("Messages verified in the past: %d."),
seen_in_past);
else
es_fprintf
(fp, _("Messages encrypted in the past: %d."),
seen_in_past);
}
else
log_assert (! "Broken SQL.\n");
}
es_fputs ("\n", fp);
}
}
if (conflict_set_count > 1 || (conflict_set->flags & BINDING_CONFLICT))
{
/* This is a conflict. */
/* TRANSLATORS: Please translate the text found in the source
* file below. We don't directly internationalize that text so
* that we can tweak it without breaking translations. */
const char *text = _("TOFU detected a binding conflict");
char *textbuf;
if (!strcmp (text, "TOFU detected a binding conflict"))
{
/* No translation. Use the English text. */
text =
"Normally, an email address is associated with a single key. "
"However, people sometimes generate a new key if "
"their key is too old or they think it might be compromised. "
"Alternatively, a new key may indicate a man-in-the-middle "
"attack! Before accepting this association, you should talk to or "
"call the person to make sure this new key is legitimate.";
}
textbuf = format_text (text, 72, 80);
es_fprintf (fp, "\n%s\n", textbuf? textbuf : "[OUT OF CORE!]");
xfree (textbuf);
}
es_fputc ('\n', fp);
/* Add a NUL terminator. */
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &prompt, NULL))
log_fatal ("error snatching memory stream\n");
/* I think showing the large message once is sufficient. If we
* would move it right before the cpr_get many lines will scroll
* away and the user might not realize that he merely entered a
* wrong choice (because he does not see that either). As a small
* benefit we allow C-L to redisplay everything. */
tty_printf ("%s", prompt);
/* Suspend any transaction: it could take a while until the user
responds. */
tofu_suspend_batch_transaction (ctrl);
while (1)
{
char *response;
/* TRANSLATORS: Two letters (normally the lower and upper case
* version of the hotkey) for each of the five choices. If
* there is only one choice in your language, repeat it. */
choices = _("gG" "aA" "uU" "rR" "bB");
if (strlen (choices) != 10)
log_bug ("Bad TOFU conflict translation! Please report.");
response = cpr_get
("tofu.conflict",
_("(G)ood, (A)ccept once, (U)nknown, (R)eject once, (B)ad? "));
trim_spaces (response);
cpr_kill_prompt ();
if (*response == CONTROL_L)
tty_printf ("%s", prompt);
else if (!response[0])
/* Default to unknown. Don't save it. */
{
xfree (response);
tty_printf (_("Defaulting to unknown.\n"));
*policy = TOFU_POLICY_UNKNOWN;
break;
}
else if (!response[1])
{
char *choice = strchr (choices, *response);
if (choice)
{
int c = ((size_t) choice - (size_t) choices) / 2;
xfree (response);
switch (c)
{
case 0: /* Good. */
*policy = TOFU_POLICY_GOOD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 1: /* Accept once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_GOOD);
break;
case 2: /* Unknown. */
*policy = TOFU_POLICY_UNKNOWN;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
case 3: /* Reject once. */
*policy = TOFU_POLICY_ASK;
*trust_level = tofu_policy_to_trust_level (TOFU_POLICY_BAD);
break;
case 4: /* Bad. */
*policy = TOFU_POLICY_BAD;
*trust_level = tofu_policy_to_trust_level (*policy);
break;
default:
log_bug ("c should be between 0 and 4 but it is %d!", c);
}
if (record_binding (dbs, fingerprint, email, user_id,
*policy, TOFU_POLICY_NONE, NULL, 0, 0, now))
{
/* If there's an error registering the
* binding, don't save the signature. */
*trust_level = _tofu_GET_TRUST_ERROR;
}
break;
}
}
xfree (response);
}
tofu_resume_batch_transaction (ctrl);
xfree (prompt);
signature_stats_free (stats);
}
/* Return the set of keys that conflict with the binding <fingerprint,
email> (including the binding itself, which will be first in the
list). For each returned key also sets BINDING_NEW, etc. */
static strlist_t
build_conflict_set (ctrl_t ctrl, tofu_dbs_t dbs,
PKT_public_key *pk, const char *fingerprint,
const char *email)
{
gpg_error_t rc;
char *sqerr;
strlist_t conflict_set = NULL;
int conflict_set_count;
strlist_t iter;
kbnode_t *kb_all;
KEYDB_HANDLE hd;
int i;
/* Get the fingerprints of any bindings that share the email address
* and whether the bindings have a known conflict.
*
* Note: if the binding in question is in the DB, it will also be
* returned. Thus, if the result set is empty, then <email,
* fingerprint> is a new binding. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.get_trust_bindings_with_this_email,
strings_collect_cb2, &conflict_set, &sqerr,
"select"
/* A binding should only appear once, but try not to break in the
* case of corruption. */
" fingerprint || case sum(conflict NOTNULL) when 0 then '' else '!' end"
" from bindings where email = ?"
" group by fingerprint"
/* Make sure the current key comes first in the result list (if
it is present). */
" order by fingerprint = ? asc, fingerprint desc;",
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), sqerr);
print_further_info ("listing fingerprints");
sqlite3_free (sqerr);
rc = gpg_error (GPG_ERR_GENERAL);
return NULL;
}
/* Set BINDING_CONFLICT if the binding has a known conflict. This
* allows us to distinguish between bindings where the user
* explicitly set the policy to ask and bindings where we set the
* policy to ask due to a conflict. */
for (iter = conflict_set; iter; iter = iter->next)
{
/* Fixme: Why the check against N+1? */
int l = strlen (iter->d);
if (!(l == 2 * 20
|| l == 2 * 20 + 1
|| l == 2 * 32
|| l == 2 * 32 + 1))
{
log_error (_("TOFU db corruption detected.\n"));
print_further_info ("fingerprint '%s' is %d characters long",
iter->d, l);
}
if (l >= 1 && iter->d[l - 1] == '!')
{
iter->flags |= BINDING_CONFLICT;
/* Remove the !. */
iter->d[l - 1] = 0;
}
}
/* If the current binding has not yet been recorded, add it to the
* list. (The order by above ensures that if it is present, it will
* be first.) */
if (! (conflict_set && strcmp (conflict_set->d, fingerprint) == 0))
{
add_to_strlist (&conflict_set, fingerprint);
conflict_set->flags |= BINDING_NEW;
}
conflict_set_count = strlist_length (conflict_set);
/* Eliminate false conflicts. */
if (conflict_set_count == 1)
/* We only have a single key. There are no false conflicts to
eliminate. But, we do need to set the flags. */
{
if (pk->has_expired)
conflict_set->flags |= BINDING_EXPIRED;
if (pk->flags.revoked)
conflict_set->flags |= BINDING_REVOKED;
return conflict_set;
}
/* If two keys have cross signatures, then they are controlled by
* the same person and thus are not in conflict. */
kb_all = xcalloc (sizeof (kb_all[0]), conflict_set_count);
hd = keydb_new (ctrl);
for (i = 0, iter = conflict_set;
i < conflict_set_count;
i ++, iter = iter->next)
{
char *fp = iter->d;
KEYDB_SEARCH_DESC desc;
kbnode_t kb;
PKT_public_key *binding_pk;
kbnode_t n;
int found_user_id;
rc = keydb_search_reset (hd);
if (rc)
{
log_error ("resetting keydb failed: %s\n", gpg_strerror (rc));
continue;
}
rc = classify_user_id (fp, &desc, 0);
if (rc)
{
log_error (_("error parsing key specification '%s': %s\n"),
fp, gpg_strerror (rc));
continue;
}
rc = keydb_search (hd, &desc, 1, NULL);
if (rc)
{
/* Note: it is entirely possible that we don't have the key
corresponding to an entry in the TOFU DB. This can
happen if we merge two TOFU DBs, but not the key
rings. */
log_info (_("key \"%s\" not found: %s\n"),
fp, gpg_strerror (rc));
continue;
}
rc = keydb_get_keyblock (hd, &kb);
if (rc)
{
log_error (_("error reading keyblock: %s\n"),
gpg_strerror (rc));
print_further_info ("fingerprint: %s", fp);
continue;
}
merge_keys_and_selfsig (ctrl, kb);
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
kb_all[i] = kb;
/* Since we have the key block, use this opportunity to figure
* out if the binding is expired or revoked. */
binding_pk = kb->pkt->pkt.public_key;
/* The binding is always expired/revoked if the key is
* expired/revoked. */
if (binding_pk->has_expired)
iter->flags |= BINDING_EXPIRED;
if (binding_pk->flags.revoked)
iter->flags |= BINDING_REVOKED;
/* The binding is also expired/revoked if the user id is
* expired/revoked. */
n = kb;
found_user_id = 0;
while ((n = find_next_kbnode (n, PKT_USER_ID)) && ! found_user_id)
{
PKT_user_id *user_id2 = n->pkt->pkt.user_id;
char *email2;
if (user_id2->attrib_data)
continue;
email2 = email_from_user_id (user_id2->name);
if (strcmp (email, email2) == 0)
{
found_user_id = 1;
if (user_id2->flags.revoked)
iter->flags |= BINDING_REVOKED;
if (user_id2->flags.expired)
iter->flags |= BINDING_EXPIRED;
}
xfree (email2);
}
if (! found_user_id)
{
log_info (_("TOFU db corruption detected.\n"));
print_further_info ("user id '%s' not on key block '%s'",
email, fingerprint);
}
}
keydb_release (hd);
/* Now that we have the key blocks, check for cross sigs. */
{
int j;
strlist_t *prevp;
strlist_t iter_next;
int *die;
log_assert (conflict_set_count > 0);
die = xtrycalloc (conflict_set_count, sizeof *die);
if (!die)
{
/*err = gpg_error_from_syserror ();*/
xoutofcore (); /* Fixme: Let the function return an error. */
}
for (i = 0; i < conflict_set_count; i ++)
{
/* Look for cross sigs between this key (i == 0) or a key
* that has cross sigs with i == 0 (i.e., transitively) */
if (! (i == 0 || die[i]))
continue;
for (j = i + 1; j < conflict_set_count; j ++)
/* Be careful: we might not have a key block for a key. */
if (kb_all[i] && kb_all[j] && cross_sigs (email, kb_all[i], kb_all[j]))
die[j] = 1;
}
/* Free unconflicting bindings (and all of the key blocks). */
for (iter = conflict_set, prevp = &conflict_set, i = 0;
iter;
iter = iter_next, i ++)
{
iter_next = iter->next;
release_kbnode (kb_all[i]);
if (die[i])
{
*prevp = iter_next;
iter->next = NULL;
free_strlist (iter);
conflict_set_count --;
}
else
{
prevp = &iter->next;
}
}
/* We shouldn't have removed the head. */
log_assert (conflict_set);
log_assert (conflict_set_count >= 1);
xfree (die);
}
xfree (kb_all);
if (DBG_TRUST)
{
log_debug ("binding <key: %s, email: %s> conflicts:\n",
fingerprint, email);
for (iter = conflict_set; iter; iter = iter->next)
{
log_debug (" %s:%s%s%s%s\n",
iter->d,
(iter->flags & BINDING_NEW) ? " new" : "",
(iter->flags & BINDING_CONFLICT) ? " known_conflict" : "",
(iter->flags & BINDING_EXPIRED) ? " expired" : "",
(iter->flags & BINDING_REVOKED) ? " revoked" : "");
}
}
return conflict_set;
}
/* Return the effective policy for the binding <FINGERPRINT, EMAIL>
* (email has already been normalized). Returns
* _tofu_GET_POLICY_ERROR if an error occurs. Returns any conflict
* information in *CONFLICT_SETP if CONFLICT_SETP is not NULL and the
* returned policy is TOFU_POLICY_ASK (consequently, if there is a
* conflict, but the user set the policy to good *CONFLICT_SETP will
* empty). Note: as per build_conflict_set, which is used to build
* the conflict information, the conflict information includes the
* current user id as the first element of the linked list.
*
* This function registers the binding in the bindings table if it has
* not yet been registered.
*/
static enum tofu_policy
get_policy (ctrl_t ctrl, tofu_dbs_t dbs, PKT_public_key *pk,
const char *fingerprint, const char *user_id, const char *email,
strlist_t *conflict_setp, time_t now)
{
int rc;
char *err = NULL;
strlist_t results = NULL;
enum tofu_policy policy = _tofu_GET_POLICY_ERROR;
enum tofu_policy effective_policy_orig = TOFU_POLICY_NONE;
enum tofu_policy effective_policy = _tofu_GET_POLICY_ERROR;
long along;
char *conflict_orig = NULL;
char *conflict = NULL;
strlist_t conflict_set = NULL;
int conflict_set_count;
/* Check if the <FINGERPRINT, EMAIL> binding is known
(TOFU_POLICY_NONE cannot appear in the DB. Thus, if POLICY is
still TOFU_POLICY_NONE after executing the query, then the
result set was empty.) */
rc = gpgsql_stepx (dbs->db, &dbs->s.get_policy_select_policy_and_conflict,
strings_collect_cb2, &results, &err,
"select policy, conflict, effective_policy from bindings\n"
" where fingerprint = ? and email = ?",
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_STRING, email,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("reading the policy");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist_length (results) == 0)
{
/* No results. Use the defaults. */
policy = TOFU_POLICY_NONE;
effective_policy = TOFU_POLICY_NONE;
}
else if (strlist_length (results) == 3)
{
/* Parse and sanity check the results. */
if (string_to_long (&along, results->d, 0, __LINE__))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("bad value for policy: %s", results->d);
goto out;
}
policy = along;
if (! (policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_DB_CORRUPTED));
print_further_info ("invalid value for policy (%d)", policy);
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
if (*results->next->d)
conflict = xstrdup (results->next->d);
if (string_to_long (&along, results->next->next->d, 0, __LINE__))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("bad value for effective policy: %s",
results->next->next->d);
goto out;
}
effective_policy = along;
if (! (effective_policy == TOFU_POLICY_NONE
|| effective_policy == TOFU_POLICY_AUTO
|| effective_policy == TOFU_POLICY_GOOD
|| effective_policy == TOFU_POLICY_UNKNOWN
|| effective_policy == TOFU_POLICY_BAD
|| effective_policy == TOFU_POLICY_ASK))
{
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_DB_CORRUPTED));
print_further_info ("invalid value for effective_policy (%d)",
effective_policy);
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
}
else
{
/* The result has the wrong form. */
log_error (_("error reading TOFU database: %s\n"),
gpg_strerror (GPG_ERR_BAD_DATA));
print_further_info ("reading policy: expected 3 columns, got %d\n",
strlist_length (results));
goto out;
}
/* Save the effective policy and conflict so we know if we changed
* them. */
effective_policy_orig = effective_policy;
conflict_orig = conflict;
/* Unless there is a conflict, if the effective policy is cached,
* just return it. The reason we don't do this when there is a
* conflict is because of the following scenario: assume A and B
* conflict and B has signed A's key. Now, later we import A's
* signature on B. We need to recheck A, but the signature was on
* B, i.e., when B changes, we invalidate B's effective policy, but
* we also need to invalidate A's effective policy. Instead, we
* assume that conflicts are rare and don't optimize for them, which
* would complicate the code. */
if (effective_policy != TOFU_POLICY_NONE && !conflict)
goto out;
/* If the user explicitly set the policy, then respect that. */
if (policy != TOFU_POLICY_AUTO && policy != TOFU_POLICY_NONE)
{
effective_policy = policy;
goto out;
}
/* Unless proven wrong, assume the effective policy is 'auto'. */
effective_policy = TOFU_POLICY_AUTO;
/* See if the key is ultimately trusted. */
{
u32 kid[2];
keyid_from_pk (pk, kid);
if (tdb_keyid_is_utk (kid))
{
effective_policy = TOFU_POLICY_GOOD;
goto out;
}
}
/* See if the key is signed by an ultimately trusted key. */
{
int fingerprint_raw_len = strlen (fingerprint) / 2;
char fingerprint_raw[MAX_FINGERPRINT_LEN];
int len = 0;
/* FIXME(fingerprint) */
if (fingerprint_raw_len != 20 /*sizeof fingerprint_raw */
|| ((len = hex2bin (fingerprint,
fingerprint_raw, fingerprint_raw_len))
!= strlen (fingerprint)))
{
if (DBG_TRUST)
log_debug ("TOFU: Bad fingerprint: %s (len: %zu, parsed: %d)\n",
fingerprint, strlen (fingerprint), len);
}
else
{
int lookup_err;
kbnode_t kb;
lookup_err = get_pubkey_byfprint (ctrl, NULL, &kb,
fingerprint_raw,
fingerprint_raw_len);
if (lookup_err)
{
if (DBG_TRUST)
log_debug ("TOFU: Looking up %s: %s\n",
fingerprint, gpg_strerror (lookup_err));
}
else
{
int is_signed_by_utk = signed_by_utk (email, kb);
release_kbnode (kb);
if (is_signed_by_utk)
{
effective_policy = TOFU_POLICY_GOOD;
goto out;
}
}
}
}
/* Check for any conflicts / see if a previously discovered conflict
* disappeared. The latter can happen if the conflicting bindings
* are now cross signed, for instance. */
conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
conflict_set_count = strlist_length (conflict_set);
if (conflict_set_count == 0)
{
/* build_conflict_set should always at least return the current
binding. Something went wrong. */
effective_policy = _tofu_GET_POLICY_ERROR;
goto out;
}
if (conflict_set_count == 1
&& (conflict_set->flags & BINDING_NEW))
{
/* We've never observed a binding with this email address and we
* have a default policy, which is not to ask the user. */
/* If we've seen this binding, then we've seen this email and
* policy couldn't possibly be TOFU_POLICY_NONE. */
log_assert (policy == TOFU_POLICY_NONE);
if (DBG_TRUST)
log_debug ("TOFU: New binding <key: %s, user id: %s>, no conflict.\n",
fingerprint, email);
effective_policy = TOFU_POLICY_AUTO;
goto out;
}
if (conflict_set_count == 1
&& (conflict_set->flags & BINDING_CONFLICT))
{
/* No known conflicts now, but there was a conflict. This means
* at some point, there was a conflict and we changed this
* binding's policy to ask and set the conflicting key. The
* conflict can go away if there is not a cross sig between the
* two keys. In this case, just silently clear the conflict and
* reset the policy to auto. */
if (DBG_TRUST)
log_debug ("TOFU: binding <key: %s, user id: %s> had a conflict, but it's been resolved (probably via cross sig).\n",
fingerprint, email);
effective_policy = TOFU_POLICY_AUTO;
conflict = NULL;
goto out;
}
if (conflict_set_count == 1)
{
/* No conflicts and never marked as conflicting. */
log_assert (!conflict);
effective_policy = TOFU_POLICY_AUTO;
goto out;
}
/* There is a conflicting key. */
log_assert (conflict_set_count > 1);
effective_policy = TOFU_POLICY_ASK;
conflict = xstrdup (conflict_set->next->d);
out:
log_assert (policy == _tofu_GET_POLICY_ERROR
|| policy == TOFU_POLICY_NONE
|| policy == TOFU_POLICY_AUTO
|| policy == TOFU_POLICY_GOOD
|| policy == TOFU_POLICY_UNKNOWN
|| policy == TOFU_POLICY_BAD
|| policy == TOFU_POLICY_ASK);
/* Everything but NONE. */
log_assert (effective_policy == _tofu_GET_POLICY_ERROR
|| effective_policy == TOFU_POLICY_AUTO
|| effective_policy == TOFU_POLICY_GOOD
|| effective_policy == TOFU_POLICY_UNKNOWN
|| effective_policy == TOFU_POLICY_BAD
|| effective_policy == TOFU_POLICY_ASK);
if (effective_policy != TOFU_POLICY_ASK && conflict)
conflict = NULL;
/* If we don't have a record of this binding, its effective policy
* changed, or conflict changed, update the DB. */
if (effective_policy != _tofu_GET_POLICY_ERROR
&& (/* New binding. */
policy == TOFU_POLICY_NONE
/* effective_policy changed. */
|| effective_policy != effective_policy_orig
/* conflict changed. */
|| (conflict != conflict_orig
&& (!conflict || !conflict_orig
|| strcmp (conflict, conflict_orig) != 0))))
{
if (record_binding (dbs, fingerprint, email, user_id,
policy == TOFU_POLICY_NONE ? TOFU_POLICY_AUTO : policy,
effective_policy, conflict, 1, 0, now) != 0)
log_error ("error setting TOFU binding's policy"
" to %s\n", tofu_policy_str (policy));
}
/* If the caller wants the set of conflicts, return it. */
if (effective_policy == TOFU_POLICY_ASK && conflict_setp)
{
if (! conflict_set)
conflict_set = build_conflict_set (ctrl, dbs, pk, fingerprint, email);
*conflict_setp = conflict_set;
}
else
{
free_strlist (conflict_set);
if (conflict_setp)
*conflict_setp = NULL;
}
xfree (conflict_orig);
if (conflict != conflict_orig)
xfree (conflict);
free_strlist (results);
return effective_policy;
}
/* Return the trust level (TRUST_NEVER, etc.) for the binding
* <FINGERPRINT, EMAIL> (email is already normalized). If no policy
* is registered, returns TOFU_POLICY_NONE. If an error occurs,
* returns _tofu_GET_TRUST_ERROR.
*
* PK is the public key object for FINGERPRINT.
*
* USER_ID is the unadulterated user id.
*
* If MAY_ASK is set, then we may interact with the user. This is
* necessary if there is a conflict or the binding's policy is
* TOFU_POLICY_ASK. In the case of a conflict, we set the new
* conflicting binding's policy to TOFU_POLICY_ASK. In either case,
* we return TRUST_UNDEFINED. Note: if MAY_ASK is set, then this
* function must not be called while in a transaction! */
static enum tofu_policy
get_trust (ctrl_t ctrl, PKT_public_key *pk,
const char *fingerprint, const char *email,
const char *user_id, int may_ask,
enum tofu_policy *policyp, strlist_t *conflict_setp,
time_t now)
{
tofu_dbs_t dbs = ctrl->tofu.dbs;
int in_transaction = 0;
enum tofu_policy policy;
int rc;
char *sqerr = NULL;
strlist_t conflict_set = NULL;
int trust_level = TRUST_UNKNOWN;
strlist_t iter;
log_assert (dbs);
if (may_ask)
log_assert (dbs->in_transaction == 0);
if (opt.batch)
may_ask = 0;
log_assert (pk_is_primary (pk));
/* Make sure _tofu_GET_TRUST_ERROR isn't equal to any of the trust
levels. */
log_assert (_tofu_GET_TRUST_ERROR != TRUST_UNKNOWN
&& _tofu_GET_TRUST_ERROR != TRUST_EXPIRED
&& _tofu_GET_TRUST_ERROR != TRUST_UNDEFINED
&& _tofu_GET_TRUST_ERROR != TRUST_NEVER
&& _tofu_GET_TRUST_ERROR != TRUST_MARGINAL
&& _tofu_GET_TRUST_ERROR != TRUST_FULLY
&& _tofu_GET_TRUST_ERROR != TRUST_ULTIMATE);
begin_transaction (ctrl, 0);
in_transaction = 1;
/* We need to call get_policy even if the key is ultimately trusted
* to make sure the binding has been registered. */
policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email,
&conflict_set, now);
if (policy == TOFU_POLICY_ASK)
/* The conflict set should always contain at least one element:
* the current key. */
log_assert (conflict_set);
else
/* If the policy is not TOFU_POLICY_ASK, then conflict_set will be
* NULL. */
log_assert (! conflict_set);
/* If the key is ultimately trusted, there is nothing to do. */
{
u32 kid[2];
keyid_from_pk (pk, kid);
if (tdb_keyid_is_utk (kid))
{
trust_level = TRUST_ULTIMATE;
policy = TOFU_POLICY_GOOD;
goto out;
}
}
if (policy == TOFU_POLICY_AUTO)
{
policy = opt.tofu_default_policy;
if (DBG_TRUST)
log_debug ("TOFU: binding <key: %s, user id: %s>'s policy is"
" auto (default: %s).\n",
fingerprint, email,
tofu_policy_str (opt.tofu_default_policy));
if (policy == TOFU_POLICY_ASK)
/* The default policy is ASK, but there is no conflict (policy
* was 'auto'). In this case, we need to make sure the
* conflict set includes at least the current user id. */
{
add_to_strlist (&conflict_set, fingerprint);
}
}
switch (policy)
{
case TOFU_POLICY_AUTO:
case TOFU_POLICY_GOOD:
case TOFU_POLICY_UNKNOWN:
case TOFU_POLICY_BAD:
/* The saved judgement is auto -> auto, good, unknown or bad.
* We don't need to ask the user anything. */
if (DBG_TRUST)
log_debug ("TOFU: Known binding <key: %s, user id: %s>'s policy: %s\n",
fingerprint, email, tofu_policy_str (policy));
trust_level = tofu_policy_to_trust_level (policy);
goto out;
case TOFU_POLICY_ASK:
/* We need to ask the user what to do. */
break;
case _tofu_GET_POLICY_ERROR:
trust_level = _tofu_GET_TRUST_ERROR;
goto out;
default:
log_bug ("%s: Impossible value for policy (%d)\n", __func__, policy);
}
/* We get here if:
*
* 1. The saved policy is auto and the default policy is ask
* (get_policy() == TOFU_POLICY_AUTO
* && opt.tofu_default_policy == TOFU_POLICY_ASK)
*
* 2. The saved policy is ask (either last time the user selected
* accept once or reject once or there was a conflict and this
* binding's policy was changed from auto to ask)
* (policy == TOFU_POLICY_ASK).
*/
log_assert (policy == TOFU_POLICY_ASK);
if (may_ask)
{
/* We can't be in a normal transaction in ask_about_binding. */
end_transaction (ctrl, 0);
in_transaction = 0;
/* If we get here, we need to ask the user about the binding. */
ask_about_binding (ctrl,
&policy,
&trust_level,
conflict_set,
fingerprint,
email,
user_id,
now);
}
else
{
trust_level = TRUST_UNDEFINED;
}
/* Mark any conflicting bindings that have an automatic policy as
* now requiring confirmation. Note: we do this after we ask for
* confirmation so that when the current policy is printed, it is
* correct. */
if (! in_transaction)
{
begin_transaction (ctrl, 0);
in_transaction = 1;
}
/* The conflict set should always contain at least one element:
* the current key. */
log_assert (conflict_set);
for (iter = conflict_set->next; iter; iter = iter->next)
{
/* We don't immediately set the effective policy to 'ask,
because */
rc = gpgsql_exec_printf
(dbs->db, NULL, NULL, &sqerr,
"update bindings set effective_policy = %d, conflict = %Q"
" where email = %Q and fingerprint = %Q and effective_policy != %d;",
TOFU_POLICY_NONE, fingerprint,
email, iter->d, TOFU_POLICY_ASK);
if (rc)
{
log_error (_("error changing TOFU policy: %s\n"), sqerr);
print_further_info ("binding: <key: %s, user id: %s>",
fingerprint, user_id);
sqlite3_free (sqerr);
sqerr = NULL;
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (DBG_TRUST)
log_debug ("Set %s to conflict with %s\n",
iter->d, fingerprint);
}
out:
if (in_transaction)
end_transaction (ctrl, 0);
if (policyp)
*policyp = policy;
if (conflict_setp)
*conflict_setp = conflict_set;
else
free_strlist (conflict_set);
return trust_level;
}
/* Return a malloced string of the form
* "7~months"
* The caller should replace all '~' in the returned string by a space
* and also free the returned string.
*
* This is actually a bad hack which may not work correctly with all
* languages.
*/
static char *
time_ago_str (long long int t)
{
/* It would be nice to use a macro to do this, but gettext
works on the unpreprocessed code. */
#define MIN_SECS (60)
#define HOUR_SECS (60 * MIN_SECS)
#define DAY_SECS (24 * HOUR_SECS)
#define WEEK_SECS (7 * DAY_SECS)
#define MONTH_SECS (30 * DAY_SECS)
#define YEAR_SECS (365 * DAY_SECS)
if (t > 2 * YEAR_SECS)
{
long long int c = t / YEAR_SECS;
return xtryasprintf (ngettext("%lld~year", "%lld~years", c), c);
}
if (t > 2 * MONTH_SECS)
{
long long int c = t / MONTH_SECS;
return xtryasprintf (ngettext("%lld~month", "%lld~months", c), c);
}
if (t > 2 * WEEK_SECS)
{
long long int c = t / WEEK_SECS;
return xtryasprintf (ngettext("%lld~week", "%lld~weeks", c), c);
}
if (t > 2 * DAY_SECS)
{
long long int c = t / DAY_SECS;
return xtryasprintf (ngettext("%lld~day", "%lld~days", c), c);
}
if (t > 2 * HOUR_SECS)
{
long long int c = t / HOUR_SECS;
return xtryasprintf (ngettext("%lld~hour", "%lld~hours", c), c);
}
if (t > 2 * MIN_SECS)
{
long long int c = t / MIN_SECS;
return xtryasprintf (ngettext("%lld~minute", "%lld~minutes", c), c);
}
return xtryasprintf (ngettext("%lld~second", "%lld~seconds", t), t);
}
/* If FP is NULL, write TOFU_STATS status line. If FP is not NULL
* write a "tfs" record to that stream. */
static void
write_stats_status (estream_t fp,
enum tofu_policy policy,
unsigned long signature_count,
unsigned long signature_first_seen,
unsigned long signature_most_recent,
unsigned long signature_days,
unsigned long encryption_count,
unsigned long encryption_first_done,
unsigned long encryption_most_recent,
unsigned long encryption_days)
{
int summary;
int validity;
unsigned long days_sq;
/* Use the euclidean distance (m = sqrt(a^2 + b^2)) rather then the
sum of the magnitudes (m = a + b) to ensure a balance between
verified signatures and encrypted messages. */
days_sq = signature_days * signature_days + encryption_days * encryption_days;
if (days_sq < 1)
validity = 1; /* Key without history. */
else if (days_sq < (2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD))
validity = 2; /* Key with too little history. */
else if (days_sq < (2 * FULL_TRUST_THRESHOLD) * (2 * FULL_TRUST_THRESHOLD))
validity = 3; /* Key with enough history for basic trust. */
else
validity = 4; /* Key with a lot of history. */
if (policy == TOFU_POLICY_ASK)
summary = 0; /* Key requires attention. */
else
summary = validity;
if (fp)
{
es_fprintf (fp, "tfs:1:%d:%lu:%lu:%s:%lu:%lu:%lu:%lu:%d:%lu:%lu:\n",
summary, signature_count, encryption_count,
tofu_policy_str (policy),
signature_first_seen, signature_most_recent,
encryption_first_done, encryption_most_recent,
validity, signature_days, encryption_days);
}
else
{
write_status_printf (STATUS_TOFU_STATS,
"%d %lu %lu %s %lu %lu %lu %lu %d %lu %lu",
summary,
signature_count,
encryption_count,
tofu_policy_str (policy),
signature_first_seen,
signature_most_recent,
encryption_first_done,
encryption_most_recent,
validity,
signature_days, encryption_days);
}
}
/* Note: If OUTFP is not NULL, this function merely prints a "tfs" record
* to OUTFP.
*
* POLICY is the key's policy (as returned by get_policy).
*
* Returns 0 if ONLY_STATUS_FD is set. Otherwise, returns whether
* the caller should call show_warning after iterating over all user
* ids.
*/
static int
show_statistics (tofu_dbs_t dbs,
const char *fingerprint, const char *email,
enum tofu_policy policy,
estream_t outfp, int only_status_fd, time_t now)
{
char *fingerprint_pp;
int rc;
strlist_t strlist = NULL;
char *err = NULL;
unsigned long signature_first_seen = 0;
unsigned long signature_most_recent = 0;
unsigned long signature_count = 0;
unsigned long signature_days = 0;
unsigned long encryption_first_done = 0;
unsigned long encryption_most_recent = 0;
unsigned long encryption_count = 0;
unsigned long encryption_days = 0;
int show_warning = 0;
if (only_status_fd && ! is_status_enabled ())
return 0;
fingerprint_pp = format_hexfingerprint (fingerprint, NULL, 0);
/* Get the signature stats. */
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), coalesce (min (signatures.time), 0),\n"
" coalesce (max (signatures.time), 0)\n"
" from signatures\n"
" left join bindings on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q;",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting signature statistics");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*) from\n"
" (select round(signatures.time / (24 * 60 * 60)) day\n"
" from signatures\n"
" left join bindings on signatures.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q\n"
" group by day);",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting signature statistics (by day)");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist)
{
/* We expect exactly 4 elements. */
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (strlist->next->next->next);
log_assert (! strlist->next->next->next->next);
string_to_ulong (&signature_days, strlist->d, -1, __LINE__);
string_to_ulong (&signature_count, strlist->next->d, -1, __LINE__);
string_to_ulong (&signature_first_seen,
strlist->next->next->d, -1, __LINE__);
string_to_ulong (&signature_most_recent,
strlist->next->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
/* Get the encryption stats. */
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*), coalesce (min (encryptions.time), 0),\n"
" coalesce (max (encryptions.time), 0)\n"
" from encryptions\n"
" left join bindings on encryptions.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q;",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting encryption statistics");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
rc = gpgsql_exec_printf
(dbs->db, strings_collect_cb, &strlist, &err,
"select count (*) from\n"
" (select round(encryptions.time / (24 * 60 * 60)) day\n"
" from encryptions\n"
" left join bindings on encryptions.binding = bindings.oid\n"
" where fingerprint = %Q and email = %Q\n"
" group by day);",
fingerprint, email);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), err);
print_further_info ("getting encryption statistics (by day)");
sqlite3_free (err);
rc = gpg_error (GPG_ERR_GENERAL);
goto out;
}
if (strlist)
{
/* We expect exactly 4 elements. */
log_assert (strlist->next);
log_assert (strlist->next->next);
log_assert (strlist->next->next->next);
log_assert (! strlist->next->next->next->next);
string_to_ulong (&encryption_days, strlist->d, -1, __LINE__);
string_to_ulong (&encryption_count, strlist->next->d, -1, __LINE__);
string_to_ulong (&encryption_first_done,
strlist->next->next->d, -1, __LINE__);
string_to_ulong (&encryption_most_recent,
strlist->next->next->next->d, -1, __LINE__);
free_strlist (strlist);
strlist = NULL;
}
if (!outfp)
write_status_text_and_buffer (STATUS_TOFU_USER, fingerprint,
email, strlen (email), 0);
write_stats_status (outfp, policy,
signature_count,
signature_first_seen,
signature_most_recent,
signature_days,
encryption_count,
encryption_first_done,
encryption_most_recent,
encryption_days);
if (!outfp && !only_status_fd)
{
estream_t fp;
char *msg;
fp = es_fopenmem (0, "rw,samethread");
if (! fp)
log_fatal ("error creating memory stream: %s\n",
gpg_strerror (gpg_error_from_syserror()));
if (signature_count == 0 && encryption_count == 0)
{
es_fprintf (fp,
_("%s: Verified 0~signatures and encrypted 0~messages."),
email);
}
else
{
if (signature_count == 0)
es_fprintf (fp, _("%s: Verified 0 signatures."), email);
else
{
/* Note: Translation not possible with that wording. */
char *ago_str = time_ago_str (now - signature_first_seen);
es_fprintf
(fp, "%s: Verified %ld~signatures in the past %s.",
email, signature_count, ago_str);
xfree (ago_str);
}
es_fputs (" ", fp);
if (encryption_count == 0)
es_fprintf (fp, _("Encrypted 0 messages."));
else
{
char *ago_str = time_ago_str (now - encryption_first_done);
/* Note: Translation not possible with this kind of
* composition. */
es_fprintf (fp, "Encrypted %ld~messages in the past %s.",
encryption_count, ago_str);
xfree (ago_str);
}
}
if (opt.verbose)
{
es_fputs (" ", fp);
es_fprintf (fp, _("(policy: %s)"), tofu_policy_str (policy));
}
es_fputs ("\n", fp);
{
char *tmpmsg, *p;
es_fputc (0, fp);
if (es_fclose_snatch (fp, (void **) &tmpmsg, NULL))
log_fatal ("error snatching memory stream\n");
msg = format_text (tmpmsg, 72, 80);
if (!msg) /* FIXME: Return the error all the way up. */
log_fatal ("format failed: %s\n",
gpg_strerror (gpg_error_from_syserror()));
es_free (tmpmsg);
/* Print a status line but suppress the trailing LF.
* Spaces are not percent escaped. */
if (*msg)
write_status_buffer (STATUS_TOFU_STATS_LONG,
msg, strlen (msg)-1, -1);
/* Remove the non-breaking space markers. */
for (p=msg; *p; p++)
if (*p == '~')
*p = ' ';
}
log_string (GPGRT_LOGLVL_INFO, msg);
xfree (msg);
if (policy == TOFU_POLICY_AUTO)
{
if (signature_count == 0)
log_info (_("Warning: we have yet to see"
" a message signed using this key and user id!\n"));
else if (signature_count == 1)
log_info (_("Warning: we've only seen one message"
" signed using this key and user id!\n"));
if (encryption_count == 0)
log_info (_("Warning: you have yet to encrypt"
" a message to this key!\n"));
else if (encryption_count == 1)
log_info (_("Warning: you have only encrypted"
" one message to this key!\n"));
/* Cf. write_stats_status */
if ((encryption_count * encryption_count
+ signature_count * signature_count)
< ((2 * BASIC_TRUST_THRESHOLD) * (2 * BASIC_TRUST_THRESHOLD)))
show_warning = 1;
}
}
out:
xfree (fingerprint_pp);
return show_warning;
}
static void
show_warning (const char *fingerprint, strlist_t user_id_list)
{
char *set_policy_command;
char *text;
char *tmpmsg;
set_policy_command =
xasprintf ("gpg --tofu-policy bad %s", fingerprint);
tmpmsg = xasprintf
(ngettext
("Warning: if you think you've seen more signatures "
"by this key and user id, then this key might be a "
"forgery! Carefully examine the email address for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
"Warning: if you think you've seen more signatures "
"by this key and these user ids, then this key might be a "
"forgery! Carefully examine the email addresses for small "
"variations. If the key is suspect, then use\n"
" %s\n"
"to mark it as being bad.\n",
strlist_length (user_id_list)),
set_policy_command);
text = format_text (tmpmsg, 72, 80);
if (!text) /* FIXME: Return the error all the way up. */
log_fatal ("format failed: %s\n",
gpg_strerror (gpg_error_from_syserror()));
xfree (tmpmsg);
log_string (GPGRT_LOGLVL_INFO, text);
xfree (text);
es_free (set_policy_command);
}
/* Extract the email address from a user id and normalize it. If the
user id doesn't contain an email address, then we use the whole
user_id and normalize that. The returned string must be freed. */
static char *
email_from_user_id (const char *user_id)
{
char *email = mailbox_from_userid (user_id, 0);
if (! email)
{
/* Hmm, no email address was provided or we are out of core. Just
take the lower-case version of the whole user id. It could be
a hostname, for instance. */
email = ascii_strlwr (xstrdup (user_id));
}
return email;
}
/* Register the signature with the bindings <fingerprint, USER_ID>,
for each USER_ID in USER_ID_LIST. The fingerprint is taken from
the primary key packet PK.
SIG_DIGEST_BIN is the binary representation of the message's
digest. SIG_DIGEST_BIN_LEN is its length.
SIG_TIME is the time that the signature was generated.
ORIGIN is a free-formed string describing the origin of the
signature. If this was from an email and the Claws MUA was used,
then this should be something like: "email:claws". If this is
NULL, the default is simply "unknown".
If MAY_ASK is 1, then this function may interact with the user.
This is necessary if there is a conflict or the binding's policy is
TOFU_POLICY_ASK.
This function returns 0 on success and an error code if an error
occurred. */
gpg_error_t
tofu_register_signature (ctrl_t ctrl,
PKT_public_key *pk, strlist_t user_id_list,
const byte *sig_digest_bin, int sig_digest_bin_len,
time_t sig_time, const char *origin)
{
time_t now = gnupg_get_time ();
gpg_error_t rc;
tofu_dbs_t dbs;
char *fingerprint = NULL;
strlist_t user_id;
char *email = NULL;
char *sqlerr = NULL;
char *sig_digest = NULL;
unsigned long c;
dbs = opendbs (ctrl);
if (! dbs)
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (rc));
return rc;
}
/* We do a query and then an insert. Make sure they are atomic
by wrapping them in a transaction. */
rc = begin_transaction (ctrl, 0);
if (rc)
return rc;
log_assert (pk_is_primary (pk));
sig_digest = make_radix64_string (sig_digest_bin, sig_digest_bin_len);
if (!sig_digest)
{
rc = gpg_error_from_syserror ();
goto leave;
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
{
rc = gpg_error_from_syserror ();
goto leave;
}
if (! origin)
origin = "unknown"; /* The default origin is simply "unknown". */
for (user_id = user_id_list; user_id; user_id = user_id->next)
{
email = email_from_user_id (user_id->d);
if (DBG_TRUST)
log_debug ("TOFU: Registering signature %s with binding"
" <key: %s, user id: %s>\n",
sig_digest, fingerprint, email);
/* Make sure the binding exists and record any TOFU
conflicts. */
if (get_trust (ctrl, pk, fingerprint, email, user_id->d,
0, NULL, NULL, now)
== _tofu_GET_TRUST_ERROR)
{
rc = gpg_error (GPG_ERR_GENERAL);
xfree (email);
break;
}
/* If we've already seen this signature before, then don't add
it again. */
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_already_seen,
get_single_unsigned_long_cb2, &c, &sqlerr,
"select count (*)\n"
" from signatures left join bindings\n"
" on signatures.binding = bindings.oid\n"
" where fingerprint = ? and email = ? and sig_time = ?\n"
" and sig_digest = ?",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_LONG_LONG, (long long) sig_time,
GPGSQL_ARG_STRING, sig_digest,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error reading TOFU database: %s\n"), sqlerr);
print_further_info ("checking existence");
sqlite3_free (sqlerr);
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (c > 1)
/* Duplicates! This should not happen. In particular,
because <fingerprint, email, sig_time, sig_digest> is the
primary key! */
log_debug ("SIGNATURES DB contains duplicate records"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>."
" Please report.\n",
fingerprint, email, (unsigned long) sig_time,
sig_digest, origin);
else if (c == 1)
{
if (DBG_TRUST)
log_debug ("Already observed the signature and binding"
" <key: %s, email: %s, time: 0x%lx, sig: %s,"
" origin: %s>\n",
fingerprint, email, (unsigned long) sig_time,
sig_digest, origin);
}
else if (opt.dry_run)
{
log_info ("TOFU database update skipped due to --dry-run\n");
}
else
/* This is the first time that we've seen this signature and
binding. Record it. */
{
if (DBG_TRUST)
log_debug ("TOFU: Saving signature"
" <key: %s, user id: %s, sig: %s>\n",
fingerprint, email, sig_digest);
log_assert (c == 0);
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_signature, NULL, NULL, &sqlerr,
"insert into signatures\n"
" (binding, sig_digest, origin, sig_time, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = ? and email = ?),\n"
" ?, ?, ?, ?);",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_STRING, sig_digest, GPGSQL_ARG_STRING, origin,
GPGSQL_ARG_LONG_LONG, (long long) sig_time,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), sqlerr);
print_further_info ("insert signatures");
sqlite3_free (sqlerr);
rc = gpg_error (GPG_ERR_GENERAL);
}
}
xfree (email);
if (rc)
break;
}
leave:
if (rc)
rollback_transaction (ctrl);
else
rc = end_transaction (ctrl, 0);
xfree (fingerprint);
xfree (sig_digest);
return rc;
}
gpg_error_t
tofu_register_encryption (ctrl_t ctrl,
PKT_public_key *pk, strlist_t user_id_list,
int may_ask)
{
time_t now = gnupg_get_time ();
gpg_error_t rc = 0;
tofu_dbs_t dbs;
kbnode_t kb = NULL;
int free_user_id_list = 0;
char *fingerprint = NULL;
strlist_t user_id;
char *sqlerr = NULL;
int in_batch = 0;
dbs = opendbs (ctrl);
if (! dbs)
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (rc));
return rc;
}
if (/* We need the key block to find the primary key. */
! pk_is_primary (pk)
/* We need the key block to find all user ids. */
|| ! user_id_list)
kb = get_pubkeyblock (ctrl, pk->keyid);
/* Make sure PK is a primary key. */
if (! pk_is_primary (pk))
pk = kb->pkt->pkt.public_key;
if (! user_id_list)
{
/* Use all non-revoked user ids. Do use expired user ids. */
kbnode_t n = kb;
while ((n = find_next_kbnode (n, PKT_USER_ID)))
{
PKT_user_id *uid = n->pkt->pkt.user_id;
if (uid->flags.revoked)
continue;
add_to_strlist (&user_id_list, uid->name);
}
free_user_id_list = 1;
if (! user_id_list)
log_info (_("WARNING: Encrypting to %s, which has no "
"non-revoked user ids\n"),
keystr (pk->keyid));
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
{
rc = gpg_error_from_syserror ();
goto leave;
}
tofu_begin_batch_update (ctrl);
in_batch = 1;
tofu_resume_batch_transaction (ctrl);
for (user_id = user_id_list; user_id; user_id = user_id->next)
{
char *email = email_from_user_id (user_id->d);
strlist_t conflict_set = NULL;
enum tofu_policy policy;
/* Make sure the binding exists and that we recognize any
conflicts. */
int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
may_ask, &policy, &conflict_set, now);
if (tl == _tofu_GET_TRUST_ERROR)
{
/* An error. */
rc = gpg_error (GPG_ERR_GENERAL);
xfree (email);
goto leave;
}
/* If there is a conflict and MAY_ASK is true, we need to show
* the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode, then
* they have already been printed (this is required to make sure
* the information is available to the caller before cpr_get is
* called). */
if (policy == TOFU_POLICY_ASK && may_ask && opt.batch)
{
strlist_t iter;
/* The conflict set should contain at least the current
* key. */
log_assert (conflict_set);
for (iter = conflict_set; iter; iter = iter->next)
show_statistics (dbs, iter->d, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
free_strlist (conflict_set);
rc = gpgsql_stepx
(dbs->db, &dbs->s.register_encryption, NULL, NULL, &sqlerr,
"insert into encryptions\n"
" (binding, time)\n"
" values\n"
" ((select oid from bindings\n"
" where fingerprint = ? and email = ?),\n"
" ?);",
GPGSQL_ARG_STRING, fingerprint, GPGSQL_ARG_STRING, email,
GPGSQL_ARG_LONG_LONG, (long long) now,
GPGSQL_ARG_END);
if (rc)
{
log_error (_("error updating TOFU database: %s\n"), sqlerr);
print_further_info ("insert encryption");
sqlite3_free (sqlerr);
rc = gpg_error (GPG_ERR_GENERAL);
}
xfree (email);
}
leave:
if (in_batch)
tofu_end_batch_update (ctrl);
release_kbnode (kb);
if (free_user_id_list)
free_strlist (user_id_list);
xfree (fingerprint);
return rc;
}
/* Combine a trust level returned from the TOFU trust model with a
trust level returned by the PGP trust model. This is primarily of
interest when the trust model is tofu+pgp (TM_TOFU_PGP).
This function ors together the upper bits (the values not covered
by TRUST_MASK, i.e., TRUST_FLAG_REVOKED, etc.). */
int
tofu_wot_trust_combine (int tofu_base, int wot_base)
{
int tofu = tofu_base & TRUST_MASK;
int wot = wot_base & TRUST_MASK;
int upper = (tofu_base & ~TRUST_MASK) | (wot_base & ~TRUST_MASK);
log_assert (tofu == TRUST_UNKNOWN
|| tofu == TRUST_EXPIRED
|| tofu == TRUST_UNDEFINED
|| tofu == TRUST_NEVER
|| tofu == TRUST_MARGINAL
|| tofu == TRUST_FULLY
|| tofu == TRUST_ULTIMATE);
log_assert (wot == TRUST_UNKNOWN
|| wot == TRUST_EXPIRED
|| wot == TRUST_UNDEFINED
|| wot == TRUST_NEVER
|| wot == TRUST_MARGINAL
|| wot == TRUST_FULLY
|| wot == TRUST_ULTIMATE);
- /* We first consider negative trust policys. These trump positive
+ /* We first consider negative trust policies. These trump positive
trust policies. */
if (tofu == TRUST_NEVER || wot == TRUST_NEVER)
/* TRUST_NEVER trumps everything else. */
return upper | TRUST_NEVER;
if (tofu == TRUST_EXPIRED || wot == TRUST_EXPIRED)
/* TRUST_EXPIRED trumps everything but TRUST_NEVER. */
return upper | TRUST_EXPIRED;
/* Now we only have positive or neutral trust policies. We take
the max. */
if (tofu == TRUST_ULTIMATE)
return upper | TRUST_ULTIMATE | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_ULTIMATE)
return upper | TRUST_ULTIMATE;
if (tofu == TRUST_FULLY)
return upper | TRUST_FULLY | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_FULLY)
return upper | TRUST_FULLY;
if (tofu == TRUST_MARGINAL)
return upper | TRUST_MARGINAL | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_MARGINAL)
return upper | TRUST_MARGINAL;
if (tofu == TRUST_UNDEFINED)
return upper | TRUST_UNDEFINED | TRUST_FLAG_TOFU_BASED;
if (wot == TRUST_UNDEFINED)
return upper | TRUST_UNDEFINED;
return upper | TRUST_UNKNOWN;
}
/* Write a "tfs" record for a --with-colons listing. */
gpg_error_t
tofu_write_tfs_record (ctrl_t ctrl, estream_t fp,
PKT_public_key *pk, const char *user_id)
{
time_t now = gnupg_get_time ();
gpg_error_t err = 0;
tofu_dbs_t dbs;
char *fingerprint;
char *email = NULL;
enum tofu_policy policy;
if (!*user_id)
return 0; /* No TOFU stats possible for an empty ID. */
dbs = opendbs (ctrl);
if (!dbs)
{
err = gpg_error (GPG_ERR_GENERAL);
log_error (_("error opening TOFU database: %s\n"), gpg_strerror (err));
return err;
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
{
err = gpg_error_from_syserror ();
goto leave;
}
email = email_from_user_id (user_id);
policy = get_policy (ctrl, dbs, pk, fingerprint, user_id, email, NULL, now);
show_statistics (dbs, fingerprint, email, policy, fp, 0, now);
leave:
xfree (email);
xfree (fingerprint);
return err;
}
/* Return the validity (TRUST_NEVER, etc.) of the bindings
<FINGERPRINT, USER_ID>, for each USER_ID in USER_ID_LIST. If
USER_ID_LIST->FLAG is set, then the id is considered to be expired.
PK is the primary key packet.
If MAY_ASK is 1 and the policy is TOFU_POLICY_ASK, then the user
will be prompted to choose a policy. If MAY_ASK is 0 and the
policy is TOFU_POLICY_ASK, then TRUST_UNKNOWN is returned.
Returns TRUST_UNDEFINED if an error occurs.
Fixme: eturn an error code
*/
int
tofu_get_validity (ctrl_t ctrl, PKT_public_key *pk, strlist_t user_id_list,
int may_ask)
{
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
char *fingerprint = NULL;
strlist_t user_id;
int trust_level = TRUST_UNKNOWN;
int bindings = 0;
int bindings_valid = 0;
int need_warning = 0;
int had_conflict = 0;
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return TRUST_UNDEFINED;
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
log_fatal ("%s: malloc failed\n", __func__);
tofu_begin_batch_update (ctrl);
/* Start the batch transaction now. */
tofu_resume_batch_transaction (ctrl);
for (user_id = user_id_list; user_id; user_id = user_id->next, bindings ++)
{
char *email = email_from_user_id (user_id->d);
strlist_t conflict_set = NULL;
enum tofu_policy policy;
/* Always call get_trust to make sure the binding is
registered. */
int tl = get_trust (ctrl, pk, fingerprint, email, user_id->d,
may_ask, &policy, &conflict_set, now);
if (tl == _tofu_GET_TRUST_ERROR)
{
/* An error. */
trust_level = TRUST_UNDEFINED;
xfree (email);
goto die;
}
if (DBG_TRUST)
log_debug ("TOFU: validity for <key: %s, user id: %s>: %s%s.\n",
fingerprint, email,
trust_value_to_string (tl),
user_id->flags ? " (but expired)" : "");
if (user_id->flags)
tl = TRUST_EXPIRED;
if (tl != TRUST_EXPIRED)
bindings_valid ++;
if (may_ask && tl != TRUST_ULTIMATE && tl != TRUST_EXPIRED)
{
/* If policy is ask, then we already printed out the
* conflict information in ask_about_binding or will do so
* in a moment. */
if (policy != TOFU_POLICY_ASK)
need_warning |=
show_statistics (dbs, fingerprint, email, policy, NULL, 0, now);
/* If there is a conflict and MAY_ASK is true, we need to
* show the TOFU statistics for the current binding and the
* conflicting bindings. But, if we are not in batch mode,
* then they have already been printed (this is required to
* make sure the information is available to the caller
* before cpr_get is called). */
if (policy == TOFU_POLICY_ASK && opt.batch)
{
strlist_t iter;
/* The conflict set should contain at least the current
* key. */
log_assert (conflict_set);
had_conflict = 1;
for (iter = conflict_set; iter; iter = iter->next)
show_statistics (dbs, iter->d, email,
TOFU_POLICY_ASK, NULL, 1, now);
}
}
free_strlist (conflict_set);
if (tl == TRUST_NEVER)
trust_level = TRUST_NEVER;
else if (tl == TRUST_EXPIRED)
/* Ignore expired bindings in the trust calculation. */
;
else if (tl > trust_level)
{
/* The expected values: */
log_assert (tl == TRUST_UNKNOWN || tl == TRUST_UNDEFINED
|| tl == TRUST_MARGINAL || tl == TRUST_FULLY
|| tl == TRUST_ULTIMATE);
/* We assume the following ordering: */
log_assert (TRUST_UNKNOWN < TRUST_UNDEFINED);
log_assert (TRUST_UNDEFINED < TRUST_MARGINAL);
log_assert (TRUST_MARGINAL < TRUST_FULLY);
log_assert (TRUST_FULLY < TRUST_ULTIMATE);
trust_level = tl;
}
xfree (email);
}
if (need_warning && ! had_conflict)
show_warning (fingerprint, user_id_list);
die:
tofu_end_batch_update (ctrl);
xfree (fingerprint);
if (bindings_valid == 0)
{
if (DBG_TRUST)
log_debug ("no (of %d) valid bindings."
" Can't get TOFU validity for this set of user ids.\n",
bindings);
return TRUST_NEVER;
}
return trust_level;
}
/* Set the policy for all non-revoked user ids in the keyblock KB to
POLICY.
If no key is available with the specified key id, then this
function returns GPG_ERR_NO_PUBKEY.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_set_policy (ctrl_t ctrl, kbnode_t kb, enum tofu_policy policy)
{
gpg_error_t err = 0;
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
PKT_public_key *pk;
char *fingerprint = NULL;
log_assert (kb->pkt->pkttype == PKT_PUBLIC_KEY);
pk = kb->pkt->pkt.public_key;
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
if (DBG_TRUST)
log_debug ("Setting TOFU policy for %s to %s\n",
keystr (pk->keyid), tofu_policy_str (policy));
if (! pk_is_primary (pk))
log_bug ("%s: Passed a subkey, but expecting a primary key.\n", __func__);
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
return gpg_error_from_syserror ();
begin_transaction (ctrl, 0);
for (; kb; kb = kb->next)
{
PKT_user_id *user_id;
char *email;
if (kb->pkt->pkttype != PKT_USER_ID)
continue;
user_id = kb->pkt->pkt.user_id;
if (user_id->flags.revoked)
/* Skip revoked user ids. (Don't skip expired user ids, the
expiry can be changed.) */
continue;
email = email_from_user_id (user_id->name);
err = record_binding (dbs, fingerprint, email, user_id->name,
policy, TOFU_POLICY_NONE, NULL, 0, 1, now);
if (err)
{
log_error ("error setting policy for key %s, user id \"%s\": %s",
fingerprint, email, gpg_strerror (err));
xfree (email);
break;
}
xfree (email);
}
if (err)
rollback_transaction (ctrl);
else
end_transaction (ctrl, 0);
xfree (fingerprint);
return err;
}
/* Return the TOFU policy for the specified binding in *POLICY. If no
policy has been set for the binding, sets *POLICY to
TOFU_POLICY_NONE.
PK is a primary public key and USER_ID is a user id.
Returns 0 on success and an error code otherwise. */
gpg_error_t
tofu_get_policy (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *user_id,
enum tofu_policy *policy)
{
time_t now = gnupg_get_time ();
tofu_dbs_t dbs;
char *fingerprint;
char *email;
/* Make sure PK is a primary key. */
log_assert (pk_is_primary (pk));
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
return gpg_error_from_syserror ();
email = email_from_user_id (user_id->name);
*policy = get_policy (ctrl, dbs, pk, fingerprint,
user_id->name, email, NULL, now);
xfree (email);
xfree (fingerprint);
if (*policy == _tofu_GET_POLICY_ERROR)
return gpg_error (GPG_ERR_GENERAL);
return 0;
}
gpg_error_t
tofu_notice_key_changed (ctrl_t ctrl, kbnode_t kb)
{
tofu_dbs_t dbs;
PKT_public_key *pk;
char *fingerprint;
char *sqlerr = NULL;
int rc;
/* Make sure PK is a primary key. */
setup_main_keyids (kb);
pk = kb->pkt->pkt.public_key;
log_assert (pk_is_primary (pk));
dbs = opendbs (ctrl);
if (! dbs)
{
log_error (_("error opening TOFU database: %s\n"),
gpg_strerror (GPG_ERR_GENERAL));
return gpg_error (GPG_ERR_GENERAL);
}
fingerprint = hexfingerprint (pk, NULL, 0);
if (!fingerprint)
return gpg_error_from_syserror ();
rc = gpgsql_stepx (dbs->db, NULL, NULL, NULL, &sqlerr,
"update bindings set effective_policy = ?"
" where fingerprint = ?;",
GPGSQL_ARG_INT, (int) TOFU_POLICY_NONE,
GPGSQL_ARG_STRING, fingerprint,
GPGSQL_ARG_END);
xfree (fingerprint);
if (rc == _tofu_GET_POLICY_ERROR)
return gpg_error (GPG_ERR_GENERAL);
return 0;
}
diff --git a/g10/trustdb.c b/g10/trustdb.c
index 6de9f6b66..88b94389f 100644
--- a/g10/trustdb.c
+++ b/g10/trustdb.c
@@ -1,2436 +1,2436 @@
/* trustdb.c
* Copyright (C) 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2005, 2006, 2007,
* 2008, 2012 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "gpg.h"
#include "../common/status.h"
#include "../common/iobuf.h"
#include "../regexp/jimregexp.h"
#include "keydb.h"
#include "../common/util.h"
#include "options.h"
#include "packet.h"
#include "main.h"
#include "../common/mbox-util.h"
#include "../common/i18n.h"
#include "tdbio.h"
#include "trustdb.h"
#include "tofu.h"
#include "key-clean.h"
typedef struct key_item **KeyHashTable; /* see new_key_hash_table() */
/*
* Structure to keep track of keys, this is used as an array where the
* item right after the last one has a keyblock set to NULL. Maybe we
* can drop this thing and replace it by key_item
*/
struct key_array
{
KBNODE keyblock;
};
/* Control information for the trust DB. */
static struct
{
int init;
int level;
char *dbname;
int no_trustdb;
} trustdb_args;
/* Some globals. */
static struct key_item *utk_list; /* all ultimately trusted keys */
/* A list used to temporary store trusted keys and a flag indicated
* whether any --trusted-key option has been seen. */
static struct key_item *trusted_key_list;
static int any_trusted_key_seen;
/* Flag whether a trustdb check is pending. */
static int pending_check_trustdb;
static void write_record (ctrl_t ctrl, TRUSTREC *rec);
static void do_sync (void);
static int validate_keys (ctrl_t ctrl, int interactive);
/**********************************************
************* some helpers *******************
**********************************************/
static u32
keyid_from_fpr20 (ctrl_t ctrl, const byte *fpr, u32 *keyid)
{
u32 dummy_keyid[2];
int fprlen;
if( !keyid )
keyid = dummy_keyid;
/* Problem: We do only use fingerprints in the trustdb but
* we need the keyID here to identify the key; we can only
* use that ugly hack to distinguish between 16 and 20
* bytes fpr - it does not work always so we better change
* the whole validation code to only work with
* fingerprints */
fprlen = (!fpr[16] && !fpr[17] && !fpr[18] && !fpr[19])? 16:20;
if (fprlen != 20)
{
/* This is special as we have to lookup the key first. */
PKT_public_key pk;
int rc;
memset (&pk, 0, sizeof pk);
rc = get_pubkey_byfprint (ctrl, &pk, NULL, fpr, fprlen);
if (rc)
{
log_printhex (fpr, fprlen,
"Oops: keyid_from_fingerprint: no pubkey; fpr:");
keyid[0] = 0;
keyid[1] = 0;
}
else
keyid_from_pk (&pk, keyid);
}
else
{
keyid[0] = buf32_to_u32 (fpr+12);
keyid[1] = buf32_to_u32 (fpr+16);
}
return keyid[1];
}
static struct key_item *
new_key_item (void)
{
struct key_item *k;
k = xmalloc_clear (sizeof *k);
return k;
}
static void
release_key_items (struct key_item *k)
{
struct key_item *k2;
for (; k; k = k2)
{
k2 = k->next;
xfree (k->trust_regexp);
xfree (k);
}
}
#define KEY_HASH_TABLE_SIZE 1024
/*
* For fast keylook up we need a hash table. Each byte of a KeyID
* should be distributed equally over the 256 possible values (except
* for v3 keyIDs but we consider them as not important here). So we
* can just use 10 bits to index a table of KEY_HASH_TABLE_SIZE key items.
* Possible optimization: Do not use key_items but other hash_table when the
* duplicates lists get too large.
*/
static KeyHashTable
new_key_hash_table (void)
{
struct key_item **tbl;
tbl = xmalloc_clear (KEY_HASH_TABLE_SIZE * sizeof *tbl);
return tbl;
}
static void
release_key_hash_table (KeyHashTable tbl)
{
int i;
if (!tbl)
return;
for (i=0; i < KEY_HASH_TABLE_SIZE; i++)
release_key_items (tbl[i]);
xfree (tbl);
}
/*
* Returns: True if the keyID is in the given hash table
*/
static int
test_key_hash_table (KeyHashTable tbl, u32 *kid)
{
struct key_item *k;
for (k = tbl[(kid[1] % KEY_HASH_TABLE_SIZE)]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return 1;
return 0;
}
/*
* Add a new key to the hash table. The key is identified by its key ID.
*/
static void
add_key_hash_table (KeyHashTable tbl, u32 *kid)
{
int i = kid[1] % KEY_HASH_TABLE_SIZE;
struct key_item *k, *kk;
for (k = tbl[i]; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return; /* already in table */
kk = new_key_item ();
kk->kid[0] = kid[0];
kk->kid[1] = kid[1];
kk->next = tbl[i];
tbl[i] = kk;
}
/*
* Release a key_array
*/
static void
release_key_array ( struct key_array *keys )
{
struct key_array *k;
if (keys) {
for (k=keys; k->keyblock; k++)
release_kbnode (k->keyblock);
xfree (keys);
}
}
/*********************************************
********** Initialization *****************
*********************************************/
/*
* Used to register extra ultimately trusted keys - this has to be done
* before initializing the validation module.
* FIXME: Should be replaced by a function to add those keys to the trustdb.
*/
static void
tdb_register_trusted_keyid (u32 *keyid)
{
struct key_item *k;
k = new_key_item ();
k->kid[0] = keyid[0];
k->kid[1] = keyid[1];
k->next = trusted_key_list;
trusted_key_list = k;
}
/* This is called for the option --trusted-key to register these keys
* for later syncing them into the trustdb. The special value "none"
* may be used to indicate that there is a trusted-key option but no
* key shall be inserted for it. This "none" value is helpful to
* distinguish between changing the gpg.conf from a trusted-key to no
* trusted-key options at all. Simply not specify the option would
* not allow to distinguish this case from the --no-options case as
* used for certain calls of gpg for example by gpg-wks-client. */
void
tdb_register_trusted_key (const char *string)
{
gpg_error_t err;
KEYDB_SEARCH_DESC desc;
u32 kid[2];
any_trusted_key_seen = 1;
if (!strcmp (string, "none"))
return;
err = classify_user_id (string, &desc, 1);
if (!err)
{
if (desc.mode == KEYDB_SEARCH_MODE_LONG_KID)
{
tdb_register_trusted_keyid (desc.u.kid);
return;
}
if (desc.mode == KEYDB_SEARCH_MODE_FPR && desc.fprlen == 20)
{
kid[0] = buf32_to_u32 (desc.u.fpr+12);
kid[1] = buf32_to_u32 (desc.u.fpr+16);
tdb_register_trusted_keyid (kid);
return;
}
if (desc.mode == KEYDB_SEARCH_MODE_FPR && desc.fprlen == 32)
{
kid[0] = buf32_to_u32 (desc.u.fpr);
kid[1] = buf32_to_u32 (desc.u.fpr+4);
tdb_register_trusted_keyid (kid);
return;
}
}
log_error (_("'%s' is not a valid long keyID\n"), string );
}
/*
* Helper to add a key to the global list of ultimately trusted keys.
* Returns: true = inserted, false = already in list.
*/
static int
add_utk (u32 *kid)
{
struct key_item *k;
if (tdb_keyid_is_utk (kid))
return 0;
k = new_key_item ();
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->ownertrust = TRUST_ULTIMATE;
k->next = utk_list;
utk_list = k;
if( opt.verbose > 1 )
log_info(_("key %s: accepted as trusted key\n"), keystr(kid));
return 1;
}
/* Add/remove KID to/from the list of ultimately trusted keys. */
void
tdb_update_utk (u32 *kid, int add)
{
struct key_item *k, *k_prev;
k_prev = NULL;
for (k = utk_list; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
break;
else
k_prev = k;
if (add)
{
if (!k)
{
k = new_key_item ();
k->kid[0] = kid[0];
k->kid[1] = kid[1];
k->ownertrust = TRUST_ULTIMATE;
k->next = utk_list;
utk_list = k;
if ( opt.verbose > 1 )
log_info(_("key %s: accepted as trusted key\n"), keystr(kid));
}
}
else
{
if (k)
{
if (k_prev)
k_prev->next = k->next;
else
utk_list = NULL;
xfree (k->trust_regexp);
xfree (k);
}
}
}
/****************
* Verify that all our secret keys are usable and put them into the utk_list.
*/
static void
verify_own_keys (ctrl_t ctrl)
{
TRUSTREC rec;
ulong recnum;
int rc;
struct key_item *k, *k2;
int need_revalidation = 0;
if (utk_list)
return; /* Has already been run. */
/* scan the trustdb to find all ultimately trusted keys */
for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ )
{
if (rec.rectype == RECTYPE_TRUST
&& (rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE)
{
u32 kid[2];
keyid_from_fpr20 (ctrl, rec.r.trust.fingerprint, kid);
if (!add_utk (kid))
log_info (_("key %s occurs more than once in the trustdb\n"),
keystr(kid));
else if ((rec.r.trust.flags & 1)
&& any_trusted_key_seen)
{
/* Record marked as inserted via --trusted-key. Is this
* still the case? */
for (k2 = trusted_key_list; k2; k2 = k2->next)
if (k2->kid[0] == kid[0] && k2->kid[1] == kid[1])
break;
if (!k2) /* No - clear the flag. */
{
if (DBG_TRUST)
log_debug ("clearing former --trusted-key %s\n",
keystr (kid));
rec.r.trust.ownertrust = TRUST_UNKNOWN;
rec.r.trust.flags &= ~(rec.r.trust.flags & 1);
write_record (ctrl, &rec);
need_revalidation = 1;
}
}
}
}
if (need_revalidation)
{
tdb_revalidation_mark (ctrl);
do_sync ();
}
/* Put any --trusted-key keys into the trustdb */
for (k = trusted_key_list; k; k = k->next)
{
if ( add_utk (k->kid) )
{ /* not yet in trustDB as ultimately trusted */
PKT_public_key pk;
memset (&pk, 0, sizeof pk);
rc = get_pubkey_with_ldap_fallback (ctrl, &pk, k->kid);
if (rc)
log_info(_("key %s: no public key for trusted key - skipped\n"),
keystr(k->kid));
else
{
tdb_update_ownertrust
(ctrl, &pk, ((tdb_get_ownertrust (ctrl, &pk, 0) & ~TRUST_MASK)
| TRUST_ULTIMATE ), 1);
release_public_key_parts (&pk);
}
if (!opt.quiet)
log_info (_("key %s marked as ultimately trusted\n"),
keystr(k->kid));
}
}
/* Release the helper table. */
release_key_items (trusted_key_list);
trusted_key_list = NULL;
return;
}
/* Returns whether KID is on the list of ultimately trusted keys. */
int
tdb_keyid_is_utk (u32 *kid)
{
struct key_item *k;
for (k = utk_list; k; k = k->next)
if (k->kid[0] == kid[0] && k->kid[1] == kid[1])
return 1;
return 0;
}
/* Return the list of ultimately trusted keys. */
struct key_item *
tdb_utks (void)
{
return utk_list;
}
/*********************************************
*********** TrustDB stuff *******************
*********************************************/
/*
* Read a record but die if it does not exist
*/
static void
read_record (ulong recno, TRUSTREC *rec, int rectype )
{
int rc = tdbio_read_record (recno, rec, rectype);
if (rc)
{
log_error(_("trust record %lu, req type %d: read failed: %s\n"),
recno, rec->rectype, gpg_strerror (rc) );
tdbio_invalid();
}
if (rectype != rec->rectype)
{
log_error(_("trust record %lu is not of requested type %d\n"),
rec->recnum, rectype);
tdbio_invalid();
}
}
/*
* Write a record and die on error
*/
static void
write_record (ctrl_t ctrl, TRUSTREC *rec)
{
int rc = tdbio_write_record (ctrl, rec);
if (rc)
{
log_error(_("trust record %lu, type %d: write failed: %s\n"),
rec->recnum, rec->rectype, gpg_strerror (rc) );
tdbio_invalid();
}
}
/*
* sync the TrustDb and die on error
*/
static void
do_sync(void)
{
int rc = tdbio_sync ();
if(rc)
{
log_error (_("trustdb: sync failed: %s\n"), gpg_strerror (rc) );
g10_exit(2);
}
}
const char *
trust_model_string (int model)
{
switch (model)
{
case TM_CLASSIC: return "classic";
case TM_PGP: return "pgp";
case TM_EXTERNAL: return "external";
case TM_TOFU: return "tofu";
case TM_TOFU_PGP: return "tofu+pgp";
case TM_ALWAYS: return "always";
case TM_DIRECT: return "direct";
default: return "unknown";
}
}
/****************
* Perform some checks over the trustdb
* level 0: only open the db
* 1: used for initial program startup
*/
int
setup_trustdb( int level, const char *dbname )
{
/* just store the args */
if( trustdb_args.init )
return 0;
trustdb_args.level = level;
trustdb_args.dbname = dbname? xstrdup(dbname): NULL;
return 0;
}
void
how_to_fix_the_trustdb (void)
{
const char *name = trustdb_args.dbname;
if (!name)
name = "trustdb.gpg";
log_info (_("You may try to re-create the trustdb using the commands:\n"));
log_info (" cd %s\n", gnupg_homedir ());
log_info (" %s --export-ownertrust > otrust.tmp\n", GPG_NAME);
#ifdef HAVE_W32_SYSTEM
log_info (" del %s\n", name);
#else
log_info (" rm %s\n", name);
#endif
log_info (" %s --import-ownertrust < otrust.tmp\n", GPG_NAME);
log_info (_("If that does not work, please consult the manual\n"));
}
/* Initialize the trustdb. With NO_CREATE set a missing trustdb is
* not an error and the function won't terminate the process on error;
* in that case 0 is returned if there is a trustdb or an error code
* if no trustdb is available. */
gpg_error_t
init_trustdb (ctrl_t ctrl, int no_create)
{
int level = trustdb_args.level;
const char* dbname = trustdb_args.dbname;
if( trustdb_args.init )
return 0;
trustdb_args.init = 1;
if(level==0 || level==1)
{
int rc = tdbio_set_dbname (ctrl, dbname, (!no_create && level),
&trustdb_args.no_trustdb);
if (no_create && trustdb_args.no_trustdb)
{
/* No trustdb found and the caller asked us not to create
* it. Return an error and set the initialization state
* back so that we always test for an existing trustdb. */
trustdb_args.init = 0;
return gpg_error (GPG_ERR_ENOENT);
}
if (rc)
log_fatal("can't init trustdb: %s\n", gpg_strerror (rc) );
}
else
BUG();
if(opt.trust_model==TM_AUTO)
{
/* Try and set the trust model off of whatever the trustdb says
it is. */
opt.trust_model=tdbio_read_model();
/* Sanity check this ;) */
if(opt.trust_model != TM_CLASSIC
&& opt.trust_model != TM_PGP
&& opt.trust_model != TM_TOFU_PGP
&& opt.trust_model != TM_TOFU
&& opt.trust_model != TM_EXTERNAL)
{
log_info(_("unable to use unknown trust model (%d) - "
"assuming %s trust model\n"),opt.trust_model,"pgp");
opt.trust_model = TM_PGP;
}
if(opt.verbose)
log_info(_("using %s trust model\n"),
trust_model_string (opt.trust_model));
}
if (opt.trust_model==TM_PGP || opt.trust_model==TM_CLASSIC
|| opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
/* Verify the list of ultimately trusted keys and move the
--trusted-keys list there as well. */
if(level==1)
verify_own_keys (ctrl);
if(!tdbio_db_matches_options())
pending_check_trustdb=1;
}
return 0;
}
/* Check whether we have a trust database, initializing it if
necessary if the trust model is not 'always trust'. Returns true
if we do have a usable trust database. */
int
have_trustdb (ctrl_t ctrl)
{
return !init_trustdb (ctrl, opt.trust_model == TM_ALWAYS);
}
/****************
* Recreate the WoT but do not ask for new ownertrusts. Special
* feature: In batch mode and without a forced yes, this is only done
* when a check is due. This can be used to run the check from a crontab
*/
void
check_trustdb (ctrl_t ctrl)
{
init_trustdb (ctrl, 0);
if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)
{
if (opt.batch && !opt.answer_yes)
{
ulong scheduled;
scheduled = tdbio_read_nextcheck ();
if (!scheduled)
{
log_info (_("no need for a trustdb check\n"));
return;
}
if (scheduled > make_timestamp ())
{
log_info (_("next trustdb check due at %s\n"),
strtimestamp (scheduled));
return;
}
}
validate_keys (ctrl, 0);
}
else
log_info (_("no need for a trustdb check with '%s' trust model\n"),
trust_model_string(opt.trust_model));
}
/*
* Recreate the WoT.
*/
void
update_trustdb (ctrl_t ctrl)
{
init_trustdb (ctrl, 0);
if (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU)
validate_keys (ctrl, 1);
else
log_info (_("no need for a trustdb update with '%s' trust model\n"),
trust_model_string(opt.trust_model));
}
void
tdb_revalidation_mark (ctrl_t ctrl)
{
init_trustdb (ctrl, 0);
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return;
/* We simply set the time for the next check to 1 (far back in 1970)
so that a --update-trustdb will be scheduled. */
if (tdbio_write_nextcheck (ctrl, 1))
do_sync ();
pending_check_trustdb = 1;
}
int
trustdb_pending_check(void)
{
return pending_check_trustdb;
}
/* If the trustdb is dirty, and we're interactive, update it.
Otherwise, check it unless no-auto-check-trustdb is set. */
void
tdb_check_or_update (ctrl_t ctrl)
{
if (trustdb_pending_check ())
{
if (opt.interactive)
update_trustdb (ctrl);
else if (!opt.no_auto_check_trustdb)
check_trustdb (ctrl);
}
}
void
read_trust_options (ctrl_t ctrl,
byte *trust_model, ulong *created, ulong *nextcheck,
byte *marginals, byte *completes, byte *cert_depth,
byte *min_cert_level)
{
TRUSTREC opts;
init_trustdb (ctrl, 0);
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
memset (&opts, 0, sizeof opts);
else
read_record (0, &opts, RECTYPE_VER);
if(trust_model)
*trust_model=opts.r.ver.trust_model;
if(created)
*created=opts.r.ver.created;
if(nextcheck)
*nextcheck=opts.r.ver.nextcheck;
if(marginals)
*marginals=opts.r.ver.marginals;
if(completes)
*completes=opts.r.ver.completes;
if(cert_depth)
*cert_depth=opts.r.ver.cert_depth;
if(min_cert_level)
*min_cert_level=opts.r.ver.min_cert_level;
}
/***********************************************
*********** Ownertrust et al. ****************
***********************************************/
static int
read_trust_record (ctrl_t ctrl, PKT_public_key *pk, TRUSTREC *rec)
{
int rc;
init_trustdb (ctrl, 0);
rc = tdbio_search_trust_bypk (ctrl, pk, rec);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("trustdb: searching trust record failed: %s\n",
gpg_strerror (rc));
return rc;
}
if (rec->rectype != RECTYPE_TRUST)
{
log_error ("trustdb: record %lu is not a trust record\n",
rec->recnum);
return GPG_ERR_TRUSTDB;
}
return 0;
}
/*
* Return the assigned ownertrust value for the given public key. The
* key should be the primary key. If NO_CREATE is set a missing
* trustdb will not be created. This comes for example handy when we
* want to print status lines (DECRYPTION_KEY) which carry ownertrust
* values but we usually use --always-trust.
*/
unsigned int
tdb_get_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create)
{
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return TRUST_UNKNOWN;
/* If the caller asked not to create a trustdb we call init_trustdb
* directly and allow it to fail with an error code for a
* non-existing trustdb. */
if (no_create && init_trustdb (ctrl, 1))
return TRUST_UNKNOWN;
err = read_trust_record (ctrl, pk, &rec);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
return TRUST_UNKNOWN; /* no record yet */
if (err)
{
tdbio_invalid ();
return TRUST_UNKNOWN; /* actually never reached */
}
return rec.r.trust.ownertrust;
}
unsigned int
tdb_get_min_ownertrust (ctrl_t ctrl, PKT_public_key *pk, int no_create)
{
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return TRUST_UNKNOWN;
/* If the caller asked not to create a trustdb we call init_trustdb
* directly and allow it to fail with an error code for a
* non-existing trustdb. */
if (no_create && init_trustdb (ctrl, 1))
return TRUST_UNKNOWN;
err = read_trust_record (ctrl, pk, &rec);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
return TRUST_UNKNOWN; /* no record yet */
if (err)
{
tdbio_invalid ();
return TRUST_UNKNOWN; /* actually never reached */
}
return rec.r.trust.min_ownertrust;
}
/*
* Set the trust value of the given public key to the new value.
* The key should be a primary one.
*/
void
tdb_update_ownertrust (ctrl_t ctrl, PKT_public_key *pk, unsigned int new_trust,
int as_trusted_key)
{
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return;
err = read_trust_record (ctrl, pk, &rec);
if (!err)
{
if (DBG_TRUST)
log_debug ("update ownertrust from %u to %u%s\n",
(unsigned int)rec.r.trust.ownertrust, new_trust,
as_trusted_key? " via --trusted-key":"");
if (rec.r.trust.ownertrust != new_trust)
{
rec.r.trust.ownertrust = new_trust;
/* Clear or set the trusted key flag if the new value is
* ultimate. This is required so that we know which keys
* have been added by --trusted-keys. */
if ((rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE)
{
if (as_trusted_key)
rec.r.trust.flags |= 1;
else
rec.r.trust.flags &= ~(rec.r.trust.flags & 1);
}
else
rec.r.trust.flags &= ~(rec.r.trust.flags & 1);
write_record (ctrl, &rec);
tdb_revalidation_mark (ctrl);
do_sync ();
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{ /* no record yet - create a new one */
if (DBG_TRUST)
log_debug ("insert ownertrust %u%s\n", new_trust,
as_trusted_key? " via --trusted-key":"");
memset (&rec, 0, sizeof rec);
rec.recnum = tdbio_new_recnum (ctrl);
rec.rectype = RECTYPE_TRUST;
fpr20_from_pk (pk, rec.r.trust.fingerprint);
rec.r.trust.ownertrust = new_trust;
if ((rec.r.trust.ownertrust & TRUST_MASK) == TRUST_ULTIMATE
&& as_trusted_key)
rec.r.trust.flags = 1;
write_record (ctrl, &rec);
tdb_revalidation_mark (ctrl);
do_sync ();
}
else
{
tdbio_invalid ();
}
}
static void
update_min_ownertrust (ctrl_t ctrl, u32 *kid, unsigned int new_trust)
{
PKT_public_key *pk;
TRUSTREC rec;
gpg_error_t err;
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return;
pk = xmalloc_clear (sizeof *pk);
err = get_pubkey (ctrl, pk, kid);
if (err)
{
log_error (_("public key %s not found: %s\n"),
keystr (kid), gpg_strerror (err));
xfree (pk);
return;
}
err = read_trust_record (ctrl, pk, &rec);
if (!err)
{
if (DBG_TRUST)
log_debug ("key %08lX%08lX: update min_ownertrust from %u to %u\n",
(ulong)kid[0],(ulong)kid[1],
(unsigned int)rec.r.trust.min_ownertrust,
new_trust );
if (rec.r.trust.min_ownertrust != new_trust)
{
rec.r.trust.min_ownertrust = new_trust;
write_record (ctrl, &rec);
tdb_revalidation_mark (ctrl);
do_sync ();
}
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{ /* no record yet - create a new one */
if (DBG_TRUST)
log_debug ("insert min_ownertrust %u\n", new_trust );
memset (&rec, 0, sizeof rec);
rec.recnum = tdbio_new_recnum (ctrl);
rec.rectype = RECTYPE_TRUST;
fpr20_from_pk (pk, rec.r.trust.fingerprint);
rec.r.trust.min_ownertrust = new_trust;
write_record (ctrl, &rec);
tdb_revalidation_mark (ctrl);
do_sync ();
}
else
{
tdbio_invalid ();
}
free_public_key (pk);
}
/*
* Clear the ownertrust and min_ownertrust values.
* Also schedule a revalidation if a stale validity record exists.
*
* Return: True if a change actually happened.
*/
int
tdb_clear_ownertrusts (ctrl_t ctrl, PKT_public_key *pk)
{
TRUSTREC rec;
gpg_error_t err;
init_trustdb (ctrl, 0);
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return 0;
err = read_trust_record (ctrl, pk, &rec);
if (!err)
{
if (DBG_TRUST)
{
log_debug ("clearing ownertrust (old value %u)\n",
(unsigned int)rec.r.trust.ownertrust);
log_debug ("clearing min_ownertrust (old value %u)\n",
(unsigned int)rec.r.trust.min_ownertrust);
}
if (rec.r.trust.ownertrust || rec.r.trust.min_ownertrust)
{
rec.r.trust.ownertrust = 0;
rec.r.trust.min_ownertrust = 0;
write_record (ctrl, &rec);
tdb_revalidation_mark (ctrl);
do_sync ();
return 1;
}
else
{
/* Check whether we have a stale RECTYPE_VALID for that key
* and if its validity ist set, schedule a revalidation. */
ulong recno = rec.r.trust.validlist;
while (recno)
{
read_record (recno, &rec, RECTYPE_VALID);
if (rec.r.valid.validity)
break;
recno = rec.r.valid.next;
}
if (recno)
{
if (DBG_TRUST)
log_debug ("stale validity value detected"
" - scheduling check\n");
tdb_revalidation_mark (ctrl);
}
}
}
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
}
return 0;
}
/*
* Note: Caller has to do a sync
*/
static void
update_validity (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid,
int depth, int validity)
{
TRUSTREC trec, vrec;
gpg_error_t err;
ulong recno;
namehash_from_uid(uid);
err = read_trust_record (ctrl, pk, &trec);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
return;
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* No record yet - create a new one. */
memset (&trec, 0, sizeof trec);
trec.recnum = tdbio_new_recnum (ctrl);
trec.rectype = RECTYPE_TRUST;
fpr20_from_pk (pk, trec.r.trust.fingerprint);
trec.r.trust.ownertrust = 0;
}
/* locate an existing one */
recno = trec.r.trust.validlist;
while (recno)
{
read_record (recno, &vrec, RECTYPE_VALID);
if ( !memcmp (vrec.r.valid.namehash, uid->namehash, 20) )
break;
recno = vrec.r.valid.next;
}
if (!recno) /* insert a new validity record */
{
memset (&vrec, 0, sizeof vrec);
vrec.recnum = tdbio_new_recnum (ctrl);
vrec.rectype = RECTYPE_VALID;
memcpy (vrec.r.valid.namehash, uid->namehash, 20);
vrec.r.valid.next = trec.r.trust.validlist;
trec.r.trust.validlist = vrec.recnum;
}
vrec.r.valid.validity = validity;
vrec.r.valid.full_count = uid->help_full_count;
vrec.r.valid.marginal_count = uid->help_marginal_count;
write_record (ctrl, &vrec);
trec.r.trust.depth = depth;
write_record (ctrl, &trec);
}
/***********************************************
********* Query trustdb values **************
***********************************************/
/* Return true if key is disabled. Note that this is usually used via
the pk_is_disabled macro. */
int
tdb_cache_disabled_value (ctrl_t ctrl, PKT_public_key *pk)
{
gpg_error_t err;
TRUSTREC trec;
int disabled = 0;
if (pk->flags.disabled_valid)
return pk->flags.disabled;
init_trustdb (ctrl, 0);
if (trustdb_args.no_trustdb)
return 0; /* No trustdb => not disabled. */
err = read_trust_record (ctrl, pk, &trec);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
goto leave;
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* No record found, so assume not disabled. */
goto leave;
}
if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED))
disabled = 1;
/* Cache it for later so we don't need to look at the trustdb every
time */
pk->flags.disabled = disabled;
pk->flags.disabled_valid = 1;
leave:
return disabled;
}
void
tdb_check_trustdb_stale (ctrl_t ctrl)
{
static int did_nextcheck=0;
init_trustdb (ctrl, 0);
if (trustdb_args.no_trustdb)
return; /* No trustdb => can't be stale. */
if (!did_nextcheck
&& (opt.trust_model == TM_PGP || opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_TOFU_PGP || opt.trust_model == TM_TOFU))
{
ulong scheduled;
did_nextcheck = 1;
scheduled = tdbio_read_nextcheck ();
if ((scheduled && scheduled <= make_timestamp ())
|| pending_check_trustdb)
{
if (opt.no_auto_check_trustdb)
{
pending_check_trustdb = 1;
if (!opt.quiet)
log_info (_("please do a --check-trustdb\n"));
}
else
{
if (!opt.quiet)
log_info (_("checking the trustdb\n"));
validate_keys (ctrl, 0);
}
}
}
}
/*
* Return the validity information for KB/PK (at least one of them
* must be non-NULL). This is the core of get_validity. If SIG is
* not NULL, then the trust is being evaluated in the context of the
* provided signature. This is used by the TOFU code to record
* statistics.
*/
unsigned int
tdb_get_validity_core (ctrl_t ctrl,
kbnode_t kb,
PKT_public_key *pk, PKT_user_id *uid,
PKT_public_key *main_pk,
PKT_signature *sig,
int may_ask)
{
TRUSTREC trec, vrec;
gpg_error_t err = 0;
ulong recno;
#ifdef USE_TOFU
unsigned int tofu_validity = TRUST_UNKNOWN;
int free_kb = 0;
#endif
unsigned int validity = TRUST_UNKNOWN;
if (kb && pk)
log_assert (keyid_cmp (pk_main_keyid (pk),
pk_main_keyid (kb->pkt->pkt.public_key)) == 0);
if (! pk)
{
log_assert (kb);
pk = kb->pkt->pkt.public_key;
}
#ifndef USE_TOFU
(void)sig;
(void)may_ask;
#endif
init_trustdb (ctrl, 0);
/* If we have no trustdb (which also means it has not been created)
and the trust-model is always, we don't know the validity -
return immediately. If we won't do that the tdbio code would try
to open the trustdb and run into a fatal error. */
if (trustdb_args.no_trustdb && opt.trust_model == TM_ALWAYS)
return TRUST_UNKNOWN;
check_trustdb_stale (ctrl);
if(opt.trust_model==TM_DIRECT)
{
/* Note that this happens BEFORE any user ID stuff is checked.
The direct trust model applies to keys as a whole. */
validity = tdb_get_ownertrust (ctrl, main_pk, 0);
goto leave;
}
#ifdef USE_TOFU
if (opt.trust_model == TM_TOFU || opt.trust_model == TM_TOFU_PGP)
{
kbnode_t n = NULL;
strlist_t user_id_list = NULL;
int done = 0;
/* If the caller didn't supply a user id then use all uids. */
if (! uid)
{
if (! kb)
{
kb = get_pubkeyblock (ctrl, main_pk->keyid);
free_kb = 1;
}
n = kb;
}
if (DBG_TRUST && sig && sig->signers_uid)
log_debug ("TOFU: only considering user id: '%s'\n",
sig->signers_uid);
while (!done && (uid || (n = find_next_kbnode (n, PKT_USER_ID))))
{
PKT_user_id *user_id;
int expired = 0;
if (uid)
{
user_id = uid;
/* If the caller specified a user id, then we only
process the specified user id and are done after the
first iteration. */
done = 1;
}
else
user_id = n->pkt->pkt.user_id;
if (user_id->attrib_data)
/* Skip user attributes. */
continue;
if (sig && sig->signers_uid)
/* Make sure the UID matches. */
{
char *email = mailbox_from_userid (user_id->name, 0);
if (!email || !*email || strcmp (sig->signers_uid, email) != 0)
{
if (DBG_TRUST)
log_debug ("TOFU: skipping user id '%s', which does"
" not match the signer's email ('%s')\n",
email, sig->signers_uid);
xfree (email);
continue;
}
xfree (email);
}
/* If the user id is revoked or expired, then skip it. */
if (user_id->flags.revoked || user_id->flags.expired)
{
if (DBG_TRUST)
{
char *s;
if (user_id->flags.revoked && user_id->flags.expired)
s = "revoked and expired";
else if (user_id->flags.revoked)
s = "revoked";
else
s = "expire";
log_debug ("TOFU: Ignoring %s user id (%s)\n",
s, user_id->name);
}
if (user_id->flags.revoked)
continue;
expired = 1;
}
add_to_strlist (&user_id_list, user_id->name);
user_id_list->flags = expired;
}
/* Process the user ids in the order they appear in the key
block. */
strlist_rev (&user_id_list);
/* It only makes sense to observe any signature before getting
the validity. This is because if the current signature
results in a conflict, then we damn well want to take that
into account. */
if (sig)
{
err = tofu_register_signature (ctrl, main_pk, user_id_list,
sig->digest, sig->digest_len,
sig->timestamp, "unknown");
if (err)
{
log_error ("TOFU: error registering signature: %s\n",
gpg_strerror (err));
tofu_validity = TRUST_UNKNOWN;
}
}
if (! err)
tofu_validity = tofu_get_validity (ctrl, main_pk, user_id_list,
may_ask);
free_strlist (user_id_list);
if (free_kb)
release_kbnode (kb);
}
#endif /*USE_TOFU*/
if (opt.trust_model == TM_TOFU_PGP
|| opt.trust_model == TM_CLASSIC
|| opt.trust_model == TM_PGP)
{
err = read_trust_record (ctrl, main_pk, &trec);
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
tdbio_invalid ();
return 0;
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* No record found. */
validity = TRUST_UNKNOWN;
goto leave;
}
/* Loop over all user IDs */
recno = trec.r.trust.validlist;
validity = 0;
while (recno)
{
read_record (recno, &vrec, RECTYPE_VALID);
if(uid)
{
/* If a user ID is given we return the validity for that
user ID ONLY. If the namehash is not found, then
there is no validity at all (i.e. the user ID wasn't
signed). */
if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
{
validity=(vrec.r.valid.validity & TRUST_MASK);
break;
}
}
else
{
/* If no user ID is given, we take the maximum validity
over all user IDs */
if (validity < (vrec.r.valid.validity & TRUST_MASK))
validity = (vrec.r.valid.validity & TRUST_MASK);
}
recno = vrec.r.valid.next;
}
if ((trec.r.trust.ownertrust & TRUST_FLAG_DISABLED))
{
validity |= TRUST_FLAG_DISABLED;
pk->flags.disabled = 1;
}
else
pk->flags.disabled = 0;
pk->flags.disabled_valid = 1;
}
leave:
#ifdef USE_TOFU
validity = tofu_wot_trust_combine (tofu_validity, validity);
#else /*!USE_TOFU*/
validity &= TRUST_MASK;
if (validity == TRUST_NEVER)
/* TRUST_NEVER trumps everything else. */
validity |= TRUST_NEVER;
if (validity == TRUST_EXPIRED)
/* TRUST_EXPIRED trumps everything but TRUST_NEVER. */
validity |= TRUST_EXPIRED;
#endif /*!USE_TOFU*/
if (opt.trust_model != TM_TOFU
&& pending_check_trustdb)
validity |= TRUST_FLAG_PENDING_CHECK;
return validity;
}
static void
get_validity_counts (ctrl_t ctrl, PKT_public_key *pk, PKT_user_id *uid)
{
TRUSTREC trec, vrec;
ulong recno;
if(pk==NULL || uid==NULL)
BUG();
namehash_from_uid(uid);
uid->help_marginal_count=uid->help_full_count=0;
init_trustdb (ctrl, 0);
if(read_trust_record (ctrl, pk, &trec))
return;
/* loop over all user IDs */
recno = trec.r.trust.validlist;
while (recno)
{
read_record (recno, &vrec, RECTYPE_VALID);
if(memcmp(vrec.r.valid.namehash,uid->namehash,20)==0)
{
uid->help_marginal_count=vrec.r.valid.marginal_count;
uid->help_full_count=vrec.r.valid.full_count;
/* es_printf("Fetched marginal %d, full %d\n",uid->help_marginal_count,uid->help_full_count); */
break;
}
recno = vrec.r.valid.next;
}
}
void
list_trust_path( const char *username )
{
(void)username;
}
/****************
* Enumerate all keys, which are needed to build all trust paths for
* the given key. This function does not return the key itself or
- * the ultimate key (the last point in cerificate chain). Only
+ * the ultimate key (the last point in certificate chain). Only
* certificate chains which ends up at an ultimately trusted key
* are listed. If ownertrust or validity is not NULL, the corresponding
* value for the returned LID is also returned in these variable(s).
*
* 1) create a void pointer and initialize it to NULL
* 2) pass this void pointer by reference to this function.
* Set lid to the key you want to enumerate and pass it by reference.
* 3) call this function as long as it does not return -1
* to indicate EOF. LID does contain the next key used to build the web
* 4) Always call this function a last time with LID set to NULL,
* so that it can free its context.
*
* Returns: -1 on EOF or the level of the returned LID
*/
int
enum_cert_paths( void **context, ulong *lid,
unsigned *ownertrust, unsigned *validity )
{
(void)context;
(void)lid;
(void)ownertrust;
(void)validity;
return -1;
}
/****************
* Print the current path
*/
void
enum_cert_paths_print (void **context, FILE *fp,
int refresh, ulong selected_lid)
{
(void)context;
(void)fp;
(void)refresh;
(void)selected_lid;
}
/****************************************
*********** NEW NEW NEW ****************
****************************************/
static int
ask_ownertrust (ctrl_t ctrl, u32 *kid, int minimum)
{
PKT_public_key *pk;
int rc;
int ot;
pk = xmalloc_clear (sizeof *pk);
rc = get_pubkey (ctrl, pk, kid);
if (rc)
{
log_error (_("public key %s not found: %s\n"),
keystr(kid), gpg_strerror (rc) );
free_public_key (pk);
return TRUST_UNKNOWN;
}
if(opt.force_ownertrust)
{
log_info("force trust for key %s to %s\n",
keystr(kid),trust_value_to_string(opt.force_ownertrust));
tdb_update_ownertrust (ctrl, pk, opt.force_ownertrust, 0);
ot=opt.force_ownertrust;
}
else
{
ot=edit_ownertrust (ctrl, pk, 0);
if(ot>0)
ot = tdb_get_ownertrust (ctrl, pk, 0);
else if(ot==0)
ot = minimum?minimum:TRUST_UNDEFINED;
else
ot = -1; /* quit */
}
free_public_key( pk );
return ot;
}
static void
mark_keyblock_seen (KeyHashTable tbl, KBNODE node)
{
for ( ;node; node = node->next )
if (node->pkt->pkttype == PKT_PUBLIC_KEY
|| node->pkt->pkttype == PKT_PUBLIC_SUBKEY)
{
u32 aki[2];
keyid_from_pk (node->pkt->pkt.public_key, aki);
add_key_hash_table (tbl, aki);
}
}
static void
dump_key_array (int depth, struct key_array *keys)
{
struct key_array *kar;
for (kar=keys; kar->keyblock; kar++)
{
KBNODE node = kar->keyblock;
u32 kid[2];
keyid_from_pk(node->pkt->pkt.public_key, kid);
es_printf ("%d:%08lX%08lX:K::%c::::\n",
depth, (ulong)kid[0], (ulong)kid[1], '?');
for (; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
int len = node->pkt->pkt.user_id->len;
if (len > 30)
len = 30;
es_printf ("%d:%08lX%08lX:U:::%c:::",
depth, (ulong)kid[0], (ulong)kid[1],
(node->flag & 4)? 'f':
(node->flag & 2)? 'm':
(node->flag & 1)? 'q':'-');
es_write_sanitized (es_stdout, node->pkt->pkt.user_id->name,
len, ":", NULL);
es_putc (':', es_stdout);
es_putc ('\n', es_stdout);
}
}
}
}
static void
store_validation_status (ctrl_t ctrl, int depth,
kbnode_t keyblock, KeyHashTable stored)
{
KBNODE node;
int status;
int any = 0;
for (node=keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
{
PKT_user_id *uid = node->pkt->pkt.user_id;
if (node->flag & 4)
status = TRUST_FULLY;
else if (node->flag & 2)
status = TRUST_MARGINAL;
else if (node->flag & 1)
status = TRUST_UNDEFINED;
else
status = 0;
if (status)
{
update_validity (ctrl, keyblock->pkt->pkt.public_key,
uid, depth, status);
mark_keyblock_seen(stored,keyblock);
any = 1;
}
}
}
if (any)
do_sync ();
}
/* Returns a sanitized copy of the regexp (which might be "", but not
NULL). */
/* Operator characters except '.' and backslash.
See regex(7) on BSD. */
#define REGEXP_OPERATOR_CHARS "^[$()|*+?{"
static char *
sanitize_regexp(const char *old)
{
size_t start=0,len=strlen(old),idx=0;
int escaped=0,standard_bracket=0;
char *new=xmalloc((len*2)+1); /* enough to \-escape everything if we
have to */
/* There are basically two commonly-used regexps here. GPG and most
versions of PGP use "<[^>]+[@.]example\.com>$" and PGP (9)
command line uses "example.com" (i.e. whatever the user specifies,
and we can't expect users know to use "\." instead of "."). So
here are the rules: we're allowed to start with "<[^>]+[@.]" and
end with ">$" or start and end with nothing. In between, the
only legal regex character is ".", and everything else gets
escaped. Part of the gotcha here is that some regex packages
allow more than RFC-4880 requires. For example, 4880 has no "{}"
operator, but GNU regex does. Commenting removes these operators
from consideration. A possible future enhancement is to use
commenting to effectively back off a given regex to the Henry
Spencer syntax in 4880. -dshaw */
/* Are we bracketed between "<[^>]+[@.]" and ">$" ? */
if(len>=12 && strncmp(old,"<[^>]+[@.]",10)==0
&& old[len-2]=='>' && old[len-1]=='$')
{
strcpy(new,"<[^>]+[@.]");
idx=strlen(new);
standard_bracket=1;
start+=10;
len-=2;
}
/* Walk the remaining characters and ensure that everything that is
left is not an operational regex character. */
for(;start<len;start++)
{
if(!escaped && old[start]=='\\')
escaped=1;
else if (!escaped && strchr (REGEXP_OPERATOR_CHARS, old[start]))
new[idx++]='\\';
else
escaped=0;
new[idx++]=old[start];
}
new[idx]='\0';
/* Note that the (sub)string we look at might end with a bare "\".
If it does, leave it that way. If the regexp actually ended with
">$", then it was escaping the ">" and is fine. If the regexp
actually ended with the bare "\", then it's an illegal regexp and
regcomp should kick it out. */
if(standard_bracket)
strcat(new,">$");
return new;
}
/* Used by validate_one_keyblock to confirm a regexp within a trust
* signature. Returns 1 for match, and 0 for no match or regex
* error. */
static int
check_regexp (const char *expr,const char *string)
{
int ret;
char *regexp;
char *stringbuf = NULL;
regex_t pat;
regexp = sanitize_regexp (expr);
ret = regcomp (&pat, regexp, (REG_ICASE|REG_EXTENDED));
if (!ret)
{
if (*regexp == '<' && !strchr (string, '<')
&& is_valid_mailbox (string))
{
/* The R.E. starts with an angle bracket but STRING seems to
* be a plain mailbox (e.g. "foo@example.org"). The
* commonly used R.E. pattern "<[^>]+[@.]example\.org>$"
* won't be able to detect this. Thus we enclose STRING
* into angle brackets for checking. */
stringbuf = xstrconcat ("<", string, ">", NULL);
string = stringbuf;
}
ret = regexec (&pat, string, 0, NULL, 0);
regfree (&pat);
}
ret = !ret;
if (DBG_TRUST)
log_debug ("regexp '%s' ('%s') on '%s'%s: %s\n",
regexp, expr, string, stringbuf? " (fixed)":"", ret? "YES":"NO");
xfree (regexp);
xfree (stringbuf);
return ret;
}
/*
* Return true if the key is signed by one of the keys in the given
* key ID list. User IDs with a valid signature are marked by node
* flags as follows:
* flag bit 0: There is at least one signature
* 1: There is marginal confidence that this is a legitimate uid
* 2: There is full confidence that this is a legitimate uid.
* 8: Used for internal purposes.
* 9: Ditto (in mark_usable_uid_certs())
* 10: Ditto (ditto)
* This function assumes that all kbnode flags are cleared on entry.
*/
static int
validate_one_keyblock (ctrl_t ctrl, kbnode_t kb, struct key_item *klist,
u32 curtime, u32 *next_expire)
{
struct key_item *kr;
KBNODE node, uidnode=NULL;
PKT_user_id *uid=NULL;
PKT_public_key *pk = kb->pkt->pkt.public_key;
u32 main_kid[2];
int issigned=0, any_signed = 0;
keyid_from_pk(pk, main_kid);
for (node=kb; node; node = node->next)
{
/* A bit of discussion here: is it better for the web of trust
to be built among only self-signed uids? On the one hand, a
self-signed uid is a statement that the key owner definitely
intended that uid to be there, but on the other hand, a
signed (but not self-signed) uid does carry trust, of a sort,
even if it is a statement being made by people other than the
key owner "through" the uids on the key owner's key. I'm
going with the latter. However, if the user ID was
explicitly revoked, or passively allowed to expire, that
should stop validity through the user ID until it is
resigned. -dshaw */
if (node->pkt->pkttype == PKT_USER_ID
&& !node->pkt->pkt.user_id->flags.revoked
&& !node->pkt->pkt.user_id->flags.expired)
{
if (uidnode && issigned)
{
if (uid->help_full_count >= opt.completes_needed
|| uid->help_marginal_count >= opt.marginals_needed )
uidnode->flag |= 4;
else if (uid->help_full_count || uid->help_marginal_count)
uidnode->flag |= 2;
uidnode->flag |= 1;
any_signed = 1;
}
uidnode = node;
uid=uidnode->pkt->pkt.user_id;
/* If the selfsig is going to expire... */
if(uid->expiredate && uid->expiredate<*next_expire)
*next_expire = uid->expiredate;
issigned = 0;
get_validity_counts (ctrl, pk, uid);
mark_usable_uid_certs (ctrl, kb, uidnode, main_kid, klist,
curtime, next_expire);
}
else if (node->pkt->pkttype == PKT_SIGNATURE
&& (node->flag & (1<<8)) && uid)
{
/* Note that we are only seeing unrevoked sigs here */
PKT_signature *sig = node->pkt->pkt.signature;
kr = is_in_klist (klist, sig);
/* If the trust_regexp does not match, it's as if the sig
did not exist. This is safe for non-trust sigs as well
since we don't accept a regexp on the sig unless it's a
trust sig. */
if (kr && (!kr->trust_regexp
|| !(opt.trust_model == TM_PGP
|| opt.trust_model == TM_TOFU_PGP)
|| (uidnode
&& check_regexp(kr->trust_regexp,
uidnode->pkt->pkt.user_id->name))))
{
/* Are we part of a trust sig chain? We always favor
the latest trust sig, rather than the greater or
lesser trust sig or value. I could make a decent
argument for any of these cases, but this seems to be
what PGP does, and I'd like to be compatible. -dms */
if ((opt.trust_model == TM_PGP
|| opt.trust_model == TM_TOFU_PGP)
&& sig->trust_depth
&& pk->trust_timestamp <= sig->timestamp)
{
unsigned char depth;
/* If the depth on the signature is less than the
chain currently has, then use the signature depth
so we don't increase the depth beyond what the
signer wanted. If the depth on the signature is
more than the chain currently has, then use the
chain depth so we use as much of the signature
depth as the chain will permit. An ultimately
trusted signature can restart the depth to
whatever level it likes. */
if (sig->trust_depth < kr->trust_depth
|| kr->ownertrust == TRUST_ULTIMATE)
depth = sig->trust_depth;
else
depth = kr->trust_depth;
if (depth)
{
if(DBG_TRUST)
log_debug ("trust sig on %s, sig depth is %d,"
" kr depth is %d\n",
uidnode->pkt->pkt.user_id->name,
sig->trust_depth,
kr->trust_depth);
/* If we got here, we know that:
this is a trust sig.
it's a newer trust sig than any previous trust
sig on this key (not uid).
it is legal in that it was either generated by an
ultimate key, or a key that was part of a trust
chain, and the depth does not violate the
original trust sig.
if there is a regexp attached, it matched
successfully.
*/
if (DBG_TRUST)
log_debug ("replacing trust value %d with %d and "
"depth %d with %d\n",
pk->trust_value,sig->trust_value,
pk->trust_depth,depth);
pk->trust_value = sig->trust_value;
pk->trust_depth = depth-1;
/* If the trust sig contains a regexp, record it
on the pk for the next round. */
if (sig->trust_regexp)
pk->trust_regexp = sig->trust_regexp;
}
}
if (kr->ownertrust == TRUST_ULTIMATE)
uid->help_full_count = opt.completes_needed;
else if (kr->ownertrust == TRUST_FULLY)
uid->help_full_count++;
else if (kr->ownertrust == TRUST_MARGINAL)
uid->help_marginal_count++;
issigned = 1;
}
}
}
if (uidnode && issigned)
{
if (uid->help_full_count >= opt.completes_needed
|| uid->help_marginal_count >= opt.marginals_needed )
uidnode->flag |= 4;
else if (uid->help_full_count || uid->help_marginal_count)
uidnode->flag |= 2;
uidnode->flag |= 1;
any_signed = 1;
}
return any_signed;
}
static int
search_skipfnc (void *opaque, u32 *kid, int dummy_uid_no)
{
(void)dummy_uid_no;
return test_key_hash_table ((KeyHashTable)opaque, kid);
}
/*
* Scan all keys and return a key_array of all suitable keys from
* klist. The caller has to pass keydb handle so that we don't use
* to create our own. Returns either a key_array or NULL in case of
* an error. No results found are indicated by an empty array.
* Caller hast to release the returned array.
*/
static struct key_array *
validate_key_list (ctrl_t ctrl, KEYDB_HANDLE hd, KeyHashTable full_trust,
struct key_item *klist, u32 curtime, u32 *next_expire)
{
KBNODE keyblock = NULL;
struct key_array *keys = NULL;
size_t nkeys, maxkeys;
int rc;
KEYDB_SEARCH_DESC desc;
maxkeys = 1000;
keys = xmalloc ((maxkeys+1) * sizeof *keys);
nkeys = 0;
rc = keydb_search_reset (hd);
if (rc)
{
log_error ("keydb_search_reset failed: %s\n", gpg_strerror (rc));
xfree (keys);
return NULL;
}
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
desc.skipfnc = search_skipfnc;
desc.skipfncvalue = full_trust;
rc = keydb_search (hd, &desc, 1, NULL);
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
keys[nkeys].keyblock = NULL;
return keys;
}
if (rc)
{
log_error ("keydb_search(first) failed: %s\n", gpg_strerror (rc));
goto die;
}
desc.mode = KEYDB_SEARCH_MODE_NEXT; /* change mode */
do
{
PKT_public_key *pk;
rc = keydb_get_keyblock (hd, &keyblock);
if (rc)
{
log_error ("keydb_get_keyblock failed: %s\n", gpg_strerror (rc));
goto die;
}
if ( keyblock->pkt->pkttype != PKT_PUBLIC_KEY)
{
log_debug ("ooops: invalid pkttype %d encountered\n",
keyblock->pkt->pkttype);
dump_kbnode (keyblock);
release_kbnode(keyblock);
continue;
}
/* prepare the keyblock for further processing */
merge_keys_and_selfsig (ctrl, keyblock);
clear_kbnode_flags (keyblock);
pk = keyblock->pkt->pkt.public_key;
if (pk->has_expired || pk->flags.revoked)
{
/* it does not make sense to look further at those keys */
mark_keyblock_seen (full_trust, keyblock);
}
else if (validate_one_keyblock (ctrl, keyblock, klist,
curtime, next_expire))
{
KBNODE node;
if (pk->expiredate && pk->expiredate >= curtime
&& pk->expiredate < *next_expire)
*next_expire = pk->expiredate;
if (nkeys == maxkeys) {
maxkeys += 1000;
keys = xrealloc (keys, (maxkeys+1) * sizeof *keys);
}
keys[nkeys++].keyblock = keyblock;
/* Optimization - if all uids are fully trusted, then we
never need to consider this key as a candidate again. */
for (node=keyblock; node; node = node->next)
if (node->pkt->pkttype == PKT_USER_ID && !(node->flag & 4))
break;
if(node==NULL)
mark_keyblock_seen (full_trust, keyblock);
keyblock = NULL;
}
release_kbnode (keyblock);
keyblock = NULL;
}
while (!(rc = keydb_search (hd, &desc, 1, NULL)));
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
{
log_error ("keydb_search_next failed: %s\n", gpg_strerror (rc));
goto die;
}
keys[nkeys].keyblock = NULL;
return keys;
die:
keys[nkeys].keyblock = NULL;
release_key_array (keys);
return NULL;
}
/* Caller must sync */
static void
reset_trust_records (ctrl_t ctrl)
{
TRUSTREC rec;
ulong recnum;
int count = 0, nreset = 0;
for (recnum=1; !tdbio_read_record (recnum, &rec, 0); recnum++ )
{
if(rec.rectype==RECTYPE_TRUST)
{
count++;
if(rec.r.trust.min_ownertrust)
{
rec.r.trust.min_ownertrust=0;
write_record (ctrl, &rec);
}
}
else if(rec.rectype==RECTYPE_VALID
&& ((rec.r.valid.validity&TRUST_MASK)
|| rec.r.valid.marginal_count
|| rec.r.valid.full_count))
{
rec.r.valid.validity &= ~TRUST_MASK;
rec.r.valid.marginal_count=rec.r.valid.full_count=0;
nreset++;
write_record (ctrl, &rec);
}
}
if (opt.verbose)
{
log_info (ngettext("%d key processed",
"%d keys processed",
count), count);
log_printf (ngettext(" (%d validity count cleared)\n",
" (%d validity counts cleared)\n",
nreset), nreset);
}
}
/*
* Run the key validation procedure.
*
* This works this way:
* Step 1: Find all ultimately trusted keys (UTK).
* mark them all as seen and put them into klist.
* Step 2: loop max_cert_times
* Step 3: if OWNERTRUST of any key in klist is undefined
* ask user to assign ownertrust
* Step 4: Loop over all keys in the keyDB which are not marked seen
* Step 5: if key is revoked or expired
* mark key as seen
* continue loop at Step 4
* Step 6: For each user ID of that key signed by a key in klist
* Calculate validity by counting trusted signatures.
* Set validity of user ID
* Step 7: If any signed user ID was found
* mark key as seen
* End Loop
* Step 8: Build a new klist from all fully trusted keys from step 6
* End Loop
* Ready
*
*/
static int
validate_keys (ctrl_t ctrl, int interactive)
{
int rc = 0;
int quit=0;
struct key_item *klist = NULL;
struct key_item *k;
struct key_array *keys = NULL;
struct key_array *kar;
KEYDB_HANDLE kdb = NULL;
KBNODE node;
int depth;
int ot_unknown, ot_undefined, ot_never, ot_marginal, ot_full, ot_ultimate;
KeyHashTable stored,used,full_trust;
u32 start_time, next_expire;
/* Make sure we have all sigs cached. TODO: This is going to
require some architectural re-thinking, as it is agonizingly slow.
Perhaps combine this with reset_trust_records(), or only check
the caches on keys that are actually involved in the web of
trust. */
keydb_rebuild_caches (ctrl, 0);
kdb = keydb_new (ctrl);
if (!kdb)
return gpg_error_from_syserror ();
start_time = make_timestamp ();
next_expire = 0xffffffff; /* set next expire to the year 2106 */
stored = new_key_hash_table ();
used = new_key_hash_table ();
full_trust = new_key_hash_table ();
reset_trust_records (ctrl);
/* Fixme: Instead of always building a UTK list, we could just build it
* here when needed */
if (!utk_list)
{
if (!opt.quiet)
log_info (_("no ultimately trusted keys found\n"));
goto leave;
}
/* mark all UTKs as used and fully_trusted and set validity to
ultimate */
for (k=utk_list; k; k = k->next)
{
KBNODE keyblock;
PKT_public_key *pk;
keyblock = get_pubkeyblock (ctrl, k->kid);
if (!keyblock)
{
log_error (_("public key of ultimately"
" trusted key %s not found\n"), keystr(k->kid));
continue;
}
mark_keyblock_seen (used, keyblock);
mark_keyblock_seen (stored, keyblock);
mark_keyblock_seen (full_trust, keyblock);
pk = keyblock->pkt->pkt.public_key;
for (node=keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID)
update_validity (ctrl, pk, node->pkt->pkt.user_id,
0, TRUST_ULTIMATE);
}
if ( pk->expiredate && pk->expiredate >= start_time
&& pk->expiredate < next_expire)
next_expire = pk->expiredate;
release_kbnode (keyblock);
do_sync ();
}
if (opt.trust_model == TM_TOFU)
/* In the TOFU trust model, we only need to save the ultimately
trusted keys. */
goto leave;
klist = utk_list;
if (!opt.quiet)
log_info ("marginals needed: %d completes needed: %d trust model: %s\n",
opt.marginals_needed, opt.completes_needed,
trust_model_string (opt.trust_model));
for (depth=0; depth < opt.max_cert_depth; depth++)
{
int valids=0,key_count;
/* See whether we should assign ownertrust values to the keys in
klist. */
ot_unknown = ot_undefined = ot_never = 0;
ot_marginal = ot_full = ot_ultimate = 0;
for (k=klist; k; k = k->next)
{
int min=0;
/* 120 and 60 are as per RFC2440 */
if(k->trust_value>=120)
min=TRUST_FULLY;
else if(k->trust_value>=60)
min=TRUST_MARGINAL;
if(min!=k->min_ownertrust)
update_min_ownertrust (ctrl, k->kid,min);
if (interactive && k->ownertrust == TRUST_UNKNOWN)
{
k->ownertrust = ask_ownertrust (ctrl, k->kid,min);
if (k->ownertrust == (unsigned int)(-1))
{
quit=1;
goto leave;
}
}
/* This can happen during transition from an old trustdb
before trust sigs. It can also happen if a user uses two
different versions of GnuPG or changes the --trust-model
setting. */
if(k->ownertrust<min)
{
if(DBG_TRUST)
log_debug("key %08lX%08lX:"
" overriding ownertrust '%s' with '%s'\n",
(ulong)k->kid[0],(ulong)k->kid[1],
trust_value_to_string(k->ownertrust),
trust_value_to_string(min));
k->ownertrust=min;
}
if (k->ownertrust == TRUST_UNKNOWN)
ot_unknown++;
else if (k->ownertrust == TRUST_UNDEFINED)
ot_undefined++;
else if (k->ownertrust == TRUST_NEVER)
ot_never++;
else if (k->ownertrust == TRUST_MARGINAL)
ot_marginal++;
else if (k->ownertrust == TRUST_FULLY)
ot_full++;
else if (k->ownertrust == TRUST_ULTIMATE)
ot_ultimate++;
valids++;
}
/* Find all keys which are signed by a key in kdlist */
keys = validate_key_list (ctrl, kdb, full_trust, klist,
start_time, &next_expire);
if (!keys)
{
log_error ("validate_key_list failed\n");
rc = GPG_ERR_GENERAL;
goto leave;
}
for (key_count=0, kar=keys; kar->keyblock; kar++, key_count++)
;
/* Store the calculated valididation status somewhere */
if (opt.verbose > 1 && DBG_TRUST)
dump_key_array (depth, keys);
for (kar=keys; kar->keyblock; kar++)
store_validation_status (ctrl, depth, kar->keyblock, stored);
if (!opt.quiet)
log_info (_("depth: %d valid: %3d signed: %3d"
" trust: %d-, %dq, %dn, %dm, %df, %du\n"),
depth, valids, key_count, ot_unknown, ot_undefined,
ot_never, ot_marginal, ot_full, ot_ultimate );
/* Build a new kdlist from all fully valid keys in KEYS */
if (klist != utk_list)
release_key_items (klist);
klist = NULL;
for (kar=keys; kar->keyblock; kar++)
{
for (node=kar->keyblock; node; node = node->next)
{
if (node->pkt->pkttype == PKT_USER_ID && (node->flag & 4))
{
u32 kid[2];
/* have we used this key already? */
keyid_from_pk (kar->keyblock->pkt->pkt.public_key, kid);
if(test_key_hash_table(used,kid)==0)
{
/* Normally we add both the primary and subkey
ids to the hash via mark_keyblock_seen, but
since we aren't using this hash as a skipfnc,
that doesn't matter here. */
add_key_hash_table (used,kid);
k = new_key_item ();
k->kid[0]=kid[0];
k->kid[1]=kid[1];
k->ownertrust =
(tdb_get_ownertrust
(ctrl, kar->keyblock->pkt->pkt.public_key, 0)
& TRUST_MASK);
k->min_ownertrust = tdb_get_min_ownertrust
(ctrl, kar->keyblock->pkt->pkt.public_key, 0);
k->trust_depth=
kar->keyblock->pkt->pkt.public_key->trust_depth;
k->trust_value=
kar->keyblock->pkt->pkt.public_key->trust_value;
if(kar->keyblock->pkt->pkt.public_key->trust_regexp)
k->trust_regexp=
xstrdup(kar->keyblock->pkt->
pkt.public_key->trust_regexp);
k->next = klist;
klist = k;
break;
}
}
}
}
release_key_array (keys);
keys = NULL;
if (!klist)
break; /* no need to dive in deeper */
}
leave:
keydb_release (kdb);
release_key_array (keys);
if (klist != utk_list)
release_key_items (klist);
release_key_hash_table (full_trust);
release_key_hash_table (used);
release_key_hash_table (stored);
if (!rc && !quit) /* mark trustDB as checked */
{
int rc2;
if (next_expire == 0xffffffff || next_expire < start_time )
tdbio_write_nextcheck (ctrl, 0);
else
{
tdbio_write_nextcheck (ctrl, next_expire);
if (!opt.quiet)
log_info (_("next trustdb check due at %s\n"),
strtimestamp (next_expire));
}
rc2 = tdbio_update_version_record (ctrl);
if (rc2)
{
log_error (_("unable to update trustdb version record: "
"write failed: %s\n"), gpg_strerror (rc2));
tdbio_invalid ();
}
do_sync ();
pending_check_trustdb = 0;
}
return rc;
}
diff --git a/g13/g13-syshelp.c b/g13/g13-syshelp.c
index 08b67377c..622ca2ac3 100644
--- a/g13/g13-syshelp.c
+++ b/g13/g13-syshelp.c
@@ -1,741 +1,741 @@
/* g13-syshelp.c - Helper for disk key management with GnuPG
* Copyright (C) 2015 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <limits.h>
#ifdef HAVE_PWD_H
# include <pwd.h>
#endif
#include <unistd.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "g13-syshelp.h"
#include <gcrypt.h>
#include <assuan.h>
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "keyblob.h"
enum cmd_and_opt_values {
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oRecipient = 'r',
aGPGConfList = 500,
oDebug,
oDebugLevel,
oDebugAll,
oDebugNone,
oDebugWait,
oDebugAllowCoreDump,
oLogFile,
oNoLogFile,
oAuditLog,
oOutput,
oAgentProgram,
oGpgProgram,
oType,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oStatusFD,
oLoggerFD,
oNoVerbose,
oNoSecmemWarn,
oHomedir,
oDryRun,
oNoDetach,
oNoRandomSeedFile,
oFakedSystemTime
};
static gpgrt_opt_t opts[] = {
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MOUNT_VALUE , "mount" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* The timer tick interval used by the idle task. */
#define TIMERTICK_INTERVAL_SEC (1)
/* It is possible that we are currently running under setuid permissions. */
static int maybe_setuid = 1;
/* Helper to implement --debug-level and --debug. */
static const char *debug_level;
static unsigned int debug_value;
/* Local prototypes. */
static void g13_syshelp_deinit_default_ctrl (ctrl_t ctrl);
static void release_tab_items (tab_item_t tab);
static tab_item_t parse_g13tab (const char *username);
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@G13@-syshelp (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
break;
case 1:
case 40: p = _("Usage: @G13@-syshelp [options] [files] (-h for help)");
break;
case 41:
p = _("Syntax: @G13@-syshelp [options] [files]\n"
"Helper to perform root-only tasks for g13\n");
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
default: p = NULL; break;
}
return p;
}
/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
debug flags are propagated to the subsystems. With DEBUG_LEVEL
set, a specific set of debug flags is set; and individual debugging
flags will be added on top. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_MOUNT_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* if (numok) */
/* opt.debug &= ~(DBG_HASHING_VALUE); */
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
g13_exit(2);
}
opt.debug |= debug_value;
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
int
main (int argc, char **argv)
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
gpg_error_t err = 0;
/* const char *fname; */
int may_coredump;
char *last_configname = NULL;
const char *configname = NULL;
int debug_argparser = 0;
int no_more_options = 0;
char *logfile = NULL;
/* int debug_wait = 0; */
int use_random_seed = 1;
/* int nodetach = 0; */
/* int nokeysetup = 0; */
struct server_control_s ctrl;
/*mtrace();*/
early_system_init ();
gnupg_reopen_std (G13_NAME "-syshelp");
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix (G13_NAME "-syshelp", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
/* Take extra care of the random pool. */
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps ();
g13_init_signals ();
dotlock_create (NULL, 0); /* Register locking cleanup. */
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
/* First check whether we have a debug option on the commandline. */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
* Now we are working under our real uid
*/
/* Setup malloc hooks. */
{
struct assuan_malloc_hooks malloc_hooks;
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
}
/* Prepare libassuan. */
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Setup a default control structure for command line mode. */
memset (&ctrl, 0, sizeof ctrl);
g13_syshelp_init_default_ctrl (&ctrl);
ctrl.no_server = 1;
ctrl.status_fd = -1; /* No status output. */
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (!no_more_options
&& gpgrt_argparser (&pargs, opts, G13_NAME"-syshelp" EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
}
break;
case oQuiet: opt.quiet = 1; break;
case oDryRun: opt.dry_run = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oNoLogFile: logfile = NULL; break;
case oNoDetach: /*nodetach = 1; */break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: debug_value = ~0; break;
case oDebugNone: debug_value = 0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: /*debug_wait = pargs.r.ret_int; */break;
case oDebugAllowCoreDump:
may_coredump = enable_core_dumps ();
break;
case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break;
case oNoRandomSeedFile: use_random_seed = 0; break;
default:
pargs.err = configname? ARGPARSE_PRINT_WARNING:ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL);
if (!last_configname)
opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (),
G13_NAME"-syshelp" EXTSEP_S "conf",
NULL);
else
{
opt.config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
g13_exit(2);
/* Now that we have the options parsed we need to update the default
control structure. */
g13_syshelp_init_default_ctrl (&ctrl);
if (may_coredump && !opt.quiet)
log_info (_("WARNING: program may create a core file!\n"));
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print any pending secure memory warnings. */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
/* Setup the debug flags for all subsystems. */
set_debug ();
/* Install a regular exit handler to make real sure that the secure
memory gets wiped out. */
g13_install_emergency_cleanup ();
/* Terminate if we found any error until now. */
if (log_get_errorcount(0))
g13_exit (2);
/* Set the standard GnuPG random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL);
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
xfree(p);
}
/* Get the UID of the caller. */
#if defined(HAVE_PWD_H) && defined(HAVE_GETPWUID)
{
const char *uidstr;
struct passwd *pwd = NULL;
uidstr = getenv ("USERV_UID");
/* Print a quick note if we are not started via userv. */
if (!uidstr)
{
if (getuid ())
{
log_info ("WARNING: Not started via userv\n");
ctrl.fail_all_cmds = 1;
}
ctrl.client.uid = getuid ();
}
else
{
unsigned long myuid;
errno = 0;
myuid = strtoul (uidstr, NULL, 10);
if (myuid == ULONG_MAX && errno)
{
log_info ("WARNING: Started via broken userv: %s\n",
strerror (errno));
ctrl.fail_all_cmds = 1;
ctrl.client.uid = getuid ();
}
else
ctrl.client.uid = (uid_t)myuid;
}
pwd = getpwuid (ctrl.client.uid);
if (!pwd || !*pwd->pw_name)
{
log_info ("WARNING: Name for UID not found: %s\n", strerror (errno));
ctrl.fail_all_cmds = 1;
ctrl.client.uname = xstrdup ("?");
}
else
ctrl.client.uname = xstrdup (pwd->pw_name);
/* Check that the user name does not contain a directory
separator. */
if (strchr (ctrl.client.uname, '/'))
{
log_info ("WARNING: Invalid user name passed\n");
ctrl.fail_all_cmds = 1;
}
}
#else /*!HAVE_PWD_H || !HAVE_GETPWUID*/
log_info ("WARNING: System does not support required syscalls\n");
ctrl.fail_all_cmds = 1;
ctrl.client.uid = getuid ();
ctrl.client.uname = xstrdup ("?");
#endif /*!HAVE_PWD_H || !HAVE_GETPWUID*/
/* Read the table entries for this user. */
if (!ctrl.fail_all_cmds
&& !(ctrl.client.tab = parse_g13tab (ctrl.client.uname)))
ctrl.fail_all_cmds = 1;
/* Start the server. */
err = syshelp_server (&ctrl);
if (err)
log_error ("server exited with error: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
/* Cleanup. */
g13_syshelp_deinit_default_ctrl (&ctrl);
g13_exit (0);
return 8; /*NOTREACHED*/
}
/* Store defaults into the per-connection CTRL object. */
void
g13_syshelp_init_default_ctrl (ctrl_t ctrl)
{
ctrl->conttype = CONTTYPE_DM_CRYPT;
}
/* Release all resources allocated by default in the CTRl object. */
static void
g13_syshelp_deinit_default_ctrl (ctrl_t ctrl)
{
xfree (ctrl->client.uname);
release_tab_items (ctrl->client.tab);
}
/* Release the list of g13tab itejms at TAB. */
static void
release_tab_items (tab_item_t tab)
{
while (tab)
{
tab_item_t next = tab->next;
xfree (tab->mountpoint);
xfree (tab);
tab = next;
}
}
void
g13_syshelp_i_know_what_i_am_doing (void)
{
const char * const yesfile = "Yes-g13-I-know-what-I-am-doing";
char *fname;
fname = make_filename (gnupg_sysconfdir (), yesfile, NULL);
if (gnupg_access (fname, F_OK))
{
log_info ("*******************************************************\n");
log_info ("* The G13 support for DM-Crypt is not yet widely used.\n");
log_info ("* Bugs or improper use may delete all your disks!\n");
log_info ("* To confirm that you are ware of this risk, create\n");
log_info ("* the file '%s'.\n", fname);
log_info ("*******************************************************\n");
exit (1);
}
xfree (fname);
}
/* Parse the /etc/gnupg/g13tab for user USERNAME. Return a table for
the user on success. Return NULL on error and print
diagnostics. */
static tab_item_t
parse_g13tab (const char *username)
{
gpg_error_t err;
int c, n;
char line[512];
char *p;
char *fname;
estream_t fp;
int lnr;
char **words = NULL;
tab_item_t table = NULL;
tab_item_t *tabletail, ti;
fname = make_filename (gnupg_sysconfdir (), G13_NAME"tab", NULL);
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
tabletail = &table;
err = 0;
lnr = 0;
while (es_fgets (line, DIM(line)-1, fp))
{
lnr++;
n = strlen (line);
if (!n || line[n-1] != '\n')
{
/* Eat until end of line. */
while ((c=es_getc (fp)) != EOF && c != '\n')
;
err = gpg_error (*line? GPG_ERR_LINE_TOO_LONG
: GPG_ERR_INCOMPLETE_LINE);
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
continue;
}
line[--n] = 0; /* Chop the LF. */
if (n && line[n-1] == '\r')
line[--n] = 0; /* Chop an optional CR. */
/* Allow for empty lines and spaces */
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
/* Parse the line. The format is
* <username> <blockdev> [<label>|"-" [<mountpoint>]]
*/
xfree (words);
words = strtokenize (p, " \t");
if (!words)
{
err = gpg_error_from_syserror ();
break;
}
if (!words[0] || !words[1])
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (GPG_ERR_SYNTAX));
continue;
}
if (!(*words[1] == '/'
|| !strncmp (words[1], "PARTUUID=", 9)
|| !strncmp (words[1], "partuuid=", 9)))
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, "Invalid block device syntax");
continue;
}
if (words[2])
{
if (strlen (words[2]) > 16 || strchr (words[2], '/'))
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, "Label too long or invalid syntax");
continue;
}
if (words[3] && *words[3] != '/')
{
log_error (_("file '%s', line %d: %s\n"),
fname, lnr, "Invalid mountpoint syntax");
continue;
}
}
if (strcmp (words[0], username))
continue; /* Skip entries for other usernames! */
ti = xtrymalloc (sizeof *ti + strlen (words[1]));
if (!ti)
{
err = gpg_error_from_syserror ();
break;
}
ti->next = NULL;
ti->label = NULL;
ti->mountpoint = NULL;
strcpy (ti->blockdev, *words[1]=='/'? words[1] : words[1]+9);
if (words[2])
{
if (strcmp (words[2], "-")
&& !(ti->label = xtrystrdup (words[2])))
{
err = gpg_error_from_syserror ();
xfree (ti);
break;
}
if (words[3] && !(ti->mountpoint = xtrystrdup (words[3])))
{
err = gpg_error_from_syserror ();
xfree (ti->label);
xfree (ti);
break;
}
}
*tabletail = ti;
tabletail = &ti->next;
}
if (!err && !es_feof (fp))
err = gpg_error_from_syserror ();
if (err)
log_error (_("error reading '%s', line %d: %s\n"),
fname, lnr, gpg_strerror (err));
leave:
xfree (words);
es_fclose (fp);
xfree (fname);
if (err)
{
release_tab_items (table);
return NULL;
}
return table;
}
diff --git a/g13/g13.c b/g13/g13.c
index dc2dca109..9d9e2e2b0 100644
--- a/g13/g13.c
+++ b/g13/g13.c
@@ -1,1061 +1,1061 @@
/* g13.c - Disk Key management with GnuPG
* Copyright (C) 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "g13.h"
#include <gcrypt.h>
#include <assuan.h>
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "../common/exechelp.h"
#include "keyblob.h"
#include "server.h"
#include "runner.h"
#include "create.h"
#include "mount.h"
#include "suspend.h"
#include "mountinfo.h"
#include "backend.h"
#include "call-syshelp.h"
enum cmd_and_opt_values {
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oRecipient = 'r',
aGPGConfList = 500,
aGPGConfTest,
aCreate,
aMount,
aUmount,
aSuspend,
aResume,
aServer,
aFindDevice,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugNone,
oDebugWait,
oDebugAllowCoreDump,
oLogFile,
oNoLogFile,
oAuditLog,
oOutput,
oAgentProgram,
oGpgProgram,
oType,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oStatusFD,
oLoggerFD,
oNoVerbose,
oNoSecmemWarn,
oNoGreeting,
oNoTTY,
oNoOptions,
oHomedir,
oWithColons,
oDryRun,
oNoDetach,
oNoMount,
oNoRandomSeedFile,
oFakedSystemTime
};
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aCreate, "create", N_("Create a new file system container")),
ARGPARSE_c (aMount, "mount", N_("Mount a file system container") ),
ARGPARSE_c (aUmount, "umount", N_("Unmount a file system container") ),
ARGPARSE_c (aSuspend, "suspend", N_("Suspend a file system container") ),
ARGPARSE_c (aResume, "resume", N_("Resume a file system container") ),
ARGPARSE_c (aServer, "server", N_("Run in server mode")),
ARGPARSE_c (aFindDevice, "find-device", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oType, "type", N_("|NAME|use container format NAME")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write log output to FILE")),
ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_n (oNoMount, "no-mount", N_("stop right before running mount")),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd",
N_("|FD|write status info to this FD")),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
ARGPARSE_group (303, N_("@\nExamples:\n\n"
" blurb\n"
" blurb\n")),
/* Hidden options. */
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
/* Command aliases. */
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MOUNT_VALUE , "mount" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* The timer tick interval used by the idle task. */
#define TIMERTICK_INTERVAL_SEC (1)
/* It is possible that we are currently running under setuid permissions. */
static int maybe_setuid = 1;
/* Helper to implement --debug-level and --debug. */
static const char *debug_level;
static unsigned int debug_value;
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* The thread id of the idle task. */
static npth_t idle_task_thread;
/* The container type as specified on the command line. */
static int cmdline_conttype;
static void set_cmd (enum cmd_and_opt_values *ret_cmd,
enum cmd_and_opt_values new_cmd );
static void start_idle_task (void);
static void join_idle_task (void);
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@G13@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <" PACKAGE_BUGREPORT ">.\n");
break;
case 1:
case 40: p = _("Usage: @G13@ [options] [files] (-h for help)");
break;
case 41:
p = _("Syntax: @G13@ [options] [files]\n"
"Create, mount or unmount an encrypted file system container\n");
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
default: p = NULL; break;
}
return p;
}
static void
wrong_args (const char *text)
{
fprintf (stderr, _("usage: %s [options] "), G13_NAME);
fputs (text, stderr);
putc ('\n', stderr);
g13_exit (2);
}
/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
debug flags are propagated to the subsystems. With DEBUG_LEVEL
set, a specific set of debug flags is set; and individual debugging
flags will be added on top. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE|DBG_MOUNT_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_MOUNT_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* if (numok) */
/* opt.debug &= ~(DBG_HASHING_VALUE); */
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
g13_exit(2);
}
opt.debug |= debug_value;
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug)
opt.quiet = 0;
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
enum cmd_and_opt_values cmd = *ret_cmd;
if (!cmd || cmd == new_cmd)
cmd = new_cmd;
else
{
log_error (_("conflicting commands\n"));
g13_exit (2);
}
*ret_cmd = cmd;
}
int
main (int argc, char **argv)
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
gpg_error_t err = 0;
/* const char *fname; */
int may_coredump;
char *last_configname = NULL;
const char *configname = NULL;
int debug_argparser = 0;
int no_more_options = 0;
char *logfile = NULL;
int greeting = 0;
int nogreeting = 0;
/* int debug_wait = 0; */
int use_random_seed = 1;
/* int nodetach = 0; */
/* int nokeysetup = 0; */
enum cmd_and_opt_values cmd = 0;
struct server_control_s ctrl;
strlist_t recipients = NULL;
/*mtrace();*/
early_system_init ();
gnupg_reopen_std (G13_NAME);
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix (G13_NAME, GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Take extra care of the random pool. */
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps ();
g13_init_signals ();
dotlock_create (NULL, 0); /* Register locking cleanup. */
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
/* First check whether we have a config file on the commandline. */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
* Now we are now working under our real uid
*/
/* Setup malloc hooks. */
{
struct assuan_malloc_hooks malloc_hooks;
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
}
/* Prepare libassuan. */
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
/* Setup a default control structure for command line mode. */
memset (&ctrl, 0, sizeof ctrl);
g13_init_default_ctrl (&ctrl);
ctrl.no_server = 1;
ctrl.status_fd = -1; /* No status output. */
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
- * flags so that the internal intialized flag won't be cleared. */
+ * flags so that the internal initialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
#if GPGRT_VERSION_NUMBER >= 0x013000 /* >= 1.48 */
| ARGPARSE_FLAG_COMMAND
#endif
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (!no_more_options
&& gpgrt_argparser (&pargs, opts, G13_NAME EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
}
break;
case aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
nogreeting = 1;
/* nokeysetup = 1; */
break;
case aServer:
case aMount:
case aUmount:
case aSuspend:
case aResume:
case aCreate:
case aFindDevice:
set_cmd (&cmd, pargs.r_opt);
break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oNoGreeting: nogreeting = 1; break;
case oNoTTY: break;
case oDryRun: opt.dry_run = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oNoLogFile: logfile = NULL; break;
case oNoDetach: /*nodetach = 1; */break;
case oNoMount: opt.no_mount = 1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: debug_value = ~0; break;
case oDebugNone: debug_value = 0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: /*debug_wait = pargs.r.ret_int; */break;
case oDebugAllowCoreDump:
may_coredump = enable_core_dumps ();
break;
case oStatusFD: ctrl.status_fd = pargs.r.ret_int; break;
case oLoggerFD: log_set_fd (pargs.r.ret_int ); break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oAgentProgram: opt.agent_program = pargs.r.ret_str; break;
case oGpgProgram: opt.gpg_program = pargs.r.ret_str; break;
case oDisplay: opt.display = xstrdup (pargs.r.ret_str); break;
case oTTYname: opt.ttyname = xstrdup (pargs.r.ret_str); break;
case oTTYtype: opt.ttytype = xstrdup (pargs.r.ret_str); break;
case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break;
case oXauthority: opt.xauthority = xstrdup (pargs.r.ret_str); break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oNoSecmemWarn: gcry_control (GCRYCTL_DISABLE_SECMEM_WARN); break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oRecipient: /* Store the encryption key. */
add_to_strlist (&recipients, pargs.r.ret_str);
break;
case oType:
if (!strcmp (pargs.r.ret_str, "help"))
{
be_parse_conttype_name (NULL);
g13_exit (0);
}
cmdline_conttype = be_parse_conttype_name (pargs.r.ret_str);
if (!cmdline_conttype)
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL);
/* Construct GPG arguments. */
{
strlist_t last;
last = append_to_strlist (&opt.gpg_arguments, "-z");
last = append_to_strlist (&last, "0");
last = append_to_strlist (&last, "--trust-model");
last = append_to_strlist (&last, "always");
(void) last;
}
if (!last_configname)
opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (),
G13_NAME EXTSEP_S "conf",
NULL);
else
{
opt.config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
g13_exit(2);
/* Now that we have the options parsed we need to update the default
control structure. */
g13_init_default_ctrl (&ctrl);
ctrl.recipients = recipients;
recipients = NULL;
if (nogreeting)
greeting = 0;
if (greeting)
{
fprintf (stderr, "%s %s; %s\n",
gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14) );
fprintf (stderr, "%s\n", gpgrt_strusage(15));
}
if (may_coredump && !opt.quiet)
log_info (_("WARNING: program may create a core file!\n"));
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
}
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print any pending secure memory warnings. */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
/* Setup the debug flags for all subsystems. */
set_debug ();
/* Install emergency cleanup handler. */
g13_install_emergency_cleanup ();
/* Terminate if we found any error until now. */
if (log_get_errorcount(0))
g13_exit (2);
/* Set the standard GnuPG random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL);
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
xfree(p);
}
/* Store given filename into FNAME. */
/* fname = argc? *argv : NULL; */
/* Parse all given encryption keys. This does a lookup of the keys
and stops if any of the given keys was not found. */
#if 0 /* Currently not implemented. */
if (!nokeysetup)
{
strlist_t sl;
int failed = 0;
for (sl = ctrl->recipients; sl; sl = sl->next)
if (check_encryption_key ())
failed = 1;
if (failed)
g13_exit (1);
}
#endif /*0*/
/* Dispatch command. */
err = 0;
switch (cmd)
{
case aGPGConfList:
{ /* List options and default values in the GPG Conf format. */
char *config_filename_esc = percent_escape (opt.config_filename, NULL);
printf ("gpgconf-g13.conf:%lu:\"%s\n",
GC_OPT_FLAG_DEFAULT, config_filename_esc);
xfree (config_filename_esc);
printf ("verbose:%lu:\n", GC_OPT_FLAG_NONE);
printf ("quiet:%lu:\n", GC_OPT_FLAG_NONE);
printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
printf ("log-file:%lu:\n", GC_OPT_FLAG_NONE);
}
break;
case aGPGConfTest:
/* This is merely a dummy command to test whether the
configuration file is valid. */
break;
case aServer:
{
start_idle_task ();
ctrl.no_server = 0;
err = g13_server (&ctrl);
if (err)
log_error ("server exited with error: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
else
g13_request_shutdown ();
}
break;
case aFindDevice:
{
char *blockdev;
if (argc != 1)
wrong_args ("--find-device name");
err = call_syshelp_find_device (&ctrl, argv[0], &blockdev);
if (err)
log_error ("error finding device '%s': %s <%s>\n",
argv[0], gpg_strerror (err), gpg_strsource (err));
else
puts (blockdev);
}
break;
case aCreate: /* Create a new container. */
{
if (argc != 1)
wrong_args ("--create filename");
start_idle_task ();
err = g13_create_container (&ctrl, argv[0]);
if (err)
log_error ("error creating a new container: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
else
g13_request_shutdown ();
}
break;
case aMount: /* Mount a container. */
{
if (argc != 1 && argc != 2 )
wrong_args ("--mount filename [mountpoint]");
start_idle_task ();
err = g13_mount_container (&ctrl, argv[0], argc == 2?argv[1]:NULL);
if (err)
log_error ("error mounting container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
case aUmount: /* Unmount a mounted container. */
{
if (argc != 1)
wrong_args ("--umount filename");
err = g13_umount_container (&ctrl, argv[0], NULL);
if (err)
log_error ("error unmounting container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
case aSuspend: /* Suspend a container. */
{
/* Fixme: Should we add a suspend all container option? */
if (argc != 1)
wrong_args ("--suspend filename");
err = g13_suspend_container (&ctrl, argv[0]);
if (err)
log_error ("error suspending container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
case aResume: /* Resume a suspended container. */
{
/* Fixme: Should we add a resume all container option? */
if (argc != 1)
wrong_args ("--resume filename");
err = g13_resume_container (&ctrl, argv[0]);
if (err)
log_error ("error resuming container '%s': %s <%s>\n",
*argv, gpg_strerror (err), gpg_strsource (err));
}
break;
default:
log_error (_("invalid command (there is no implicit command)\n"));
break;
}
g13_deinit_default_ctrl (&ctrl);
if (!err)
join_idle_task ();
/* Cleanup. */
g13_exit (0);
return 8; /*NOTREACHED*/
}
/* Store defaults into the per-connection CTRL object. */
void
g13_init_default_ctrl (ctrl_t ctrl)
{
ctrl->conttype = cmdline_conttype? cmdline_conttype : CONTTYPE_ENCFS;
}
/* Release remaining resources allocated in the CTRL object. */
void
g13_deinit_default_ctrl (ctrl_t ctrl)
{
call_syshelp_release (ctrl);
FREE_STRLIST (ctrl->recipients);
}
/* Request a shutdown. This can be used when the process should
* finish instead of running the idle task. */
void
g13_request_shutdown (void)
{
shutdown_pending++;
}
/* This function is called for each signal we catch. It is run in the
main context or the one of a NPth thread and thus it is not
restricted in what it may do. */
static void
handle_signal (int signo)
{
switch (signo)
{
#ifndef HAVE_W32_SYSTEM
case SIGHUP:
log_info ("SIGHUP received - re-reading configuration\n");
/* Fixme: Not yet implemented. */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
mountinfo_dump_all ();
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %u runners active\n",
runner_get_threads ());
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13) );
g13_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
g13_exit (0);
break;
#endif /*!HAVE_W32_SYSTEM*/
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
/* This ticker function is called about every TIMERTICK_INTERVAL_SEC
seconds. */
static void
handle_tick (void)
{
/* log_debug ("TICK\n"); */
}
/* The idle task. We use a separate thread to do idle stuff and to
catch signals. */
static void *
idle_task (void *dummy_arg)
{
int signo; /* The number of a raised signal is stored here. */
int saved_errno;
struct timespec abstime;
struct timespec curtime;
struct timespec timeout;
int ret;
(void)dummy_arg;
/* Create the event to catch the signals. */
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
#endif
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL_SEC;
for (;;)
{
/* The shutdown flag allows us to terminate the idle task. */
if (shutdown_pending)
{
runner_cancel_all ();
if (!runner_get_threads ())
break; /* ready */
}
npth_clock_gettime (&curtime);
if (!(npth_timercmp (&curtime, &abstime, <)))
{
/* Timeout. */
handle_tick ();
npth_clock_gettime (&abstime);
abstime.tv_sec += TIMERTICK_INTERVAL_SEC;
}
npth_timersub (&abstime, &curtime, &timeout);
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (0, NULL, NULL, NULL, &timeout, npth_sigev_sigmask());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (0, NULL, NULL, NULL, &timeout, NULL, NULL);
saved_errno = errno;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
npth_sleep (1);
continue;
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
next timeout. */
continue;
}
/* Here one would add processing of file descriptors. */
}
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
return NULL;
}
/* Start the idle task. */
static void
start_idle_task (void)
{
npth_attr_t tattr;
npth_t thread;
sigset_t sigs; /* The set of signals we want to catch. */
int err;
#ifndef HAVE_W32_SYSTEM
/* These signals should always go to the idle task, so they need to
be blocked everywhere else. We assume start_idle_task is called
from the main thread before any other threads are created. */
sigemptyset (&sigs);
sigaddset (&sigs, SIGHUP);
sigaddset (&sigs, SIGUSR1);
sigaddset (&sigs, SIGUSR2);
sigaddset (&sigs, SIGINT);
sigaddset (&sigs, SIGTERM);
npth_sigmask (SIG_BLOCK, &sigs, NULL);
#endif
npth_attr_init (&tattr);
npth_attr_setdetachstate (&tattr, NPTH_CREATE_JOINABLE);
err = npth_create (&thread, &tattr, idle_task, NULL);
if (err)
{
log_fatal ("error starting idle task: %s\n", strerror (err));
return; /*NOTREACHED*/
}
npth_setname_np (thread, "idle-task");
idle_task_thread = thread;
npth_attr_destroy (&tattr);
}
/* Wait for the idle task to finish. */
static void
join_idle_task (void)
{
int err;
/* FIXME: This assumes that a valid pthread_t is non-null. That is
not guaranteed. */
if (idle_task_thread)
{
err = npth_join (idle_task_thread, NULL);
if (err)
log_error ("waiting for idle task thread failed: %s\n",
strerror (err));
}
}
diff --git a/g13/sh-dmcrypt.c b/g13/sh-dmcrypt.c
index c3b5a6d77..19884f37b 100644
--- a/g13/sh-dmcrypt.c
+++ b/g13/sh-dmcrypt.c
@@ -1,1057 +1,1057 @@
/* sh-dmcrypt.c - The DM-Crypt part for g13-syshelp
* Copyright (C) 2015, 2016 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <errno.h>
#include <assert.h>
#include <sys/types.h>
#ifdef HAVE_SYS_MKDEV_H
#include <sys/mkdev.h>
#endif
#ifdef HAVE_SYS_SYSMACROS_H
# include <sys/sysmacros.h>
#endif
#ifdef HAVE_STAT
# include <sys/stat.h>
#endif
#include <unistd.h>
#include "g13-syshelp.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "g13tuple.h"
#include "../common/exectool.h"
#include "../common/sysutils.h"
#include "keyblob.h"
/* The standard disk block size (logical). */
#define SECTOR_SIZE 512
/* The physical block size used by modern devices. */
#define PHY_SECTOR_SIZE (SECTOR_SIZE*8) /* 4 KiB */
/* The length of the crypto setup area in sectors. 16 KiB is a nice
multiple of a modern block size and should be sufficient for all
kind of extra public key encryption packets. */
#define SETUP_AREA_SECTORS 32 /* 16 KiB */
/* The number of header block copies stored at the begin and end of
the device. */
#define HEADER_SETUP_AREA_COPIES 2
#define FOOTER_SETUP_AREA_COPIES 2
/* The length in blocks of the space we put at the start and at the
end of the device. This space is used to store N copies of the
setup area for the actual encrypted container in between. */
#define HEADER_SECTORS (SETUP_AREA_SECTORS * HEADER_SETUP_AREA_COPIES)
#define FOOTER_SECTORS (SETUP_AREA_SECTORS * FOOTER_SETUP_AREA_COPIES)
-/* Minimim size of the encrypted space in blocks. This is more or
+/* Minimum size of the encrypted space in blocks. This is more or
less an arbitrary value. */
#define MIN_ENCRYPTED_SPACE 32
/* Some consistency checks for the above constants. */
#if (PHY_SECTOR_SIZE % SECTOR_SIZE)
# error the physical secotor size should be a multiple of 512
#endif
#if ((SETUP_AREA_SECTORS*SECTOR_SIZE) % PHY_SECTOR_SIZE)
# error The setup area size should be a multiple of the phy. sector size.
#endif
/*
* Check whether the block device DEVNAME is used by device mapper.
* If EXPECT_BUSY is set no error message is printed if the device is
* busy. Returns: 0 if the device is good and not yet used by DM.
*/
static gpg_error_t
check_blockdev (const char *devname, int expect_busy)
{
gpg_error_t err;
struct stat sb;
unsigned int devmajor, devminor;
char *result = NULL;
char **lines = NULL;
char **fields = NULL;
int lno, count;
if (gnupg_stat (devname, &sb))
{
err = gpg_error_from_syserror ();
log_error ("error stating '%s': %s\n", devname, gpg_strerror (err));
return err;
}
if (!S_ISBLK (sb.st_mode))
{
err = gpg_error (GPG_ERR_ENOTBLK);
log_error ("can't use '%s': %s\n", devname, gpg_strerror (err));
return err;
}
devmajor = major (sb.st_rdev);
devminor = minor (sb.st_rdev);
{
const char *argv[2];
argv[0] = "deps";
argv[1] = NULL;
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running '%s' to search for '%s': %s\n",
"dmsetup deps", devname, gpg_strerror (err));
goto leave;
}
lines = strsplit (result, '\n', 0, NULL);
if (!lines)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (lines[0] && !strcmp (lines[0], "No devices found"))
;
else
{
for (lno=0; lines[lno]; lno++)
{
unsigned int xmajor, xminor;
if (!*lines[lno])
continue;
xfree (fields);
fields = strsplit (lines[lno], ':', 0, &count);
if (!fields)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (count < 3
|| sscanf (fields[2], " (%u,%u)", &xmajor, &xminor) != 2)
{
log_error ("error running '%s' to search for '%s': %s\n",
"dmsetup deps", devname, "unexpected output");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (xmajor == devmajor && xminor == devminor)
{
if (!expect_busy)
log_error ("device '%s' (%u:%u)"
" already in use by device mapper\n",
devname, devmajor, devminor);
err = gpg_error (GPG_ERR_EBUSY);
goto leave;
}
}
}
leave:
xfree (fields);
xfree (lines);
xfree (result);
return err;
}
/* Return a malloced buffer with the prefix of the setup area. This
is the data written right before the encrypted keyblob. Return NULL
on error and sets ERRNO. */
static void *
mk_setup_area_prefix (size_t *r_length)
{
unsigned char *packet;
size_t setuparealen;
packet = xtrymalloc (32);
if (!packet)
return NULL;
*r_length = 32;
setuparealen = SETUP_AREA_SECTORS * SECTOR_SIZE;
packet[0] = (0xc0|61); /* CTB for the private packet type 0x61. */
packet[1] = 0xff; /* 5 byte length packet, value 20. */
packet[2] = 0;
packet[3] = 0;
packet[4] = 0;
packet[5] = 26;
memcpy (packet+6, "GnuPG/G13", 10); /* Packet subtype. */
packet[16] = 1; /* G13 packet format version. */
packet[17] = 0; /* Reserved. */
packet[18] = 0; /* Reserved. */
packet[19] = 1; /* OS Flag = Linux */
packet[20] = (setuparealen >> 24); /* Total length of header. */
packet[21] = (setuparealen >> 16);
packet[22] = (setuparealen >> 8);
packet[23] = (setuparealen);
packet[24] = HEADER_SETUP_AREA_COPIES;
packet[25] = FOOTER_SETUP_AREA_COPIES;
packet[26] = 0; /* Reserved. */
packet[27] = 0; /* Reserved. */
packet[28] = 0; /* Reserved. */
packet[29] = 0; /* Reserved. */
packet[30] = 0; /* Reserved. */
packet[31] = 0; /* Reserved. */
return packet;
}
/* Create a new g13 style DM-Crypt container on device DEVNAME. */
gpg_error_t
sh_dmcrypt_create_container (ctrl_t ctrl, const char *devname, estream_t devfp)
{
gpg_error_t err;
char *header_space;
size_t header_space_size, header_space_used;
size_t paddinglen;
char *targetname = NULL;
size_t nread;
char *p;
char hexkey[16*2+1];
char *table = NULL;
unsigned long long nblocks;
char *result = NULL;
unsigned char twobyte[2];
membuf_t keyblob;
void *keyblob_buf = NULL;
size_t keyblob_len;
size_t n;
const char *s;
unsigned char *packet;
int copy;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
header_space_size = SETUP_AREA_SECTORS * SECTOR_SIZE;
header_space = xtrymalloc (header_space_size);
if (!header_space)
return gpg_error_from_syserror ();
/* Start building the keyblob. */
init_membuf (&keyblob, 512);
append_tuple (&keyblob, KEYBLOB_TAG_BLOBVERSION, "\x01", 1);
n = CONTTYPE_DM_CRYPT;
twobyte[0] = (n >> 8);
twobyte[1] = n;
append_tuple (&keyblob, KEYBLOB_TAG_CONTTYPE, twobyte, 2);
{
gnupg_isotime_t tbuf;
gnupg_get_isotime (tbuf);
append_tuple (&keyblob, KEYBLOB_TAG_CREATED, tbuf, strlen (tbuf));
}
/* Rewind device stream. */
if (es_fseeko (devfp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking to begin of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
es_clearerr (devfp);
/* Extra check that the device is empty. */
if (es_read (devfp, header_space, header_space_size, &nread))
err = gpg_error_from_syserror ();
else if (nread != header_space_size)
err = gpg_error (GPG_ERR_TOO_SHORT);
else
err = 0;
if (err)
{
log_error ("error reading header space of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
for (p=header_space; nread && !*p; nread--, p++)
;
if (nread)
{
log_error ("header space of '%s' already used - use %s to override\n",
devname, "--force");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Check that the device is not used by device mapper. */
err = check_blockdev (devname, 0);
if (err)
goto leave;
/* Compute the number of blocks. */
err = sh_blockdev_getsz (devname, &nblocks);
if (err)
{
log_error ("error getting size of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (nblocks <= HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS)
{
log_error ("device '%s' is too small (min=%d blocks)\n",
devname,
HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS);
err = gpg_error (GPG_ERR_TOO_SHORT);
goto leave;
}
append_tuple_uint (&keyblob, KEYBLOB_TAG_CONT_NSEC, nblocks);
nblocks -= HEADER_SECTORS + FOOTER_SECTORS;
append_tuple_uint (&keyblob, KEYBLOB_TAG_ENC_NSEC, nblocks);
append_tuple_uint (&keyblob, KEYBLOB_TAG_ENC_OFF, HEADER_SECTORS);
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname = strconcat ("g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Create the key. */
{
char key[16];
gcry_randomize (key, sizeof key, GCRY_STRONG_RANDOM);
append_tuple (&keyblob, KEYBLOB_TAG_ENCKEY, key, sizeof key);
bin2hex (key, 16, hexkey);
wipememory (key, 16);
/* Add a 2*(4+16) byte filler to conceal the fact that we use
AES-128. If we ever want to switch to 256 bit we can resize
that filler to keep the keyblob at the same size. */
append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key);
append_tuple (&keyblob, KEYBLOB_TAG_FILLER, key, sizeof key);
}
/* Build dmcrypt table. */
s = "aes-cbc-essiv:sha256";
append_tuple (&keyblob, KEYBLOB_TAG_ALGOSTR, s, strlen (s));
table = es_bsprintf ("0 %llu crypt %s %s 0 %s %d",
nblocks, s, hexkey, devname, HEADER_SECTORS);
if (!table)
{
err = gpg_error_from_syserror ();
goto leave;
}
wipememory (hexkey, sizeof hexkey);
/* Add a copy of the setup area prefix to the keyblob. */
p = mk_setup_area_prefix (&n);
if (!p)
{
err = gpg_error_from_syserror ();
goto leave;
}
append_tuple (&keyblob, KEYBLOB_TAG_HDRCOPY, p, n);
assert (n < header_space_size);
memcpy (header_space, p, n);
header_space_used = n;
/* Turn the keyblob into a buffer and callback to encrypt it. */
keyblob_buf = get_membuf (&keyblob, &keyblob_len);
if (!keyblob_buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = sh_encrypt_keyblob (ctrl, keyblob_buf, keyblob_len, &p, &n);
if (err)
{
log_error ("encrypting the keyblob failed: %s\n", gpg_strerror (err));
goto leave;
}
log_debug ("plain setuparea=%p %zu bytes\n", keyblob_buf, keyblob_len);
wipememory (keyblob_buf, keyblob_len);
xfree (keyblob_buf);
keyblob_buf = NULL;
log_debug ("encry setuparea=%p %zu bytes\n", p, n);
if (n >= header_space_size || (header_space_used + n) >= header_space_size)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
log_error ("setup area would overflow: %s\n", gpg_strerror (err));
goto leave;
}
memcpy (header_space + header_space_used, p, n);
header_space_used += n;
/* Write the padding. */
packet = header_space + header_space_used;
paddinglen = header_space_size - header_space_used;
if (paddinglen < 16)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
log_error ("setup area too short for padding: %s\n", gpg_strerror (err));
goto leave;
}
packet[0] = (0xc0|61); /* CTB for Private packet type 0x61. */
packet[1] = 0xff; /* 5 byte length packet, value 20. */
packet[2] = (paddinglen-6) >> 24;
packet[3] = (paddinglen-6) >> 16;
packet[4] = (paddinglen-6) >> 8;
packet[5] = (paddinglen-6);
packet += 6;
paddinglen -= 6;
header_space_used += 6;
for ( ;paddinglen >= 10;
paddinglen -= 10, packet += 10, header_space_used += 10)
memcpy (packet, "GnuPG/PAD", 10);
for ( ;paddinglen; paddinglen--, packet++, header_space_used++)
*packet = 0;
if (header_space_used != header_space_size)
BUG ();
/* Create the container. */
{
const char *argv[3];
argv[0] = "create";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup create %s\"\n", targetname);
log_debug (" with table='%s'\"\n", table);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, table, &result, NULL);
}
if (err)
{
log_error ("error running dmsetup for '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
/* Write the setup area. */
if (es_fseeko (devfp, 0, SEEK_SET))
{
err = gpg_error_from_syserror ();
log_error ("error seeking to begin of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
es_clearerr (devfp);
for (copy = 0; copy < HEADER_SETUP_AREA_COPIES; copy++)
{
size_t nwritten;
if (es_write (devfp, header_space, header_space_size, &nwritten))
{
err = gpg_error_from_syserror ();
break;
}
else if (nwritten != header_space_size)
{
err = gpg_error (GPG_ERR_TOO_SHORT);
break;
}
}
if (err)
{
log_error ("error writing header space copy %d of '%s': %s\n",
copy, devname, gpg_strerror (err));
goto leave;
}
if (es_fseeko (devfp,
(- header_space_size * FOOTER_SETUP_AREA_COPIES), SEEK_END))
{
err = gpg_error_from_syserror ();
log_error ("error seeking to end of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
es_clearerr (devfp);
for (copy = 0; copy < FOOTER_SETUP_AREA_COPIES; copy++)
{
size_t nwritten;
if (es_write (devfp, header_space, header_space_size, &nwritten))
{
err = gpg_error_from_syserror ();
break;
}
else if (nwritten != header_space_size)
{
err = gpg_error (GPG_ERR_TOO_SHORT);
break;
}
}
if (!err && es_fflush (devfp))
err = gpg_error_from_syserror ();
if (err)
{
log_error ("error writing footer space copy %d of '%s': %s\n",
copy, devname, gpg_strerror (err));
goto leave;
}
leave:
wipememory (hexkey, sizeof hexkey);
if (table)
{
wipememory (table, strlen (table));
xfree (table);
}
if (keyblob_buf)
{
wipememory (keyblob_buf, keyblob_len);
xfree (keyblob_buf);
}
xfree (get_membuf (&keyblob, NULL));
xfree (targetname);
xfree (result);
xfree (header_space);
return err;
}
/* Mount a DM-Crypt container on device DEVNAME taking keys and other
* meta data from KEYBLOB. If NOMOUNT is set the actual mount command
* is not run. */
gpg_error_t
sh_dmcrypt_mount_container (ctrl_t ctrl, const char *devname,
tupledesc_t keyblob, int nomount)
{
gpg_error_t err;
char *targetname_abs = NULL;
const char *targetname;
char hexkey[16*2+1];
char *table = NULL;
unsigned long long nblocks, nblocks2;
char *result = NULL;
size_t n;
const char *s;
const char *algostr;
size_t algostrlen;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is not yet used by device mapper. */
err = check_blockdev (devname, 0);
if (err)
goto leave;
/* Compute the number of blocks and compare them to the value
provided as meta data. */
err = sh_blockdev_getsz (devname, &nblocks);
if (err)
{
log_error ("error getting size of '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
err = find_tuple_uint (keyblob, KEYBLOB_TAG_CONT_NSEC, &nblocks2);
if (err)
{
log_error ("error getting size from keyblob: %s\n", gpg_strerror (err));
goto leave;
}
if (nblocks != nblocks2)
{
log_error ("inconsistent size of container: expected==%llu got=%llu\n",
nblocks2, nblocks);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (nblocks <= HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS)
{
log_error ("device '%s' is too small (min=%d blocks)\n",
devname,
HEADER_SECTORS + MIN_ENCRYPTED_SPACE + FOOTER_SECTORS);
err = gpg_error (GPG_ERR_TOO_SHORT);
goto leave;
}
nblocks -= HEADER_SECTORS + FOOTER_SECTORS;
err = find_tuple_uint (keyblob, KEYBLOB_TAG_ENC_NSEC, &nblocks2);
if (err)
{
log_error ("error getting enc size from keyblob: %s\n",
gpg_strerror (err));
goto leave;
}
if (nblocks != nblocks2)
{
log_error ("inconsistent size of enc data: expected==%llu got=%llu\n",
nblocks2, nblocks);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Check that the offset is consistent. */
err = find_tuple_uint (keyblob, KEYBLOB_TAG_ENC_OFF, &nblocks2);
if (err)
{
log_error ("error getting enc offset from keyblob: %s\n",
gpg_strerror (err));
goto leave;
}
if (nblocks2 != HEADER_SECTORS)
{
log_error ("inconsistent offset of enc data: expected==%llu got=%d\n",
nblocks2, HEADER_SECTORS);
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
targetname = strrchr (targetname_abs, '/');
if (!targetname)
BUG ();
targetname++;
/* Get the algorithm string. */
algostr = find_tuple (keyblob, KEYBLOB_TAG_ALGOSTR, &algostrlen);
if (!algostr || algostrlen > 100)
{
log_error ("algo string not found in keyblob or too long\n");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Get the key. */
s = find_tuple (keyblob, KEYBLOB_TAG_ENCKEY, &n);
if (!s || n != 16)
{
if (!s)
log_error ("no key found in keyblob\n");
else
log_error ("unexpected size of key (%zu)\n", n);
err = gpg_error (GPG_ERR_INV_KEYLEN);
goto leave;
}
bin2hex (s, 16, hexkey);
/* Build dmcrypt table. */
table = es_bsprintf ("0 %llu crypt %.*s %s 0 %s %d",
nblocks, (int)algostrlen, algostr,
hexkey, devname, HEADER_SECTORS);
wipememory (hexkey, sizeof hexkey);
if (!table)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Load the table. */
{
const char *argv[3];
argv[0] = "create";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup create %s\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, table, &result, NULL);
}
if (err)
{
log_error ("error running dmsetup for '%s': %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
g13_status (ctrl, STATUS_PLAINDEV, targetname_abs, NULL);
/* Mount if a mountpoint has been given. */
if (!nomount && ctrl->devti->mountpoint)
{
const char *argv[3];
argv[0] = targetname_abs;
argv[1] = ctrl->devti->mountpoint;
argv[2] = NULL;
log_debug ("now running \"mount %s %s\"\n",
targetname_abs, ctrl->devti->mountpoint);
err = gnupg_exec_tool ("/bin/mount", argv, NULL, &result, NULL);
if (err)
{
log_error ("error running mount: %s\n", gpg_strerror (err));
goto leave;
}
if (result && *result) /* (We should not see output to stdout). */
log_info ("WARNING: mount returned data on stdout! (%s)\n", result);
}
leave:
wipememory (hexkey, sizeof hexkey);
if (table)
{
wipememory (table, strlen (table));
xfree (table);
}
xfree (targetname_abs);
xfree (result);
return err;
}
/* Unmount a DM-Crypt container on device DEVNAME and wipe the keys. */
gpg_error_t
sh_dmcrypt_umount_container (ctrl_t ctrl, const char *devname)
{
gpg_error_t err;
char *targetname_abs = NULL;
char *result = NULL;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is used by device mapper. */
err = check_blockdev (devname, 1);
if (gpg_err_code (err) != GPG_ERR_EBUSY)
{
log_error ("device '%s' is not used by the device mapper: %s\n",
devname, gpg_strerror (err));
goto leave;
}
/* Fixme: Check that this is really a g13 partition. */
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Run the regular umount command but first test with findmnt. */
{
const char *argv[3];
argv[0] = targetname_abs;
argv[1] = NULL;
log_debug ("now running \"findmnt %s\"\n", targetname_abs);
err = gnupg_exec_tool ("/bin/findmnt", argv, NULL, &result, NULL);
if (err)
log_info ("Note: device was not mounted\n");
else
{
xfree (result);
result = NULL;
argv[0] = targetname_abs;
argv[1] = NULL;
log_debug ("now running \"umount %s\"\n", targetname_abs);
err = gnupg_exec_tool ("/bin/umount", argv, NULL, &result, NULL);
if (err)
{
log_error ("error running umount: %s\n", gpg_strerror (err));
if (1)
{
/* Try to show some info about processes using the partition. */
argv[0] = "-mv";
argv[1] = targetname_abs;
argv[2] = NULL;
gnupg_exec_tool ("/bin/fuser", argv, NULL, &result, NULL);
}
goto leave;
}
if (result && *result) /* (We should not see output to stdout). */
log_info ("WARNING: umount returned data on stdout! (%s)\n", result);
}
}
xfree (result);
result = NULL;
/* Run the dmsetup remove command. */
{
const char *argv[3];
argv[0] = "remove";
argv[1] = targetname_abs;
argv[2] = NULL;
log_debug ("now running \"dmsetup remove %s\"\n", targetname_abs);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup remove %s\": %s\n",
targetname_abs, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
leave:
xfree (targetname_abs);
xfree (result);
return err;
}
/* Suspend a DM-Crypt container on device DEVNAME and wipe the keys. */
gpg_error_t
sh_dmcrypt_suspend_container (ctrl_t ctrl, const char *devname)
{
gpg_error_t err;
char *targetname_abs = NULL;
const char *targetname;
char *result = NULL;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is used by device mapper. */
err = check_blockdev (devname, 1);
if (gpg_err_code (err) != GPG_ERR_EBUSY)
{
log_error ("device '%s' is not used by the device mapper: %s\n",
devname, gpg_strerror (err));
goto leave;
}
/* Fixme: Check that this is really a g13 partition. */
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
targetname = strrchr (targetname_abs, '/');
if (!targetname)
BUG ();
targetname++;
/* Send the suspend command. */
{
const char *argv[3];
argv[0] = "suspend";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup suspend %s\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup suspend %s\": %s\n",
targetname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
/* Send the wipe key command. */
{
const char *argv[5];
argv[0] = "message";
argv[1] = targetname;
argv[2] = "0";
argv[3] = "key wipe";
argv[4] = NULL;
log_debug ("now running \"dmsetup message %s 0 key wipe\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup message %s 0 key wipe\": %s\n",
targetname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
leave:
xfree (targetname_abs);
xfree (result);
return err;
}
/* Resume a DM-Crypt container on device DEVNAME taking keys and other
* meta data from KEYBLOB. */
gpg_error_t
sh_dmcrypt_resume_container (ctrl_t ctrl, const char *devname,
tupledesc_t keyblob)
{
gpg_error_t err;
char *targetname_abs = NULL;
const char *targetname;
char hexkey[8+16*2+1]; /* 8 is used to prepend "key set ". */
char *table = NULL;
char *result = NULL;
size_t n;
const char *s;
const char *algostr;
size_t algostrlen;
if (!ctrl->devti)
return gpg_error (GPG_ERR_INV_ARG);
g13_syshelp_i_know_what_i_am_doing ();
/* Check that the device is used by device mapper. */
err = check_blockdev (devname, 1);
if (gpg_err_code (err) != GPG_ERR_EBUSY)
{
log_error ("device '%s' is not used by the device mapper: %s\n",
devname, gpg_strerror (err));
goto leave;
}
/* Device mapper needs a name for the device: Take it from the label
or use "0". */
targetname_abs = strconcat ("/dev/mapper/",
"g13-", ctrl->client.uname, "-",
ctrl->devti->label? ctrl->devti->label : "0",
NULL);
if (!targetname_abs)
{
err = gpg_error_from_syserror ();
goto leave;
}
targetname = strrchr (targetname_abs, '/');
if (!targetname)
BUG ();
targetname++;
/* Get the algorithm string. */
algostr = find_tuple (keyblob, KEYBLOB_TAG_ALGOSTR, &algostrlen);
if (!algostr || algostrlen > 100)
{
log_error ("algo string not found in keyblob or too long\n");
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
/* Get the key. */
s = find_tuple (keyblob, KEYBLOB_TAG_ENCKEY, &n);
if (!s || n != 16)
{
if (!s)
log_error ("no key found in keyblob\n");
else
log_error ("unexpected size of key (%zu)\n", n);
err = gpg_error (GPG_ERR_INV_KEYLEN);
goto leave;
}
strcpy (hexkey, "key set ");
bin2hex (s, 16, hexkey+8);
/* Send the key */
{
const char *argv[4];
argv[0] = "message";
argv[1] = targetname;
argv[2] = "0";
argv[3] = NULL;
log_debug ("now running \"dmsetup message %s 0 [key set]\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, hexkey, &result, NULL);
}
wipememory (hexkey, sizeof hexkey);
if (err)
{
log_error ("error running \"dmsetup message %s 0 [key set]\": %s\n",
devname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
/* Send the resume command. */
{
const char *argv[3];
argv[0] = "resume";
argv[1] = targetname;
argv[2] = NULL;
log_debug ("now running \"dmsetup resume %s\"\n", targetname);
err = gnupg_exec_tool ("/sbin/dmsetup", argv, NULL, &result, NULL);
}
if (err)
{
log_error ("error running \"dmsetup resume %s\": %s\n",
targetname, gpg_strerror (err));
goto leave;
}
if (result && *result)
log_debug ("dmsetup result: %s\n", result);
xfree (result);
result = NULL;
leave:
wipememory (hexkey, sizeof hexkey);
if (table)
{
wipememory (table, strlen (table));
xfree (table);
}
xfree (targetname_abs);
xfree (result);
return err;
}
diff --git a/kbx/backend-sqlite.c b/kbx/backend-sqlite.c
index ec891da6d..2398aa77f 100644
--- a/kbx/backend-sqlite.c
+++ b/kbx/backend-sqlite.c
@@ -1,1873 +1,1873 @@
/* backend-sqlite.c - SQLite based backend for keyboxd
* Copyright (C) 2019, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <sqlite3.h>
#include <npth.h>
#include "keyboxd.h"
#include "../common/i18n.h"
#include "../common/mbox-util.h"
#include "backend.h"
#include "keybox-search-desc.h"
#include "keybox-defs.h" /* (for the openpgp parser) */
/* Our definition of the backend handle. */
struct backend_handle_s
{
enum database_types db_type; /* Always DB_TYPE_SQLITE. */
unsigned int backend_id; /* Always the id of the backend. */
char filename[1];
};
/* Definition of local request data. */
struct be_sqlite_local_s
{
/* The statement object of the current select command. */
sqlite3_stmt *select_stmt;
/* The column numbers for UIDNO and SUBKEY or 0. */
int select_col_uidno;
int select_col_subkey;
/* The search mode represented by the current select command. */
KeydbSearchMode select_mode;
/* The flags active when the select was first done. */
unsigned int filter_opgp : 1;
unsigned int filter_x509 : 1;
/* Flag indicating that LASTUBID has a value. */
unsigned int lastubid_valid : 1;
/* The current description index. */
unsigned int descidx;
/* The select statement has been executed with success. */
int select_done;
/* The last row has already been reached. */
int select_eof;
/* The last UBID found by a select; only valid if LASTUBID_VALID is
* set. This is required to return only one blob in case a search
* is done over the user id and the same user id occurs several
* times in a blob. */
unsigned char lastubid[UBID_LEN];
};
/* The Mutex we use to protect all our SQLite calls. */
static npth_mutex_t database_mutex = NPTH_MUTEX_INITIALIZER;
/* The one and only database handle. */
static sqlite3 *database_hd;
/* A lockfile used make sure only we are accessing the database. */
static dotlock_t database_lock;
/* The version of our current database schema. */
#define DATABASE_VERSION 1
/* Table definitions for the database. */
static struct
{
const char *sql;
int special;
} table_definitions[] =
{
{ "PRAGMA foreign_keys = ON" },
/* Table to store config values:
* Standard name value pairs:
* dbversion = 1
* created = <ISO time string>
*/
{ "CREATE TABLE IF NOT EXISTS config ("
"name TEXT NOT NULL UNIQUE,"
"value TEXT NOT NULL "
")", 1 },
/* The actual data; either X.509 certificates or OpenPGP
* keyblocks. */
{ "CREATE TABLE IF NOT EXISTS pubkey ("
/* The 20 octet truncated primary-fpr */
"ubid BLOB NOT NULL PRIMARY KEY,"
/* The type of the public key: 1 = openpgp, 2 = X.509. */
"type INTEGER NOT NULL,"
/* The Ephemeral flag as used by gpgsm. Values: 0 or 1. */
"ephemeral INTEGER NOT NULL DEFAULT 0,"
/* The Revoked flag as set by gpgsm. Values: 0 or 1. */
"revoked INTEGER NOT NULL DEFAULT 0,"
/* The OpenPGP keyblock or X.509 certificate. */
"keyblob BLOB NOT NULL"
")" },
/* Table with fingerprints and keyids of OpenPGP and X.509 keys.
* It is also used for the primary key and the X.509 fingerprint
* because we want to be able to use the keyid and keygrip. */
{ "CREATE TABLE IF NOT EXISTS fingerprint ("
/* The fingerprint, for OpenPGP either 20 octets or 32 octets;
* for X.509 it is the same as the UBID. */
"fpr BLOB NOT NULL PRIMARY KEY,"
/* The long keyid as a 64 bit blob. */
"kid BLOB NOT NULL,"
/* The keygrip for this key. */
"keygrip BLOB NOT NULL,"
/* 0 = primary or X.509, > 0 = subkey. Also used as
* order number for the keys similar to uidno. */
"subkey INTEGER NOT NULL,"
/* The Unique Blob ID (possibly truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
/* Indices for the fingerprint table. */
{ "CREATE INDEX IF NOT EXISTS fingerprintidx0 on fingerprint (ubid)" },
{ "CREATE INDEX IF NOT EXISTS fingerprintidx1 on fingerprint (fpr)" },
{ "CREATE INDEX IF NOT EXISTS fingerprintidx2 on fingerprint (keygrip)" },
/* Table to allow fast access via user ids or mail addresses. */
{ "CREATE TABLE IF NOT EXISTS userid ("
/* The full user id - for X.509 the Subject or altSubject. */
"uid TEXT NOT NULL,"
/* The mail address if available or NULL. */
"addrspec TEXT,"
/* The type of the public key: 1 = openpgp, 2 = X.509. */
"type INTEGER NOT NULL,"
/* The order number of the user id within the keyblock or
* certificates. For X.509 0 is reserved for the issuer, 1 the
* subject, 2 and up the altSubjects. For OpenPGP this starts
* with 1 for the first user id in the keyblock. */
"uidno INTEGER NOT NULL,"
/* The Unique Blob ID (possibly truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
/* Indices for the userid table. */
{ "CREATE INDEX IF NOT EXISTS userididx0 on userid (ubid)" },
{ "CREATE INDEX IF NOT EXISTS userididx1 on userid (uid)" },
{ "CREATE INDEX IF NOT EXISTS userididx3 on userid (addrspec)" },
/* Table to allow fast access via s/n + issuer DN (X.509 only). */
{ "CREATE TABLE IF NOT EXISTS issuer ("
/* The hex encoded S/N. */
"sn TEXT NOT NULL,"
/* The RFC2253 issuer DN. */
"dn TEXT NOT NULL,"
/* The Unique Blob ID (usually the truncated fingerprint). */
"ubid BLOB NOT NULL REFERENCES pubkey"
")" },
{ "CREATE INDEX IF NOT EXISTS issueridx1 on issuer (dn)" }
};
/*-- prototypes --*/
static gpg_error_t get_config_value (const char *name, char **r_value);
static gpg_error_t set_config_value (const char *name, const char *value);
/* Take a lock for accessing SQLite. */
static void
acquire_mutex (void)
{
int res = npth_mutex_lock (&database_mutex);
if (res)
log_fatal ("failed to acquire database lock: %s\n",
gpg_strerror (gpg_error_from_errno (res)));
}
/* Release a lock. */
static void
release_mutex (void)
{
int res = npth_mutex_unlock (&database_mutex);
if (res)
log_fatal ("failed to release database db lock: %s\n",
gpg_strerror (gpg_error_from_errno (res)));
}
static void
show_sqlstr (const char *sqlstr)
{
if (!opt.verbose)
return;
log_info ("(SQL: %s)\n", sqlstr);
}
static void
show_sqlstmt (sqlite3_stmt *stmt)
{
char *p;
if (!opt.verbose)
return;
p = sqlite3_expanded_sql (stmt);
if (p)
log_info ("(SQL: %s)\n", p);
sqlite3_free (p);
}
static gpg_error_t
diag_prepare_err (int res, const char *sqlstr)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstr (sqlstr);
log_error ("error preparing SQL statement: %s\n", sqlite3_errstr (res));
return err;
}
static gpg_error_t
diag_bind_err (int res, sqlite3_stmt *stmt)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error binding a value to an SQL statement: %s\n",
sqlite3_errstr (res));
return err;
}
static gpg_error_t
diag_step_err (int res, sqlite3_stmt *stmt)
{
gpg_error_t err;
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error executing SQL statement: %s\n", sqlite3_errstr (res));
return err;
}
/* We store the keyid in the database as an 8 byte blob. This
* function converts it from the usual u32[2] array. BUFFER is a
* caller provided buffer of at least 8 bytes; a pointer to that
* buffer is the return value. */
static GPGRT_INLINE unsigned char *
kid_from_u32 (u32 *keyid, unsigned char *buffer)
{
buffer[0] = keyid[0] >> 24;
buffer[1] = keyid[0] >> 16;
buffer[2] = keyid[0] >> 8;
buffer[3] = keyid[0];
buffer[4] = keyid[1] >> 24;
buffer[5] = keyid[1] >> 16;
buffer[6] = keyid[1] >> 8;
buffer[7] = keyid[1];
return buffer;
}
/* Run an SQL reset on STMT. */
static gpg_error_t
run_sql_reset (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
res = sqlite3_reset (stmt);
if (res)
{
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error executing SQL reset: %s\n", sqlite3_errstr (res));
}
else
err = 0;
return err;
}
/* Run an SQL prepare for SQLSTR and return a statement at R_STMT. If
* EXTRA or EXTRA2 are not NULL these parts are appended to the SQL
* statement. */
static gpg_error_t
run_sql_prepare (const char *sqlstr, const char *extra, const char *extra2,
sqlite3_stmt **r_stmt)
{
gpg_error_t err;
int res;
char *buffer = NULL;
if (extra || extra2)
{
buffer = strconcat (sqlstr, extra?extra:"", extra2, NULL);
if (!buffer)
return gpg_error_from_syserror ();
sqlstr = buffer;
}
res = sqlite3_prepare_v2 (database_hd, sqlstr, -1, r_stmt, NULL);
if (res)
err = diag_prepare_err (res, sqlstr);
else
err = 0;
xfree (buffer);
return err;
}
/* Helper to bind a BLOB parameter to a statement. */
static gpg_error_t
run_sql_bind_blob (sqlite3_stmt *stmt, int no,
const void *blob, size_t bloblen)
{
gpg_error_t err;
int res;
res = sqlite3_bind_blob (stmt, no, blob, bloblen, SQLITE_TRANSIENT);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind an INTEGER parameter to a statement. */
static gpg_error_t
run_sql_bind_int (sqlite3_stmt *stmt, int no, int value)
{
gpg_error_t err;
int res;
res = sqlite3_bind_int (stmt, no, value);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. */
static gpg_error_t
run_sql_bind_ntext (sqlite3_stmt *stmt, int no,
const char *value, size_t valuelen)
{
gpg_error_t err;
int res;
res = sqlite3_bind_text (stmt, no, value, value? valuelen:0,
SQLITE_TRANSIENT);
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
return err;
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. */
static gpg_error_t
run_sql_bind_text (sqlite3_stmt *stmt, int no, const char *value)
{
return run_sql_bind_ntext (stmt, no, value, value? strlen (value):0);
}
/* Helper to bind a string parameter to a statement. VALUE is allowed
* to be NULL to bind NULL. A non-NULL VALUE is clamped with percent
* signs. */
static gpg_error_t
run_sql_bind_text_like (sqlite3_stmt *stmt, int no, const char *value)
{
gpg_error_t err;
int res;
char *buf;
if (!value)
{
res = sqlite3_bind_null (stmt, no);
buf = NULL;
}
else
{
buf = xtrymalloc (strlen (value) + 2 + 1);
if (!buf)
return gpg_error_from_syserror ();
*buf = '%';
strcpy (buf+1, value);
strcat (buf+1, "%");
res = sqlite3_bind_text (stmt, no, buf, strlen (buf), SQLITE_TRANSIENT);
}
if (res)
err = diag_bind_err (res, stmt);
else
err = 0;
xfree (buf);
return err;
}
/* Wrapper around sqlite3_step for use with simple functions. */
static gpg_error_t
run_sql_step (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
show_sqlstmt (stmt);
res = sqlite3_step (stmt);
if (res != SQLITE_DONE)
err = diag_step_err (res, stmt);
else
err = 0;
return err;
}
/* Wrapper around sqlite3_step for use with select. This version does
* not print diags for SQLITE_DONE or SQLITE_ROW but returns them as
* gpg error codes. */
static gpg_error_t
run_sql_step_for_select (sqlite3_stmt *stmt)
{
gpg_error_t err;
int res;
res = sqlite3_step (stmt);
if (res == SQLITE_DONE || res == SQLITE_ROW)
err = gpg_error (gpg_err_code_from_sqlite (res));
else
{
/* SQL_OK is unexpected for a select so in this case we return
* the OK error code by bypassing the special mapping. */
if (!res)
err = gpg_error (GPG_ERR_SQL_OK);
else
err = gpg_error (gpg_err_code_from_sqlite (res));
show_sqlstmt (stmt);
log_error ("error running SQL step: %s\n", sqlite3_errstr (res));
}
return err;
}
/* Run the simple SQL statement in SQLSTR. If UBID is not NULL this
* will be bound to ?1 in SQLSTR. This command may not be used for
* select or other command which return rows. */
static gpg_error_t
run_sql_statement_bind_ubid (const char *sqlstr, const unsigned char *ubid)
{
gpg_error_t err;
sqlite3_stmt *stmt;
err = run_sql_prepare (sqlstr, NULL, NULL, &stmt);
if (err)
goto leave;
if (ubid)
{
err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
if (err)
goto leave;
}
err = run_sql_step (stmt);
sqlite3_finalize (stmt);
if (err)
goto leave;
leave:
return err;
}
/* Run the simple SQL statement in SQLSTR. This command may not be used
* for select or other command which return rows. */
static gpg_error_t
run_sql_statement (const char *sqlstr)
{
return run_sql_statement_bind_ubid (sqlstr, NULL);
}
static int
dblock_info_cb (dotlock_t h, void *opaque, enum dotlock_reasons reason,
const char *format, ...)
{
ctrl_t ctrl = opaque;
va_list arg_ptr;
gpg_error_t err;
int rc = 0;
char tmpbuf[200];
(void)h;
if (reason == DOTLOCK_WAITING)
{
if (format)
{
va_start (arg_ptr, format);
gpgrt_vsnprintf (tmpbuf, sizeof tmpbuf, format, arg_ptr);
va_end (arg_ptr);
}
else
*tmpbuf = 0;
err = kbxd_status_printf (ctrl, "NOTE", "database_open %u %s",
gpg_error (GPG_ERR_LOCKED), tmpbuf);
if (err)
{
log_error ("sending status line failed: %s\n", gpg_strerror (err));
rc = 1; /* snprintf failed. */
}
}
return rc;
}
/* Create and initialize a new SQL database file if it does not
* exists; else open it and check that all required objects are
* available. */
static gpg_error_t
create_or_open_database (ctrl_t ctrl, const char *filename)
{
gpg_error_t err;
int res;
int idx;
char *value;
int dbversion;
int setdbversion = 0;
if (database_hd)
return 0; /* Already initialized. */
acquire_mutex ();
/* To avoid races with other temporary instances of keyboxd trying
* to create or update the database, we run the database with a lock
* file held. */
database_lock = dotlock_create (filename, DOTLOCK_PREPARE_CREATE);
if (!database_lock)
{
err = gpg_error_from_syserror ();
if (opt.verbose)
log_info ("can't allocate dotlock handle: %s\n", gpg_strerror (err));
goto leave;
}
dotlock_set_info_cb (database_lock, dblock_info_cb, ctrl);
database_lock = dotlock_finish_create (database_lock, filename);
if (!database_lock)
{
err = gpg_error_from_syserror ();
/* A reason for this to fail is that the directory is not
* writable. However, this whole locking stuff does not make
* sense if this is the case. An empty non-writable directory
* with no database is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s': %s\n",
filename, gpg_strerror (err));
goto leave;
}
if (dotlock_take (database_lock, 10000))
{
err = gpg_error_from_syserror ();
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s': %s\n", filename, gpg_strerror (err));
goto leave;
}
/* Database has not yet been opened. Open or create it, make sure
* the tables exist, and prepare the required statements. We use
* our own locking instead of the more complex serialization sqlite
* would have to do and it avoid that we call
* npth_unprotect/protect. */
res = sqlite3_open_v2 (filename,
&database_hd,
(SQLITE_OPEN_READWRITE
| SQLITE_OPEN_CREATE
| SQLITE_OPEN_NOMUTEX),
NULL);
if (res)
{
err = gpg_error (gpg_err_code_from_sqlite (res));
log_error ("error opening '%s': %s\n", filename, sqlite3_errstr (res));
goto leave;
}
/* Enable extended error codes. */
sqlite3_extended_result_codes (database_hd, 1);
/* Create the tables if needed. */
for (idx=0; idx < DIM(table_definitions); idx++)
{
err = run_sql_statement (table_definitions[idx].sql);
if (err)
goto leave;
if (table_definitions[idx].special == 1)
{
/* Check and create dbversion etc entries. */
err = get_config_value ("dbversion", &value);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
dbversion = 0;
setdbversion = 1;
}
else if (err)
{
log_error ("error reading database version: %s\n",
gpg_strerror (err));
err = 0;
dbversion = 0;
}
else if ((dbversion = atoi (value)) < 1)
{
log_error ("database version %d is not valid\n", dbversion);
dbversion = 0;
}
log_info ("database version: %d\n", dbversion);
xfree (value);
err = get_config_value ("created", &value);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
log_info ("database created: %.50s\n", "[unknown]");
else if (err)
log_error ("error getting database creation date: %s\n",
gpg_strerror (err));
else
log_info ("database created: %.50s\n", value);
xfree (value);
value = NULL;
}
}
if (!opt.quiet)
log_info (_("database '%s' created\n"), filename);
if (setdbversion)
{
err = set_config_value ("dbversion", STR2(DATABASE_VERSION));
if (!err)
err = set_config_value ("created", isotimestamp (gnupg_get_time ()));
}
err = 0;
leave:
if (err)
{
log_error (_("error creating database '%s': %s\n"),
filename, gpg_strerror (err));
if (dotlock_is_locked (database_lock))
dotlock_release (database_lock);
dotlock_destroy (database_lock);
database_lock = NULL;
}
release_mutex ();
return err;
}
/* Install a new resource and return a handle for that backend. */
gpg_error_t
be_sqlite_add_resource (ctrl_t ctrl, backend_handle_t *r_hd,
const char *filename, int readonly)
{
backend_handle_t hd;
(void)ctrl;
(void)readonly; /* FIXME: implement read-only mode. */
*r_hd = NULL;
hd = xtrycalloc (1, sizeof *hd + strlen (filename));
if (!hd)
return gpg_error_from_syserror ();
hd->db_type = DB_TYPE_SQLITE;
strcpy (hd->filename, filename);
hd->backend_id = be_new_backend_id ();
*r_hd = hd;
return 0;
}
/* Release the backend handle HD and all its resources. HD is not
* valid after a call to this function. */
void
be_sqlite_release_resource (ctrl_t ctrl, backend_handle_t hd)
{
(void)ctrl;
if (!hd)
return;
hd->db_type = DB_TYPE_NONE;
xfree (hd);
}
/* Helper for be_find_request_part to initialize a sqlite request part. */
gpg_error_t
be_sqlite_init_local (backend_handle_t backend_hd, db_request_part_t part)
{
(void)backend_hd;
part->besqlite = xtrycalloc (1, sizeof *part->besqlite);
if (!part->besqlite)
return gpg_error_from_syserror ();
return 0;
}
/* Release local data of a sqlite request part. */
void
be_sqlite_release_local (be_sqlite_local_t ctx)
{
if (ctx->select_stmt)
sqlite3_finalize (ctx->select_stmt);
xfree (ctx);
}
gpg_error_t
be_sqlite_rollback (void)
{
opt.in_transaction = 0;
if (!opt.active_transaction)
return 0; /* Nothing to do. */
if (!database_hd)
{
log_error ("Warning: No database handle for global rollback\n");
return gpg_error (GPG_ERR_INTERNAL);
}
opt.active_transaction = 0;
return run_sql_statement ("rollback");
}
gpg_error_t
be_sqlite_commit (void)
{
opt.in_transaction = 0;
if (!opt.active_transaction)
return 0; /* Nothing to do. */
if (!database_hd)
{
log_error ("Warning: No database handle for global commit\n");
return gpg_error (GPG_ERR_INTERNAL);
}
opt.active_transaction = 0;
return run_sql_statement ("commit");
}
/* Return a value from the config table. NAME most not have quotes
* etc. If no error is returned the caller must xfree the value
* stored at R_VALUE. On error NULL is stored there. */
static gpg_error_t
get_config_value (const char *name, char **r_value)
{
gpg_error_t err;
sqlite3_stmt *stmt;
char *sqlstr;
const char *s;
*r_value = NULL;
sqlstr = strconcat ("SELECT value FROM config WHERE name='", name, "'", NULL);
if (!sqlstr)
return gpg_error_from_syserror ();
err = run_sql_prepare (sqlstr, NULL, NULL, &stmt);
xfree (sqlstr);
if (err)
return err;
err = run_sql_step_for_select (stmt);
if (gpg_err_code (err) == GPG_ERR_SQL_ROW)
{
s = sqlite3_column_text (stmt, 0);
*r_value = xtrystrdup (s? s : "");
if (!*r_value)
err = gpg_error_from_syserror ();
else
err = 0;
}
else if (gpg_err_code (err) == GPG_ERR_SQL_DONE)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
log_assert (err); /* We'll never see 0 here. */
sqlite3_finalize (stmt);
return err;
}
/* Insert or update a value in the config table. */
static gpg_error_t
set_config_value (const char *name, const char *value)
{
gpg_error_t err;
sqlite3_stmt *stmt;
err = run_sql_prepare ("INSERT OR REPLACE INTO config(name,value)"
" VALUES(?1,?2)", NULL, NULL, &stmt);
if (err)
return err;
err = run_sql_bind_text (stmt, 1, name);
if (!err)
err = run_sql_bind_text (stmt, 2, value);
if (!err)
err = run_sql_step (stmt);
sqlite3_finalize (stmt);
return err;
}
/* Run a select for the search given by (DESC,NDESC). The data is not
* returned but stored in the request item. */
static gpg_error_t
run_select_statement (ctrl_t ctrl, be_sqlite_local_t ctx,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err = 0;
unsigned int descidx;
const char *extra = NULL;
unsigned char kidbuf[8];
const char *s;
size_t n;
descidx = ctx->descidx;
if (descidx >= ndesc)
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
- /* Check whether we can re-use the current select statement. */
+ /* Check whether we can reuse the current select statement. */
if (!ctx->select_stmt)
;
else if (ctx->select_mode != desc[descidx].mode)
{
sqlite3_finalize (ctx->select_stmt);
ctx->select_stmt = NULL;
}
else if (ctx->filter_opgp != ctrl->filter_opgp
|| ctx->filter_x509 != ctrl->filter_x509)
{
/* The filter flags changed, thus we can't reuse the statement. */
sqlite3_finalize (ctx->select_stmt);
ctx->select_stmt = NULL;
}
ctx->select_mode = desc[descidx].mode;
ctx->filter_opgp = ctrl->filter_opgp;
ctx->filter_x509 = ctrl->filter_x509;
/* Prepare the select and bind the parameters. */
if (ctx->select_stmt)
{
err = run_sql_reset (ctx->select_stmt);
if (err)
goto leave;
}
else
{
if (ctx->filter_opgp && ctx->filter_x509)
extra = " AND ( p.type = 1 OR p.type = 2 )";
else if (ctx->filter_opgp && !ctx->filter_x509)
extra = " AND p.type = 1";
else if (!ctx->filter_opgp && ctx->filter_x509)
extra = " AND p.type = 2";
err = 0;
}
ctx->select_col_uidno = ctx->select_col_subkey = 0;
switch (desc[descidx].mode)
{
case KEYDB_SEARCH_MODE_NONE:
never_reached ();
err = gpg_error (GPG_ERR_INTERNAL);
break;
case KEYDB_SEARCH_MODE_EXACT:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.uid = ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1, desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.addrspec = ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
{
s = desc[descidx].u.name;
if (s && *s == '<' && s[1])
{ /* It is common that the indicator for exact addrspec
* search has not been removed. We do this here. */
s++;
n = strlen (s);
if (n > 1 && s[n-1] == '>')
n--;
}
else
n = s? strlen (s):0;
err = run_sql_bind_ntext (ctx->select_stmt, 1, s, n);
}
break;
case KEYDB_SEARCH_MODE_MAILSUB:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.addrspec LIKE ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_text_like (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid AND u.uid LIKE ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_text_like (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
case KEYDB_SEARCH_MODE_WORDS:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
case KEYDB_SEARCH_MODE_ISSUER:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob"
" FROM pubkey as p, issuer as i"
" WHERE p.ubid = i.ubid"
" AND i.dn = $1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
if (!desc[descidx].snhex)
{
/* We should never get a binary S/N here. */
log_debug ("%s: issuer_sn with binary s/n\n", __func__);
err = gpg_error (GPG_ERR_INTERNAL);
}
else
{
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob"
" FROM pubkey as p, issuer as i"
" WHERE p.ubid = i.ubid"
" AND i.sn = $1 AND i.dn = $2",
extra, " ORDER BY p.ubid",
&ctx->select_stmt);
if (!err)
err = run_sql_bind_ntext (ctx->select_stmt, 1,
desc[descidx].sn, desc[descidx].snlen);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 2,
desc[descidx].u.name);
}
break;
case KEYDB_SEARCH_MODE_SN:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
/* if (has_sn (blob, sn_array? sn_array[n].sn : desc[n].sn, */
/* sn_array? sn_array[n].snlen : desc[n].snlen)) */
/* goto found; */
break;
case KEYDB_SEARCH_MODE_SUBJECT:
ctx->select_col_uidno = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, u.uidno"
" FROM pubkey as p, userid as u"
" WHERE p.ubid = u.ubid"
" AND u.uid = $1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_text (ctx->select_stmt, 1,
desc[descidx].u.name);
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND"
" substr(f.kid,5) = ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
kid_from_u32 (desc[descidx].u.kid, kidbuf)+4,
4);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.kid = ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
kid_from_u32 (desc[descidx].u.kid, kidbuf),
8);
break;
case KEYDB_SEARCH_MODE_FPR:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral,"
" p.revoked, p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.fpr = ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.fpr, desc[descidx].fprlen);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
ctx->select_col_subkey = 5;
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT p.ubid, p.type, p.ephemeral, p.revoked,"
" p.keyblob, f.subkey"
" FROM pubkey as p, fingerprint as f"
" WHERE p.ubid = f.ubid AND f.keygrip = ?1",
extra, " ORDER BY p.ubid", &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.grip, KEYGRIP_LEN);
break;
case KEYDB_SEARCH_MODE_UBID:
if (!ctx->select_stmt)
err = run_sql_prepare ("SELECT ubid, type, ephemeral, revoked, keyblob"
" FROM pubkey as p"
" WHERE ubid = ?1",
extra, NULL, &ctx->select_stmt);
if (!err)
err = run_sql_bind_blob (ctx->select_stmt, 1,
desc[descidx].u.ubid, UBID_LEN);
break;
case KEYDB_SEARCH_MODE_FIRST:
if (!ctx->select_stmt)
{
if (ctx->filter_opgp && ctx->filter_x509)
extra = " WHERE ( p.type = 1 OR p.type = 2 ) ORDER by ubid";
else if (ctx->filter_opgp && !ctx->filter_x509)
extra = " WHERE p.type = 1 ORDER by ubid";
else if (!ctx->filter_opgp && ctx->filter_x509)
extra = " WHERE p.type = 2 ORDER by ubid";
else
extra = " ORDER by ubid";
err = run_sql_prepare ("SELECT ubid, type, ephemeral, revoked,"
" keyblob"
" FROM pubkey as p",
extra, NULL, &ctx->select_stmt);
}
break;
case KEYDB_SEARCH_MODE_NEXT:
err = gpg_error (GPG_ERR_INTERNAL);
break;
default:
err = gpg_error (GPG_ERR_INV_VALUE);
break;
}
leave:
return err;
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. */
gpg_error_t
be_sqlite_search (ctrl_t ctrl,
backend_handle_t backend_hd, db_request_t request,
KEYDB_SEARCH_DESC *desc, unsigned int ndesc)
{
gpg_error_t err;
db_request_part_t part;
be_sqlite_local_t ctx;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
err = create_or_open_database (ctrl, backend_hd->filename);
if (err)
return err;
acquire_mutex ();
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
ctx = part->besqlite;
if (!desc)
{
/* Reset */
ctx->select_done = 0;
ctx->select_eof = 0;
ctx->descidx = 0;
ctx->lastubid_valid = 0;
err = 0;
goto leave;
}
if (ctx->select_eof)
{
/* Still in EOF state. */
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
/* Start a global transaction if needed. */
if (!opt.active_transaction && opt.in_transaction)
{
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
opt.active_transaction = 1;
}
again:
if (!ctx->select_done)
{
/* Initial search - run the select. */
err = run_select_statement (ctrl, ctx, desc, ndesc);
if (err)
goto leave;
ctx->select_done = 1;
}
show_sqlstmt (ctx->select_stmt);
/* SQL select succeeded - get the first or next row. */
err = run_sql_step_for_select (ctx->select_stmt);
if (gpg_err_code (err) == GPG_ERR_SQL_ROW)
{
int n;
const void *ubid, *keyblob;
size_t keybloblen;
enum pubkey_types pubkey_type;
int is_ephemeral, is_revoked;
int pk_no, uid_no;
ubid = sqlite3_column_blob (ctx->select_stmt, 0);
n = sqlite3_column_bytes (ctx->select_stmt, 0);
if (!ubid || n < 0)
{
if (!ubid && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
else
err = gpg_error (GPG_ERR_DB_CORRUPTED);
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column UBID: No column (n=%d)\n",n);
goto leave;
}
if (n != UBID_LEN)
{
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column UBID: Bad value (n=%d)\n",n);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (ctx->lastubid_valid && !memcmp (ctx->lastubid, ubid, UBID_LEN))
{
/* The search has already returned this blob and thus we may
* not return this again. Consider the case that we are
* searching for user id "foo" and a keyblock or certificate
* has several userids with "foo" in it (or with even a full
* mail address in it but with other extra parts). The code
* in gpg and gpgsm expects to see only a single block and
* not several of them. Whether the UIDNO makes any sense
* in this case is questionable and we ignore that because
* we currently are not able to return several UIDNOs. */
goto again;
}
memcpy (ctx->lastubid, ubid, UBID_LEN);
ctx->lastubid_valid = 1;
n = sqlite3_column_int (ctx->select_stmt, 1);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column TYPE: %s)\n",
gpg_strerror (err));
goto leave;
}
pubkey_type = n;
n = sqlite3_column_int (ctx->select_stmt, 2);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column EPHEMERAL: %s)\n",
gpg_strerror (err));
goto leave;
}
is_ephemeral = !!n;
n = sqlite3_column_int (ctx->select_stmt, 3);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column REVOKED: %s)\n",
gpg_strerror (err));
goto leave;
}
is_revoked = !!n;
keyblob = sqlite3_column_blob (ctx->select_stmt, 4);
n = sqlite3_column_bytes (ctx->select_stmt, 4);
if (!keyblob || n < 0)
{
if (!keyblob && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
else
err = gpg_error (GPG_ERR_DB_CORRUPTED);
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column KEYBLOB: %s\n",
gpg_strerror (err));
goto leave;
}
keybloblen = n;
if (ctx->select_col_uidno)
{
n = sqlite3_column_int (ctx->select_stmt, ctx->select_col_uidno);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column UIDNO: %s)\n",
gpg_strerror (err));
uid_no = 0;
}
else if (n < 0)
uid_no = 0;
else
uid_no = n + 1;
}
else
uid_no = 0;
if (ctx->select_col_subkey)
{
n = sqlite3_column_int (ctx->select_stmt, ctx->select_col_subkey);
if (!n && sqlite3_errcode (database_hd) == SQLITE_NOMEM)
{
err = gpg_error (gpg_err_code_from_sqlite (SQLITE_NOMEM));
show_sqlstmt (ctx->select_stmt);
log_error ("error in returned SQL column SUBKEY: %s)\n",
gpg_strerror (err));
goto leave;
}
else if (n < 0)
pk_no = 0;
else
pk_no = n + 1;
}
else
pk_no = 0;
err = be_return_pubkey (ctrl, keyblob, keybloblen, pubkey_type,
ubid, is_ephemeral, is_revoked, uid_no, pk_no);
if (!err)
be_cache_pubkey (ctrl, ubid, keyblob, keybloblen, pubkey_type);
}
else if (gpg_err_code (err) == GPG_ERR_SQL_DONE)
{
if (++ctx->descidx < ndesc)
{
ctx->select_done = 0;
goto again;
}
err = gpg_error (GPG_ERR_EOF);
ctx->select_eof = 1;
}
else
{
log_assert (err);
}
leave:
release_mutex ();
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the pubkey
* table. */
static gpg_error_t
store_into_pubkey (enum kbxd_store_modes mode,
enum pubkey_types pktype, const unsigned char *ubid,
const void *blob, size_t bloblen)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
if (mode == KBXD_STORE_UPDATE)
sqlstr = ("UPDATE pubkey set keyblob = ?3, type = ?2 WHERE ubid = ?1");
else if (mode == KBXD_STORE_INSERT)
sqlstr = ("INSERT INTO pubkey(ubid,type,keyblob) VALUES(?1,?2,?3)");
else /* Auto */
sqlstr = ("INSERT OR REPLACE INTO pubkey(ubid,type,keyblob)"
" VALUES(?1,?2,?3)");
err = run_sql_prepare (sqlstr, NULL, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 1, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 2, (int)pktype);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, blob, bloblen);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the
* fingerprint table. */
static gpg_error_t
store_into_fingerprint (const unsigned char *ubid, int subkey,
const unsigned char *keygrip,
const unsigned char *kid,
const unsigned char *fpr, int fprlen)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
sqlstr = ("INSERT OR REPLACE INTO fingerprint(fpr,kid,keygrip,subkey,ubid)"
" VALUES(?1,?2,?3,?4,?5)");
err = run_sql_prepare (sqlstr, NULL, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 1, fpr, fprlen);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 2, kid, 8);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, keygrip, KEYGRIP_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 4, subkey);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 5, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the userid
* table. If OVERRIDE_MBOX is set, that value is used instead of a
* value extracted from UID. */
static gpg_error_t
store_into_userid (const unsigned char *ubid, enum pubkey_types pktype,
const char *uid, int uidno, const char *override_mbox)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
char *addrspec = NULL;
sqlstr = ("INSERT OR REPLACE INTO userid(uid,addrspec,type,ubid,uidno)"
" VALUES(?1,?2,?3,?4,?5)");
err = run_sql_prepare (sqlstr, NULL, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_text (stmt, 1, uid);
if (err)
goto leave;
if (override_mbox)
err = run_sql_bind_text (stmt, 2, override_mbox);
else
{
addrspec = mailbox_from_userid (uid, 0);
err = run_sql_bind_text (stmt, 2, addrspec);
}
if (err)
goto leave;
err = run_sql_bind_int (stmt, 3, pktype);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 4, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_bind_int (stmt, 5, uidno);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
xfree (addrspec);
return err;
}
/* Helper for be_sqlite_store to update or insert a row in the
* issuer table. */
static gpg_error_t
store_into_issuer (const unsigned char *ubid,
const char *sn, const char *issuer)
{
gpg_error_t err;
const char *sqlstr;
sqlite3_stmt *stmt = NULL;
char *addrspec = NULL;
sqlstr = ("INSERT OR REPLACE INTO issuer(sn,dn,ubid)"
" VALUES(?1,?2,?3)");
err = run_sql_prepare (sqlstr, NULL, NULL, &stmt);
if (err)
goto leave;
err = run_sql_bind_text (stmt, 1, sn);
if (err)
goto leave;
err = run_sql_bind_text (stmt, 2, issuer);
if (err)
goto leave;
err = run_sql_bind_blob (stmt, 3, ubid, UBID_LEN);
if (err)
goto leave;
err = run_sql_step (stmt);
leave:
if (stmt)
sqlite3_finalize (stmt);
xfree (addrspec);
return err;
}
/* Store (BLOB,BLOBLEN) into the database. UBID is the UBID matching
* that blob. BACKEND_HD is the handle for this backend and REQUEST
* is the current database request object. MODE is the store
* mode. */
gpg_error_t
be_sqlite_store (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, enum kbxd_store_modes mode,
enum pubkey_types pktype, const unsigned char *ubid,
const void *blob, size_t bloblen)
{
gpg_error_t err;
db_request_part_t part;
/* be_sqlite_local_t ctx; */
int got_mutex = 0;
int in_transaction = 0;
int info_valid = 0;
struct _keybox_openpgp_info info;
ksba_cert_t cert = NULL;
char *sn = NULL;
char *dn = NULL;
char *kludge_mbox = NULL;
int uidno;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
/* Fixme: The code below is duplicated in be_ubid_from_blob - we
* should have only one function and pass the passed info around
* with the BLOB. */
if (be_is_x509_blob (blob, bloblen))
{
log_assert (pktype == PUBKEY_TYPE_X509);
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, blob, bloblen);
if (err)
goto leave;
}
else
{
err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
if (err)
{
log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
goto leave;
}
info_valid = 1;
log_assert (pktype == PUBKEY_TYPE_OPGP);
log_assert (info.primary.fprlen >= 20);
log_assert (!memcmp (ubid, info.primary.fpr, UBID_LEN));
}
acquire_mutex ();
got_mutex = 1;
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* ctx = part->besqlite; */
if (!opt.active_transaction)
{
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
if (opt.in_transaction)
opt.active_transaction = 1;
}
in_transaction = 1;
err = store_into_pubkey (mode, pktype, ubid, blob, bloblen);
if (err)
goto leave;
/* Delete all related rows so that we can freshly add possibly added
* or changed user ids and subkeys. */
err = run_sql_statement_bind_ubid
("DELETE FROM fingerprint WHERE ubid = ?1", ubid);
if (err)
goto leave;
err = run_sql_statement_bind_ubid
("DELETE FROM userid WHERE ubid = ?1", ubid);
if (err)
goto leave;
if (cert)
{
err = run_sql_statement_bind_ubid
("DELETE FROM issuer WHERE ubid = ?1", ubid);
if (err)
goto leave;
}
if (cert) /* X.509 */
{
unsigned char grip[KEYGRIP_LEN];
int idx;
err = be_get_x509_keygrip (cert, grip);
if (err)
goto leave;
/* Note that for X.509 the UBID is also the fingerprint. */
err = store_into_fingerprint (ubid, 0, grip,
ubid+12,
ubid, UBID_LEN);
if (err)
goto leave;
/* Now the issuer. */
sn = be_get_x509_serial (cert);
if (!sn)
{
err = gpg_error_from_syserror ();
goto leave;
}
dn = ksba_cert_get_issuer (cert, 0);
if (!dn)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = store_into_issuer (ubid, sn, dn);
if (err)
goto leave;
/* Loop over the subject and alternate subjects. */
uidno = 0;
for (idx=0; (xfree (dn), dn = ksba_cert_get_subject (cert, idx)); idx++)
{
/* In the case that the same email address is in the
* subject DN as well as in an alternate subject name
* we avoid printing it a second time. */
if (kludge_mbox && !strcmp (kludge_mbox, dn))
continue;
err = store_into_userid (ubid, PUBKEY_TYPE_X509, dn, ++uidno, NULL);
if (err)
goto leave;
if (!idx)
{
kludge_mbox = _keybox_x509_email_kludge (dn);
if (kludge_mbox)
{
err = store_into_userid (ubid, PUBKEY_TYPE_X509,
dn, ++uidno, kludge_mbox);
if (err)
goto leave;
}
}
} /* end loop over the subjects. */
}
else /* OpenPGP */
{
struct _keybox_openpgp_key_info *kinfo;
kinfo = &info.primary;
err = store_into_fingerprint (ubid, 0, kinfo->grip,
kinfo->keyid,
kinfo->fpr, kinfo->fprlen);
if (err)
goto leave;
if (info.nsubkeys)
{
int subkey = 1;
for (kinfo = &info.subkeys; kinfo; kinfo = kinfo->next, subkey++)
{
err = store_into_fingerprint (ubid, subkey, kinfo->grip,
kinfo->keyid,
kinfo->fpr, kinfo->fprlen);
if (err)
goto leave;
}
}
if (info.nuids)
{
struct _keybox_openpgp_uid_info *u;
uidno = 0;
u = &info.uids;
do
{
log_assert (u->off <= bloblen);
log_assert (u->off + u->len <= bloblen);
{
char *uid = xtrymalloc (u->len + 1);
if (!uid)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (uid, (const unsigned char *)blob + u->off, u->len);
uid[u->len] = 0;
/* Note that we ignore embedded zeros in the user id;
* this is what we do all over the place. */
err = store_into_userid (ubid, pktype, uid, ++uidno, NULL);
xfree (uid);
}
if (err)
goto leave;
u = u->next;
}
while (u);
}
}
leave:
if (in_transaction && !err)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else
err = run_sql_statement ("commit");
}
else if (in_transaction)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else if (run_sql_statement ("rollback"))
log_error ("Warning: database rollback failed - should not happen!\n");
}
if (got_mutex)
release_mutex ();
if (info_valid)
_keybox_destroy_openpgp_info (&info);
if (cert)
ksba_cert_release (cert);
ksba_free (dn);
xfree (sn);
xfree (kludge_mbox);
return err;
}
/* Delete the blob specified by UBID from the database. BACKEND_HD is
* the handle for this backend and REQUEST is the current database
* request object. */
gpg_error_t
be_sqlite_delete (ctrl_t ctrl, backend_handle_t backend_hd,
db_request_t request, const unsigned char *ubid)
{
gpg_error_t err;
db_request_part_t part;
/* be_sqlite_local_t ctx; */
sqlite3_stmt *stmt = NULL;
int in_transaction = 0;
(void)ctrl;
log_assert (backend_hd && backend_hd->db_type == DB_TYPE_SQLITE);
log_assert (request);
acquire_mutex ();
/* Find the specific request part or allocate it. */
err = be_find_request_part (backend_hd, request, &part);
if (err)
goto leave;
/* ctx = part->besqlite; */
if (!opt.active_transaction)
{
err = run_sql_statement ("begin transaction");
if (err)
goto leave;
if (opt.in_transaction)
opt.active_transaction = 1;
}
in_transaction = 1;
err = run_sql_statement_bind_ubid
("DELETE from userid WHERE ubid = ?1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from fingerprint WHERE ubid = ?1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from issuer WHERE ubid = ?1", ubid);
if (!err)
err = run_sql_statement_bind_ubid
("DELETE from pubkey WHERE ubid = ?1", ubid);
leave:
if (stmt)
sqlite3_finalize (stmt);
if (in_transaction && !err)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else
err = run_sql_statement ("commit");
}
else if (in_transaction)
{
if (opt.active_transaction)
; /* We are in a global transaction. */
else if (run_sql_statement ("rollback"))
log_error ("Warning: database rollback failed - should not happen!\n");
}
release_mutex ();
return err;
}
diff --git a/kbx/backend-support.c b/kbx/backend-support.c
index 4d3738064..2f0c7168a 100644
--- a/kbx/backend-support.c
+++ b/kbx/backend-support.c
@@ -1,364 +1,364 @@
/* backend-support.c - Supporting functions for the backend.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include "keyboxd.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/tlv.h"
#include "backend.h"
#include "keybox-defs.h"
/* Common definition part of all backend handle. All definitions of
* this structure must start with these fields. */
struct backend_handle_s
{
enum database_types db_type;
unsigned int backend_id;
};
/* Return a string with the name of the database type T. */
const char *
strdbtype (enum database_types t)
{
switch (t)
{
case DB_TYPE_NONE: return "none";
case DB_TYPE_CACHE:return "cache";
case DB_TYPE_KBX: return "keybox";
case DB_TYPE_SQLITE: return "sqlite";
}
return "?";
}
/* Return a new backend ID. Backend IDs are used to identify backends
* without using the actual object. The number of backend resources
* is limited because they are specified in the config file. Thus an
* overflow check is not required. */
unsigned int
be_new_backend_id (void)
{
static unsigned int last;
return ++last;
}
/* Release the backend described by HD. This is a generic function
* which dispatches to the the actual backend. */
void
be_generic_release_backend (ctrl_t ctrl, backend_handle_t hd)
{
if (!hd)
return;
switch (hd->db_type)
{
case DB_TYPE_NONE:
xfree (hd);
break;
case DB_TYPE_CACHE:
be_cache_release_resource (ctrl, hd);
break;
case DB_TYPE_KBX:
be_kbx_release_resource (ctrl, hd);
break;
case DB_TYPE_SQLITE:
be_sqlite_release_resource (ctrl, hd);
break;
default:
log_error ("%s: faulty backend handle of type %d given\n",
__func__, hd->db_type);
}
}
/* Release the request object REQ. */
void
be_release_request (db_request_t req)
{
db_request_part_t part, partn;
if (!req)
return;
for (part = req->part; part; part = partn)
{
partn = part->next;
be_kbx_release_kbx_hd (part->kbx_hd);
be_sqlite_release_local (part->besqlite);
xfree (part);
}
}
/* Given the backend handle BACKEND_HD and the REQUEST find or
* allocate a request part for that backend and store it at R_PART.
* On error R_PART is set to NULL and an error returned. */
gpg_error_t
be_find_request_part (backend_handle_t backend_hd, db_request_t request,
db_request_part_t *r_part)
{
gpg_error_t err;
db_request_part_t part;
for (part = request->part; part; part = part->next)
if (part->backend_id == backend_hd->backend_id)
break;
if (!part)
{
part = xtrycalloc (1, sizeof *part);
if (!part)
return gpg_error_from_syserror ();
part->backend_id = backend_hd->backend_id;
if (backend_hd->db_type == DB_TYPE_KBX)
{
err = be_kbx_init_request_part (backend_hd, part);
if (err)
{
xfree (part);
return err;
}
}
else if (backend_hd->db_type == DB_TYPE_SQLITE)
{
err = be_sqlite_init_local (backend_hd, part);
if (err)
{
xfree (part);
return err;
}
}
part->next = request->part;
request->part = part;
}
*r_part = part;
return 0;
}
/* Return the public key (BUFFER,BUFLEN) which has the type
* PUBKEY_TYPE to the caller. */
gpg_error_t
be_return_pubkey (ctrl_t ctrl, const void *buffer, size_t buflen,
enum pubkey_types pubkey_type, const unsigned char *ubid,
int is_ephemeral, int is_revoked, int uid_no, int pk_no)
{
gpg_error_t err;
char hexubid[2*UBID_LEN+1];
bin2hex (ubid, UBID_LEN, hexubid);
err = kbxd_status_printf (ctrl, "PUBKEY_INFO", "%d %s %c%c %d %d",
pubkey_type, hexubid,
is_ephemeral? 'e':'-',
is_revoked? 'r':'-',
uid_no, pk_no);
if (err)
goto leave;
if (ctrl->no_data_return)
err = 0;
else
err = kbxd_write_data_line (ctrl, buffer, buflen);
leave:
return err;
}
/* Return true if (BLOB/BLOBLEN) seems to be an X509 certificate. */
int
be_is_x509_blob (const unsigned char *blob, size_t bloblen)
{
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, cons, ndef;
/* An X.509 certificate can be identified by this DER encoding:
*
* 30 82 05 B8 30 82 04 A0 A0 03 02 01 02 02 07 15 46 A0 BF 30 07 39
* ----------- +++++++++++ ----- ++++++++ --------------------------
* SEQUENCE SEQUENCE [0] INTEGER INTEGER
* (tbs) (version) (s/n)
*
- * Note that v0 certificates don't have an explict version number.
+ * Note that v0 certificates don't have an explicit version number.
*/
p = blob;
n = bloblen;
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
return 0; /* Does not start with a sequence. */
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
return 0; /* No TBS sequence. */
if (n < 7 || objlen < 7)
return 0; /* Too short: [0], version and min. s/n required. */
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_CONTEXT && tag == 0 && cons))
{
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER && !cons)
- return 1; /* Might be a X.509 v0 cert with implict version. */
+ return 1; /* Might be a X.509 v0 cert with implicit version. */
return 0; /* No context tag. */
}
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER
&& !cons && objlen == 1 && n && (*p == 1 || *p == 2)))
return 0; /* Unknown X.509 version. */
p++; /* Skip version number. */
n--;
if (parse_ber_header (&p, &n, &class, &tag, &cons, &ndef, &objlen, &hdrlen))
return 0; /* Not a proper BER object. */
if (!(class == CLASS_UNIVERSAL && tag == TAG_INTEGER && !cons))
return 0; /* No s/n. */
return 1; /* Looks like an X.509 certificate. */
}
/* Return the public key type and the (primary) fingerprint for
* (BLOB,BLOBLEN). r_UBID must point to a buffer of at least UBID_LEN
* bytes, on success it receives the UBID (primary fingerprint
* truncated 20 octets). R_PKTYPE receives the public key type. */
gpg_error_t
be_ubid_from_blob (const void *blob, size_t bloblen,
enum pubkey_types *r_pktype, char *r_ubid)
{
gpg_error_t err;
if (be_is_x509_blob (blob, bloblen))
{
/* Although libksba has a dedicated function to compute the
* fingerprint we compute it here directly because we know that
* we have the entire certificate here (we checked the start of
* the blob and assume that the length is also okay). */
*r_pktype = PUBKEY_TYPE_X509;
gcry_md_hash_buffer (GCRY_MD_SHA1, r_ubid, blob, bloblen);
err = 0;
}
else
{
struct _keybox_openpgp_info info;
err = _keybox_parse_openpgp (blob, bloblen, NULL, &info);
if (err)
{
log_info ("error parsing OpenPGP blob: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
}
else
{
*r_pktype = PUBKEY_TYPE_OPGP;
log_assert (info.primary.fprlen >= 20);
memcpy (r_ubid, info.primary.fpr, UBID_LEN);
_keybox_destroy_openpgp_info (&info);
}
}
return err;
}
/* Return a certificates serial number in hex encoding. Caller must
* free the returned string. NULL is returned on error but ERRNO
* might not be set if the certificate and thus Libksba is broken. */
char *
be_get_x509_serial (ksba_cert_t cert)
{
const char *p;
unsigned long n;
char *endp;
p = (const char *)ksba_cert_get_serial (cert);
if (!p)
{
log_debug ("oops: Libksba returned a certificate w/o a serial\n");
return NULL;
}
if (*p != '(')
{
log_debug ("oops: Libksba returned an invalid s-expression\n");
return NULL;
}
p++;
n = strtoul (p, &endp, 10);
p = endp;
if (*p != ':')
{
log_debug ("oops: Libksba returned an invalid s-expression\n");
return NULL;
}
p++;
return bin2hex (p, n, NULL);
}
/* Return the keygrip for the X.509 certificate CERT. The grip is
* stored at KEYGRIP which must have been allocated by the caller
* with a size of KEYGRIP_LEN. */
gpg_error_t
be_get_x509_keygrip (ksba_cert_t cert, unsigned char *keygrip)
{
gpg_error_t err;
size_t n;
ksba_sexp_t p;
gcry_sexp_t s_pkey;
p = ksba_cert_get_public_key (cert);
if (!p)
return gpg_error (GPG_ERR_NO_PUBKEY);
n = gcry_sexp_canon_len (p, 0, NULL, NULL);
if (!n)
{
ksba_free (p);
return gpg_error (GPG_ERR_NO_PUBKEY);
}
err = gcry_sexp_sscan (&s_pkey, NULL, (char*)p, n);
ksba_free (p);
if (err)
return err;
if (!gcry_pk_get_keygrip (s_pkey, keygrip))
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
gcry_sexp_release (s_pkey);
return err;
}
diff --git a/kbx/frontend.c b/kbx/frontend.c
index 9cb9f145f..f0f2af960 100644
--- a/kbx/frontend.c
+++ b/kbx/frontend.c
@@ -1,566 +1,566 @@
-/* frontend.c - Database fronend code for keyboxd
+/* frontend.c - Database frontend code for keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include "keyboxd.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "../common/userids.h"
#include "backend.h"
#include "frontend.h"
/* An object to keep infos about the database. */
struct
{
enum database_types db_type;
backend_handle_t backend_handle;
} the_database;
/* Take a lock for reading the databases. */
static void
take_read_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Take a lock for reading and writing the databases. */
static void
take_read_write_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Release a lock. It is valid to call this even if no lock has been
* taken in which case this is a nop. */
static void
release_lock (ctrl_t ctrl)
{
/* FIXME */
(void)ctrl;
}
/* Set the database to use. Depending on the FILENAME suffix we
* decide which one to use. This function must be called at daemon
* startup because it employs no locking. If FILENAME has no
* directory separator, the file is expected or created below
* "$GNUPGHOME/public-keys.d/". In READONLY mode the file must exists;
* otherwise it is created. */
gpg_error_t
kbxd_set_database (ctrl_t ctrl, const char *filename_arg, int readonly)
{
gpg_error_t err;
char *filename;
enum database_types db_type = 0;
backend_handle_t handle = NULL;
unsigned int n;
/* Do tilde expansion etc. */
if (strchr (filename_arg, DIRSEP_C)
#ifdef HAVE_W32_SYSTEM
|| strchr (filename_arg, '/') /* Windows also accepts a slash. */
#endif
)
filename = make_filename (filename_arg, NULL);
else
filename = make_filename (gnupg_homedir (), GNUPG_PUBLIC_KEYS_DIR,
filename_arg, NULL);
/* If this is the first call to the function and the request is not
* for the cache backend, add the cache backend so that it will
* always be the first to be queried. */
if (the_database.db_type)
{
log_error ("error: only one database allowed\n");
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
/* Init the cache. */
err = be_cache_initialize ();
if (err)
goto leave;
n = strlen (filename);
if (db_type)
; /* We already know it. */
else if (n > 4 && !strcmp (filename + n - 4, ".kbx"))
db_type = DB_TYPE_KBX;
else if (n > 3 && !strcmp (filename + n - 3, ".db"))
db_type = DB_TYPE_SQLITE;
else
{
log_error (_("can't use file '%s': %s\n"), filename, _("unknown suffix"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = gpg_error (GPG_ERR_BUG);
switch (db_type)
{
case DB_TYPE_NONE: /* NOTREACHED */
break;
case DB_TYPE_CACHE:
err = be_cache_add_resource (ctrl, &handle);
break;
case DB_TYPE_KBX:
err = be_kbx_add_resource (ctrl, &handle, filename, readonly);
break;
case DB_TYPE_SQLITE:
err = be_sqlite_add_resource (ctrl, &handle, filename, readonly);
break;
}
if (err)
goto leave;
the_database.db_type = db_type;
the_database.backend_handle = handle;
handle = NULL;
leave:
if (err)
{
log_error ("error setting database '%s': %s\n",
filename, gpg_strerror (err));
be_generic_release_backend (ctrl, handle);
}
xfree (filename);
return err;
}
/* Release all per session objects. */
void
kbxd_release_session_info (ctrl_t ctrl)
{
if (!ctrl)
return;
be_release_request (ctrl->db_req);
ctrl->db_req = NULL;
}
gpg_error_t
kbxd_rollback (void)
{
return be_sqlite_rollback ();
}
gpg_error_t
kbxd_commit (void)
{
return be_sqlite_commit ();
}
static void
dump_search_desc (struct keydb_search_desc *desc)
{
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
log_printf ("EXACT: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
log_printf ("SUBSTR: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
log_printf ("MAIL: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_MAILSUB:
log_printf ("MAILSUB: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
log_printf ("MAILEND: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_WORDS:
log_printf ("WORDS: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
log_printf ("SHORT_KID: 0x%08lX\n", (ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
log_printf ("LONG_KID: 0x%08lX%08lX\n",
(ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
log_printf ("FPR%02d: ", desc->fprlen);
log_printhex (desc->u.fpr, desc->fprlen, "");
break;
case KEYDB_SEARCH_MODE_ISSUER:
log_printf ("ISSUER: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
log_printf ("ISSUER_SN: '#%.*s/%s'\n",
(int)desc->snlen, desc->sn, desc->u.name);
break;
case KEYDB_SEARCH_MODE_SN:
log_printf ("SN: '%.*s'\n", (int)desc->snlen, desc->sn);
break;
case KEYDB_SEARCH_MODE_SUBJECT:
log_printf ("SUBJECT: '%s'\n", desc->u.name);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
log_printf ("KEYGRIP: ");
log_printhex (desc[0].u.grip, KEYGRIP_LEN, "");
break;
case KEYDB_SEARCH_MODE_UBID:
log_printf ("UBID: ");
log_printhex (desc[0].u.ubid, UBID_LEN, "");
break;
case KEYDB_SEARCH_MODE_FIRST:
log_printf ("FIRST\n");
break;
case KEYDB_SEARCH_MODE_NEXT:
log_printf ("NEXT\n");
break;
default:
log_printf ("Bad search mode (%d)\n", desc->mode);
}
}
/* Search for the keys described by (DESC,NDESC) and return them to
* the caller. If RESET is set, the search state is first reset.
* Only a reset guarantees that changed search description in DESC are
* considered. */
gpg_error_t
kbxd_search (ctrl_t ctrl, KEYDB_SEARCH_DESC *desc, unsigned int ndesc,
int reset)
{
gpg_error_t err;
int i;
db_request_t request;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
if (DBG_LOOKUP)
{
log_debug ("%s: %u search descriptions:\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
log_debug ("%s %d: ", __func__, i);
dump_search_desc (&desc[i]);
}
}
take_read_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->db_req)
{
ctrl->db_req = xtrycalloc (1, sizeof *ctrl->db_req);
if (!ctrl->db_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->db_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
/* If requested do a reset. Using the reset flag is faster than
* letting the caller do a separate call for an initial reset. */
if (!desc || reset)
{
switch (the_database.db_type)
{
case DB_TYPE_CACHE:
err = 0; /* Nothing to do. */
break;
case DB_TYPE_KBX:
err = be_kbx_search (ctrl, the_database.backend_handle,
request, NULL, 0);
break;
case DB_TYPE_SQLITE:
err = be_sqlite_search (ctrl, the_database.backend_handle,
request, NULL, 0);
break;
default:
err = gpg_error (GPG_ERR_INTERNAL);
break;
}
if (err)
{
log_error ("error during the %ssearch reset: %s\n",
reset? "initial ":"", gpg_strerror (err));
goto leave;
}
request->any_search = 0;
request->any_found = 0;
request->next_dbidx = 0;
if (!desc) /* Reset only mode */
{
err = 0;
goto leave;
}
}
/* Divert to the backend for the actual search. */
switch (the_database.db_type)
{
case DB_TYPE_CACHE:
err = be_cache_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
/* Expected error codes from the cache lookup are:
* 0 - found and returned via the cache
* GPG_ERR_NOT_FOUND - marked in the cache as not available
* GPG_ERR_EOF - cache miss. */
break;
case DB_TYPE_KBX:
err = be_kbx_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
break;
case DB_TYPE_SQLITE:
err = be_sqlite_search (ctrl, the_database.backend_handle, request,
desc, ndesc);
break;
default:
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s => %s\n", __func__,
strdbtype (the_database.db_type), gpg_strerror (err));
request->any_search = 1;
if (!err)
{
request->any_found = 1;
}
else if (gpg_err_code (err) == GPG_ERR_EOF)
{
if (the_database.db_type == DB_TYPE_CACHE && request->last_cached_valid)
{
if (request->last_cached_final)
goto leave;
}
request->next_dbidx++;
/* FIXME: We need to see which pubkey type we need to insert. */
be_cache_not_found (ctrl, PUBKEY_TYPE_UNKNOWN, desc, ndesc);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave (%s)", __func__, err? "not found" : "found");
return err;
}
/* Store; that is insert or update the key (BLOB,BLOBLEN). MODE
* controls whether only updates or only inserts are allowed. */
gpg_error_t
kbxd_store (ctrl_t ctrl, const void *blob, size_t bloblen,
enum kbxd_store_modes mode)
{
gpg_error_t err;
db_request_t request;
char ubid[UBID_LEN];
enum pubkey_types pktype;
int insert = 0;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
take_read_write_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->db_req)
{
ctrl->db_req = xtrycalloc (1, sizeof *ctrl->db_req);
if (!ctrl->db_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->db_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
/* Check whether to insert or update. */
err = be_ubid_from_blob (blob, bloblen, &pktype, ubid);
if (err)
goto leave;
if (the_database.db_type == DB_TYPE_KBX)
{
err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid);
if (!err)
; /* Found - need to update. */
else if (gpg_err_code (err) == GPG_ERR_EOF)
insert = 1; /* Not found - need to insert. */
else
{
log_debug ("%s: searching fingerprint failed: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (insert)
{
if (mode == KBXD_STORE_UPDATE)
err = gpg_error (GPG_ERR_CONFLICT);
else
err = be_kbx_insert (ctrl, the_database.backend_handle, request,
pktype, blob, bloblen);
}
else /* Update. */
{
if (mode == KBXD_STORE_INSERT)
err = gpg_error (GPG_ERR_CONFLICT);
else
err = be_kbx_update (ctrl, the_database.backend_handle, request,
pktype, blob, bloblen);
}
}
else if (the_database.db_type == DB_TYPE_SQLITE)
{
err = be_sqlite_store (ctrl, the_database.backend_handle, request,
mode, pktype, ubid, blob, bloblen);
}
else
{
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
}
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave", __func__);
return err;
}
/* Delete; remove the blob identified by UBID. */
gpg_error_t
kbxd_delete (ctrl_t ctrl, const unsigned char *ubid)
{
gpg_error_t err;
db_request_t request;
if (DBG_CLOCK)
log_clock ("%s: enter", __func__);
take_read_write_lock (ctrl);
/* Allocate a handle object if none exists for this context. */
if (!ctrl->db_req)
{
ctrl->db_req = xtrycalloc (1, sizeof *ctrl->db_req);
if (!ctrl->db_req)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
request = ctrl->db_req;
if (!the_database.db_type)
{
log_error ("%s: error: no database configured\n", __func__);
err = gpg_error (GPG_ERR_NOT_INITIALIZED);
goto leave;
}
if (the_database.db_type == DB_TYPE_KBX)
{
err = be_kbx_seek (ctrl, the_database.backend_handle, request, ubid);
if (!err)
; /* Found - we can delete. */
else if (gpg_err_code (err) == GPG_ERR_EOF)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
else
{
log_debug ("%s: searching primary fingerprint failed: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
err = be_kbx_delete (ctrl, the_database.backend_handle, request);
}
else if (the_database.db_type == DB_TYPE_SQLITE)
{
err = be_sqlite_delete (ctrl, the_database.backend_handle, request, ubid);
}
else
{
log_error ("%s: unsupported database type %d\n",
__func__, the_database.db_type);
err = gpg_error (GPG_ERR_INTERNAL);
}
leave:
release_lock (ctrl);
if (DBG_CLOCK)
log_clock ("%s: leave", __func__);
return err;
}
diff --git a/kbx/kbxserver.c b/kbx/kbxserver.c
index d09a8f8eb..3e68bfc26 100644
--- a/kbx/kbxserver.c
+++ b/kbx/kbxserver.c
@@ -1,1089 +1,1089 @@
/* kbxserver.c - Handle Assuan commands send to the keyboxd
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <string.h>
#include <assert.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <errno.h>
#include "keyboxd.h"
#include <assuan.h>
#include "../common/i18n.h"
#include "../common/server-help.h"
#include "../common/userids.h"
#include "../common/asshelp.h"
#include "../common/host2net.h"
#include "frontend.h"
#define PARM_ERROR(t) assuan_set_error (ctx, \
gpg_error (GPG_ERR_ASS_PARAMETER), (t))
#define set_error(e,t) (ctx ? assuan_set_error (ctx, gpg_error (e), (t)) \
/**/: gpg_error (e))
/* Helper to provide packing memory for search descriptions. */
struct search_backing_store_s
{
unsigned char *sn;
char *name;
};
/* Control structure per connection. */
struct server_local_s
{
/* We keep a list of all active sessions with the anchor at
* SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* The pid of the client. */
pid_t client_pid;
/* Data used to associate an Assuan context with local server data */
assuan_context_t assuan_ctx;
/* The session id (a counter). */
unsigned int session_id;
/* If this flag is set to true this process will be terminated after
* the end of this session. */
int stopme;
/* If the first both flags are set the assuan logging of data lines
* is suppressed. The count variable is used to show the number of
* non-logged bytes. */
size_t inhibit_data_logging_count;
unsigned int inhibit_data_logging : 1;
unsigned int inhibit_data_logging_now : 1;
/* This flag is set if the last search command was called with --more. */
unsigned int search_expecting_more : 1;
/* This flag is set if the last search command was successful. */
unsigned int search_any_found : 1;
/* The first is the current search description as parsed by the
* cmd_search. If more than one pattern is required, cmd_search
* also allocates and sets multi_search_desc and
* multi_search_desc_len. If a search description has ever been
* allocated the allocated size is stored at multi_search_desc_size.
* multi_search_store is allocated at the same size as
- * multi_search_desc and used to provde backing store for the SN and
+ * multi_search_desc and used to provide backing store for the SN and
* NAME elements of KEYBOX_SEARCH_DESC. */
KEYBOX_SEARCH_DESC search_desc;
KEYBOX_SEARCH_DESC *multi_search_desc;
struct search_backing_store_s *multi_search_store;
unsigned int multi_search_desc_size;
unsigned int multi_search_desc_len;
/* If not NULL write output to this stream instead of using D lines. */
estream_t outstream;
};
/* To keep track of all running sessions, we link all active server
* contexts and anchor them at this variable. */
static struct server_local_s *session_list;
/* Return the assuan contxt from the local server info in CTRL. */
static assuan_context_t
get_assuan_ctx_from_ctrl (ctrl_t ctrl)
{
if (!ctrl || !ctrl->server_local)
return NULL;
return ctrl->server_local->assuan_ctx;
}
/* If OUTPUT has been used prepare the output FD for use. This needs
* to be called by all functions which will in any way use
* kbxd_write_data_line later. Whether the output goes to the output
* stream is decided by this function. */
static gpg_error_t
prepare_outstream (ctrl_t ctrl)
{
gnupg_fd_t fd;
estream_t out_fp = NULL;
log_assert (ctrl && ctrl->server_local);
if (ctrl->server_local->outstream)
return 0; /* Already enabled. */
fd = assuan_get_output_fd (get_assuan_ctx_from_ctrl (ctrl));
if (fd == GNUPG_INVALID_FD)
return 0; /* No Output command active. */
else
{
out_fp = open_stream_nc (fd, "w");
if (!out_fp)
return gpg_err_code_from_syserror ();
}
ctrl->server_local->outstream = out_fp;
return 0;
}
/* The usual writen function; here with diagnostic output. */
static gpg_error_t
kbxd_writen (estream_t fp, const void *buffer, size_t length)
{
gpg_error_t err;
size_t nwritten;
if (es_write (fp, buffer, length, &nwritten))
{
err = gpg_error_from_syserror ();
log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
}
else if (length != nwritten)
{
err = gpg_error (GPG_ERR_EIO);
log_error ("error writing OUTPUT: %s\n", "short write");
}
else
err = 0;
return err;
}
/* This status functions expects a printf style format string. */
gpg_error_t
kbxd_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl);
if (!ctx) /* Oops - no assuan context. */
return gpg_error (GPG_ERR_NOT_PROCESSED);
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* A wrapper around assuan_send_data which makes debugging the output
* in verbose mode easier. It also takes CTRL as argument. */
gpg_error_t
kbxd_write_data_line (ctrl_t ctrl, const void *buffer_arg, size_t size)
{
const char *buffer = buffer_arg;
assuan_context_t ctx = get_assuan_ctx_from_ctrl (ctrl);
gpg_error_t err;
if (!ctx) /* Oops - no assuan context. */
return gpg_error (GPG_ERR_NOT_PROCESSED);
/* Write toa file descriptor if enabled. */
if (ctrl && ctrl->server_local && ctrl->server_local->outstream)
{
unsigned char lenbuf[4];
ulongtobuf (lenbuf, size);
err = kbxd_writen (ctrl->server_local->outstream, lenbuf, 4);
if (!err)
err = kbxd_writen (ctrl->server_local->outstream, buffer, size);
if (!err && es_fflush (ctrl->server_local->outstream))
{
err = gpg_error_from_syserror ();
log_error ("error writing OUTPUT: %s\n", gpg_strerror (err));
}
goto leave;
}
/* If we do not want logging, enable it here. */
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
ctrl->server_local->inhibit_data_logging_now = 1;
if (0 && opt.verbose && buffer && size)
{
/* Ease reading of output by limiting the line length. */
size_t n, nbytes;
nbytes = size;
do
{
n = nbytes > 64? 64 : nbytes;
err = assuan_send_data (ctx, buffer, n);
if (err)
{
gpg_err_set_errno (EIO);
goto leave;
}
buffer += n;
nbytes -= n;
if (nbytes && (err=assuan_send_data (ctx, NULL, 0))) /* Flush line. */
{
gpg_err_set_errno (EIO);
goto leave;
}
}
while (nbytes);
}
else
{
err = assuan_send_data (ctx, buffer, size);
if (err)
{
gpg_err_set_errno (EIO); /* For use by data_line_cookie_write. */
goto leave;
}
}
leave:
if (ctrl && ctrl->server_local && ctrl->server_local->inhibit_data_logging)
{
ctrl->server_local->inhibit_data_logging_count += size;
ctrl->server_local->inhibit_data_logging_now = 0;
}
return err;
}
/* Helper to print a message while leaving a command. */
static gpg_error_t
leave_cmd (assuan_context_t ctx, gpg_error_t err)
{
if (err && opt.verbose)
{
const char *name = assuan_get_command_name (ctx);
if (!name)
name = "?";
if (gpg_err_source (err) == GPG_ERR_SOURCE_DEFAULT)
log_error ("command '%s' failed: %s\n", name,
gpg_strerror (err));
else
log_error ("command '%s' failed: %s <%s>\n", name,
gpg_strerror (err), gpg_strsource (err));
}
return err;
}
/* Handle OPTION commands. */
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
if (!strcmp (key, "lc-messages"))
{
if (ctrl->lc_messages)
xfree (ctrl->lc_messages);
ctrl->lc_messages = xtrystrdup (value);
if (!ctrl->lc_messages)
return out_of_core ();
}
else
err = gpg_error (GPG_ERR_UNKNOWN_OPTION);
return err;
}
static const char hlp_search[] =
"SEARCH [--no-data] [--openpgp|--x509] [[--more] PATTERN]\n"
"\n"
"Search for the keys identified by PATTERN. With --more more\n"
"patterns to be used for the search are expected with the next\n"
"command. With --no-data only the search status is returned but\n"
"not the actual data. With --openpgp or --x509 only the respective\n"
"keys are returned. See also \"NEXT\".";
static gpg_error_t
cmd_search (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_more, opt_no_data, opt_openpgp, opt_x509;
gpg_error_t err;
unsigned int n, k;
opt_no_data = has_option (line, "--no-data");
opt_more = has_option (line, "--more");
opt_openpgp = has_option (line, "--openpgp");
opt_x509 = has_option (line, "--x509");
line = skip_options (line);
ctrl->server_local->search_any_found = 0;
if (!*line)
{
if (opt_more)
{
err = set_error (GPG_ERR_INV_ARG, "--more but no pattern");
goto leave;
}
else if (!*line && ctrl->server_local->search_expecting_more)
{
/* It would be too surprising to first set a pattern but
* finally add no pattern to search the entire DB. */
err = set_error (GPG_ERR_INV_ARG, "--more pending but no pattern");
goto leave;
}
else /* No pattern - return the first item. */
{
memset (&ctrl->server_local->search_desc, 0,
sizeof ctrl->server_local->search_desc);
ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_FIRST;
}
}
else
{
err = classify_user_id (line, &ctrl->server_local->search_desc, 1);
if (err)
goto leave;
}
if (opt_more || ctrl->server_local->search_expecting_more)
{
/* More pattern are expected - store the current one and return
* success. */
KEYBOX_SEARCH_DESC *desc;
struct search_backing_store_s *store;
if (!ctrl->server_local->multi_search_desc_size)
{
n = 10;
ctrl->server_local->multi_search_desc
= xtrycalloc (n, sizeof *ctrl->server_local->multi_search_desc);
if (!ctrl->server_local->multi_search_desc)
{
err = gpg_error_from_syserror ();
goto leave;
}
ctrl->server_local->multi_search_store
= xtrycalloc (n, sizeof *ctrl->server_local->multi_search_store);
if (!ctrl->server_local->multi_search_store)
{
err = gpg_error_from_syserror ();
xfree (ctrl->server_local->multi_search_desc);
ctrl->server_local->multi_search_desc = NULL;
goto leave;
}
ctrl->server_local->multi_search_desc_size = n;
}
if (ctrl->server_local->multi_search_desc_len
== ctrl->server_local->multi_search_desc_size)
{
n = ctrl->server_local->multi_search_desc_size + 10;
desc = xtrycalloc (n, sizeof *desc);
if (!desc)
{
err = gpg_error_from_syserror ();
goto leave;
}
store = xtrycalloc (n, sizeof *store);
if (!store)
{
err = gpg_error_from_syserror ();
xfree (desc);
goto leave;
}
for (k=0; k < ctrl->server_local->multi_search_desc_size; k++)
{
desc[k] = ctrl->server_local->multi_search_desc[k];
store[k] = ctrl->server_local->multi_search_store[k];
}
xfree (ctrl->server_local->multi_search_desc);
xfree (ctrl->server_local->multi_search_store);
ctrl->server_local->multi_search_desc = desc;
ctrl->server_local->multi_search_store = store;
ctrl->server_local->multi_search_desc_size = n;
}
/* Actually store. We need to fix up the const pointers by
* copies from our backing store. */
desc = &(ctrl->server_local->multi_search_desc
[ctrl->server_local->multi_search_desc_len]);
store = &(ctrl->server_local->multi_search_store
[ctrl->server_local->multi_search_desc_len]);
*desc = ctrl->server_local->search_desc;
if (ctrl->server_local->search_desc.sn)
{
xfree (store->sn);
store->sn = xtrymalloc (ctrl->server_local->search_desc.snlen);
if (!store->sn)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (store->sn, ctrl->server_local->search_desc.sn,
ctrl->server_local->search_desc.snlen);
desc->sn = store->sn;
}
if (ctrl->server_local->search_desc.name_used)
{
xfree (store->name);
store->name = xtrystrdup (ctrl->server_local->search_desc.u.name);
if (!store->name)
{
err = gpg_error_from_syserror ();
xfree (store->sn);
store->sn = NULL;
goto leave;
}
desc->u.name = store->name;
}
ctrl->server_local->multi_search_desc_len++;
if (opt_more)
{
/* We need to be called again with more pattern. */
ctrl->server_local->search_expecting_more = 1;
goto leave;
}
ctrl->server_local->search_expecting_more = 0;
/* Continue with the actual search. */
}
else
ctrl->server_local->multi_search_desc_len = 0;
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
ctrl->no_data_return = opt_no_data;
ctrl->filter_opgp = opt_openpgp;
ctrl->filter_x509 = opt_x509;
err = prepare_outstream (ctrl);
if (err)
;
else if (ctrl->server_local->multi_search_desc_len)
err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
ctrl->server_local->multi_search_desc_len, 1);
else
err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 1);
if (err)
goto leave;
/* Set a flag for use by NEXT. */
ctrl->server_local->search_any_found = 1;
leave:
if (err)
ctrl->server_local->multi_search_desc_len = 0;
ctrl->no_data_return = 0;
ctrl->server_local->inhibit_data_logging = 0;
return leave_cmd (ctx, err);
}
static const char hlp_next[] =
"NEXT [--no-data]\n"
"\n"
"Get the next search result from a previous search.";
static gpg_error_t
cmd_next (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_no_data;
gpg_error_t err;
opt_no_data = has_option (line, "--no-data");
line = skip_options (line);
if (*line)
{
err = set_error (GPG_ERR_INV_ARG, "no args expected");
goto leave;
}
if (!ctrl->server_local->search_any_found)
{
err = set_error (GPG_ERR_NOTHING_FOUND, "no previous SEARCH");
goto leave;
}
ctrl->server_local->inhibit_data_logging = 1;
ctrl->server_local->inhibit_data_logging_now = 0;
ctrl->server_local->inhibit_data_logging_count = 0;
ctrl->no_data_return = opt_no_data;
err = prepare_outstream (ctrl);
if (err)
;
else if (ctrl->server_local->multi_search_desc_len)
{
/* The next condition should never be true but we better handle
* the first/next transition anyway. */
if (ctrl->server_local->multi_search_desc[0].mode
== KEYDB_SEARCH_MODE_FIRST)
ctrl->server_local->multi_search_desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
err = kbxd_search (ctrl, ctrl->server_local->multi_search_desc,
ctrl->server_local->multi_search_desc_len, 0);
}
else
{
/* We need to do the transition from first to next here. */
if (ctrl->server_local->search_desc.mode == KEYDB_SEARCH_MODE_FIRST)
ctrl->server_local->search_desc.mode = KEYDB_SEARCH_MODE_NEXT;
err = kbxd_search (ctrl, &ctrl->server_local->search_desc, 1, 0);
}
if (err)
goto leave;
leave:
ctrl->no_data_return = 0;
ctrl->server_local->inhibit_data_logging = 0;
return leave_cmd (ctx, err);
}
static const char hlp_store[] =
"STORE [--update|--insert]\n"
"\n"
"Insert a key into the database. Whether to insert or update\n"
"the key is decided by looking at the primary key's fingerprint.\n"
"With option --update the key must already exist.\n"
"With option --insert the key must not already exist.\n"
"The actual key material is requested by this function using\n"
" INQUIRE BLOB";
static gpg_error_t
cmd_store (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int opt_update, opt_insert;
enum kbxd_store_modes mode;
gpg_error_t err;
unsigned char *value = NULL;
size_t valuelen;
opt_update = has_option (line, "--update");
opt_insert = has_option (line, "--insert");
line = skip_options (line);
if (*line)
{
err = set_error (GPG_ERR_INV_ARG, "no args expected");
goto leave;
}
if (opt_update && !opt_insert)
mode = KBXD_STORE_UPDATE;
else if (!opt_update && opt_insert)
mode = KBXD_STORE_INSERT;
else
mode = KBXD_STORE_AUTO;
/* Ask for the key material. */
err = assuan_inquire (ctx, "BLOB", &value, &valuelen, 0);
if (err)
{
log_error (_("assuan_inquire failed: %s\n"), gpg_strerror (err));
goto leave;
}
if (!valuelen) /* No data received. */
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = kbxd_store (ctrl, value, valuelen, mode);
leave:
xfree (value);
return leave_cmd (ctx, err);
}
static const char hlp_delete[] =
"DELETE <ubid> \n"
"\n"
"Delete a key into the database. The UBID identifies the key.\n";
static gpg_error_t
cmd_delete (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int n;
unsigned char ubid[UBID_LEN];
line = skip_options (line);
if (!*line)
{
err = set_error (GPG_ERR_INV_ARG, "UBID missing");
goto leave;
}
/* Skip an optional UBID identifier character. */
if (*line == '^' && line[1])
line++;
if ((n=hex2bin (line, ubid, UBID_LEN)) < 0)
{
err = set_error (GPG_ERR_INV_USER_ID, "invalid UBID");
goto leave;
}
if (line[n])
{
err = set_error (GPG_ERR_INV_ARG, "garbage after UBID");
goto leave;
}
err = kbxd_delete (ctrl, ubid);
leave:
return leave_cmd (ctx, err);
}
static const char hlp_transaction[] =
"TRANSACTION [begin|commit|rollback]\n"
"\n"
"For bulk import of data it is often useful to run everything\n"
"in one transaction. This can be achieved with this command.\n"
"If the last connection of client is closed before a commit\n"
"or rollback an implicit rollback is done. With no argument\n"
"the status of the current transaction is returned.";
static gpg_error_t
cmd_transaction (assuan_context_t ctx, char *line)
{
gpg_error_t err = 0;
line = skip_options (line);
if (!strcmp (line, "begin"))
{
/* Note that we delay the actual transaction until we have to
* use SQL. */
if (opt.in_transaction)
err = set_error (GPG_ERR_CONFLICT, "already in a transaction");
else
{
opt.in_transaction = 1;
opt.transaction_pid = assuan_get_pid (ctx);
}
}
else if (!strcmp (line, "commit"))
{
if (!opt.in_transaction)
err = set_error (GPG_ERR_CONFLICT, "not in a transaction");
else if (opt.transaction_pid != assuan_get_pid (ctx))
err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction");
else
err = kbxd_commit ();
}
else if (!strcmp (line, "rollback"))
{
if (!opt.in_transaction)
err = set_error (GPG_ERR_CONFLICT, "not in a transaction");
else if (opt.transaction_pid != assuan_get_pid (ctx))
err = set_error (GPG_ERR_CONFLICT, "other client is in a transaction");
else
err = kbxd_rollback ();
}
else if (!*line)
{
if (opt.in_transaction && opt.transaction_pid == assuan_get_pid (ctx))
err = assuan_set_okay_line (ctx, opt.active_transaction?
"active transaction" :
"pending transaction");
else if (opt.in_transaction)
err = assuan_set_okay_line (ctx, opt.active_transaction?
"active transaction on other client" :
"pending transaction on other client");
else
err = set_error (GPG_ERR_FALSE, "no transaction");
}
else
{
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown transaction command");
}
return leave_cmd (ctx, err);
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
"version - Return the version of the program.\n"
"pid - Return the process id of the server.\n"
"socket_name - Return the name of the socket.\n"
"session_id - Return the current session_id.\n"
"getenv NAME - Return value of envvar NAME\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char numbuf[50];
if (!strcmp (line, "version"))
{
const char *s = VERSION;
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "socket_name"))
{
const char *s = get_kbxd_socket_name ();
if (!s)
s = "[none]";
err = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "session_id"))
{
snprintf (numbuf, sizeof numbuf, "%u", ctrl->server_local->session_id);
err = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "getenv", 6)
&& (line[6] == ' ' || line[6] == '\t' || !line[6]))
{
line += 6;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
err = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
const char *s = getenv (line);
if (!s)
err = set_error (GPG_ERR_NOT_FOUND, "No such envvar");
else
err = assuan_send_data (ctx, s, strlen (s));
}
}
else
err = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return leave_cmd (ctx, err);
}
static const char hlp_killkeyboxd[] =
"KILLKEYBOXD\n"
"\n"
"This command allows a user - given sufficient permissions -\n"
"to kill this keyboxd process.\n";
static gpg_error_t
cmd_killkeyboxd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_reloadkeyboxd[] =
"RELOADKEYBOXD\n"
"\n"
"This command is an alternative to SIGHUP\n"
"to reload the configuration.";
static gpg_error_t
cmd_reloadkeyboxd (assuan_context_t ctx, char *line)
{
(void)ctx;
(void)line;
kbxd_sighup_action ();
return 0;
}
static const char hlp_output[] =
"OUTPUT FD[=<n>]\n"
"\n"
"Set the file descriptor to write the output data to N. If N is not\n"
"given and the operating system supports file descriptor passing, the\n"
"file descriptor currently in flight will be used.";
/* Tell the assuan library about our commands. */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "SEARCH", cmd_search, hlp_search },
{ "NEXT", cmd_next, hlp_next },
{ "STORE", cmd_store, hlp_store },
{ "DELETE", cmd_delete, hlp_delete },
{ "TRANSACTION",cmd_transaction,hlp_transaction },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "OUTPUT", NULL, hlp_output },
{ "KILLKEYBOXD",cmd_killkeyboxd,hlp_killkeyboxd },
{ "RELOADKEYBOXD",cmd_reloadkeyboxd,hlp_reloadkeyboxd },
{ NULL, NULL }
};
int i, j, rc;
for (i=j=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
return 0;
}
/* Note that we do not reset the list of configured keyservers. */
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
(void)ctrl;
return 0;
}
/* This function is called by our assuan log handler to test whether a
* log message shall really be printed. The function must return
* false to inhibit the logging of MSG. CAT gives the requested log
* category. MSG might be NULL. */
int
kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)cat;
(void)msg;
if (!ctrl || !ctrl->server_local)
return 1; /* Can't decide - allow logging. */
if (!ctrl->server_local->inhibit_data_logging)
return 1; /* Not requested - allow logging. */
/* Disallow logging if *_now is true. */
return !ctrl->server_local->inhibit_data_logging_now;
}
/* Startup the server and run the main command loop. With FD = -1,
* use stdin/stdout. SESSION_ID is either 0 or a unique number
* identifying a session. */
void
kbxd_start_command_handler (ctrl_t ctrl, gnupg_fd_t fd, unsigned int session_id)
{
static const char hello[] = "Keyboxd " VERSION " at your service";
static char *hello_line;
int rc;
assuan_context_t ctx;
ctrl->server_local = xtrycalloc (1, sizeof *ctrl->server_local);
if (!ctrl->server_local)
{
log_error (_("can't allocate control structure: %s\n"),
gpg_strerror (gpg_error_from_syserror ()));
return;
}
ctrl->server_local->client_pid = ASSUAN_INVALID_PID;
rc = assuan_new (&ctx);
if (rc)
{
log_error (_("failed to allocate assuan context: %s\n"),
gpg_strerror (rc));
kbxd_exit (2);
}
if (fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, fd,
(ASSUAN_SOCKET_SERVER_ACCEPTED
|ASSUAN_SOCKET_SERVER_FDPASSING));
}
if (rc)
{
assuan_release (ctx);
log_error (_("failed to initialize the server: %s\n"),
gpg_strerror (rc));
kbxd_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error (_("failed to the register commands with Assuan: %s\n"),
gpg_strerror(rc));
kbxd_exit (2);
}
if (!hello_line)
{
hello_line = xtryasprintf
("Home: %s\n"
"Config: %s\n"
"%s",
gnupg_homedir (),
/*opt.config_filename? opt.config_filename :*/ "[none]",
hello);
}
ctrl->server_local->assuan_ctx = ctx;
assuan_set_pointer (ctx, ctrl);
assuan_set_hello_line (ctx, hello_line);
assuan_register_option_handler (ctx, option_handler);
assuan_register_reset_notify (ctx, reset_notify);
ctrl->server_local->session_id = session_id;
/* Put the session int a list. */
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
break;
if (rc)
{
log_info (_("Assuan accept problem: %s\n"), gpg_strerror (rc));
break;
}
#ifndef HAVE_W32_SYSTEM
if (opt.verbose)
{
assuan_peercred_t peercred;
if (!assuan_get_peercred (ctx, &peercred))
log_info ("connection from process %ld (%ld:%ld)\n",
(long)peercred->pid, (long)peercred->uid,
(long)peercred->gid);
}
#endif
ctrl->server_local->client_pid = assuan_get_pid (ctx);
rc = assuan_process (ctx);
if (rc)
{
log_info (_("Assuan processing failed: %s\n"), gpg_strerror (rc));
continue;
}
}
if (opt.in_transaction
&& opt.transaction_pid == ctrl->server_local->client_pid)
{
struct server_local_s *sl;
pid_t thispid = ctrl->server_local->client_pid;
int npids = 0;
/* Only if this is the last connection rollback the transaction. */
for (sl = session_list; sl; sl = sl->next_session)
if (sl->client_pid == thispid)
npids++;
if (npids == 1)
kbxd_rollback ();
}
assuan_close_output_fd (ctx);
ctrl->server_local->assuan_ctx = NULL;
assuan_release (ctx);
if (ctrl->server_local->stopme)
kbxd_exit (0);
if (ctrl->refcount)
log_error ("oops: connection control structure still referenced (%d)\n",
ctrl->refcount);
else
{
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
xfree (ctrl->server_local->multi_search_desc);
if (ctrl->server_local->multi_search_store)
{
size_t nn;
for (nn=0; nn < ctrl->server_local->multi_search_desc_size; nn++)
{
xfree (ctrl->server_local->multi_search_store[nn].sn);
xfree (ctrl->server_local->multi_search_store[nn].name);
}
xfree (ctrl->server_local->multi_search_store);
}
xfree (ctrl->server_local);
ctrl->server_local = NULL;
}
}
diff --git a/kbx/keybox-init.c b/kbx/keybox-init.c
index 439d1c477..7bd206383 100644
--- a/kbx/keybox-init.c
+++ b/kbx/keybox-init.c
@@ -1,453 +1,453 @@
/* keybox-init.c - Initialization of the library
* Copyright (C) 2001 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <assert.h>
#include "keybox-defs.h"
#include "../common/sysutils.h"
#include "../common/mischelp.h"
#ifdef HAVE_W32_SYSTEM
# define DEFAULT_LL_BUFFER_SIZE 128
#else
# define DEFAULT_LL_BUFFER_SIZE 64
#endif
static unsigned int ll_buffer_size = DEFAULT_LL_BUFFER_SIZE;
static KB_NAME kb_names;
/* This object is used to mahe setvbuf buffers. We use a short arary
* to be able to reuse already allocated buffers. */
struct stream_buffer_s
{
int inuse; /* True if used by a stream. */
size_t bufsize;
char *buf;
};
static struct stream_buffer_s stream_buffers[5];
/* Register a filename for plain keybox files. Returns 0 on success,
* GPG_ERR_EEXIST if it has already been registered, or another error
* code. On success or with error code GPG_ERR_EEXIST a token usable
* to access the keybox handle is stored at R_TOKEN, NULL is stored
* for all other errors. */
gpg_error_t
keybox_register_file (const char *fname, int secret, void **r_token)
{
KB_NAME kr;
*r_token = NULL;
for (kr=kb_names; kr; kr = kr->next)
{
if (same_file_p (kr->fname, fname) )
{
*r_token = kr;
return gpg_error (GPG_ERR_EEXIST); /* Already registered. */
}
}
kr = xtrymalloc (sizeof *kr + strlen (fname));
if (!kr)
return gpg_error_from_syserror ();
strcpy (kr->fname, fname);
kr->secret = !!secret;
kr->handle_table = NULL;
kr->handle_table_size = 0;
kr->lockhd = NULL;
kr->is_locked = 0;
kr->did_full_scan = 0;
/* keep a list of all issued pointers */
kr->next = kb_names;
kb_names = kr;
/* create the offset table the first time a function here is used */
/* if (!kb_offtbl) */
/* kb_offtbl = new_offset_hash_table (); */
*r_token = kr;
return 0;
}
int
keybox_is_writable (void *token)
{
KB_NAME r = token;
return r? !gnupg_access (r->fname, W_OK) : 0;
}
-/* Change the default buffering to KBYTES KiB; using 0 uses the syste
+/* Change the default buffering to KBYTES KiB; using 0 uses the system
* buffers. This function must be called early. */
void
keybox_set_buffersize (unsigned int kbytes, int reserved)
{
(void)reserved;
/* Round down to 8k multiples. */
ll_buffer_size = (kbytes + 7)/8 * 8;
}
static KEYBOX_HANDLE
do_keybox_new (KB_NAME resource, int secret, int for_openpgp)
{
KEYBOX_HANDLE hd;
int idx;
assert (resource && !resource->secret == !secret);
hd = xtrycalloc (1, sizeof *hd);
if (hd)
{
hd->kb = resource;
hd->secret = !!secret;
hd->for_openpgp = for_openpgp;
if (!resource->handle_table)
{
resource->handle_table_size = 3;
resource->handle_table = xtrycalloc (resource->handle_table_size,
sizeof *resource->handle_table);
if (!resource->handle_table)
{
resource->handle_table_size = 0;
xfree (hd);
return NULL;
}
}
for (idx=0; idx < resource->handle_table_size; idx++)
if (!resource->handle_table[idx])
{
resource->handle_table[idx] = hd;
break;
}
if (!(idx < resource->handle_table_size))
{
KEYBOX_HANDLE *tmptbl;
size_t newsize;
newsize = resource->handle_table_size + 5;
tmptbl = xtryrealloc (resource->handle_table,
newsize * sizeof (*tmptbl));
if (!tmptbl)
{
xfree (hd);
return NULL;
}
resource->handle_table = tmptbl;
resource->handle_table_size = newsize;
resource->handle_table[idx] = hd;
for (idx++; idx < resource->handle_table_size; idx++)
resource->handle_table[idx] = NULL;
}
}
return hd;
}
/* Create a new handle for the resource associated with TOKEN. SECRET
is just a cross-check. This is the OpenPGP version. The returned
handle must be released using keybox_release. */
KEYBOX_HANDLE
keybox_new_openpgp (void *token, int secret)
{
KB_NAME resource = token;
return do_keybox_new (resource, secret, 1);
}
/* Create a new handle for the resource associated with TOKEN. SECRET
is just a cross-check. This is the X.509 version. The returned
handle must be released using keybox_release. */
KEYBOX_HANDLE
keybox_new_x509 (void *token, int secret)
{
KB_NAME resource = token;
return do_keybox_new (resource, secret, 0);
}
void
keybox_release (KEYBOX_HANDLE hd)
{
if (!hd)
return;
if (hd->kb->handle_table)
{
int idx;
for (idx=0; idx < hd->kb->handle_table_size; idx++)
if (hd->kb->handle_table[idx] == hd)
hd->kb->handle_table[idx] = NULL;
}
_keybox_release_blob (hd->found.blob);
_keybox_release_blob (hd->saved_found.blob);
if (hd->fp)
{
_keybox_ll_close (hd->fp);
hd->fp = NULL;
}
xfree (hd->word_match.name);
xfree (hd->word_match.pattern);
xfree (hd);
}
/* Save the current found state in HD for later retrieval by
keybox_restore_found_state. Only one state may be saved. */
void
keybox_push_found_state (KEYBOX_HANDLE hd)
{
if (hd->saved_found.blob)
{
_keybox_release_blob (hd->saved_found.blob);
hd->saved_found.blob = NULL;
}
hd->saved_found = hd->found;
hd->found.blob = NULL;
}
/* Restore the saved found state in HD. */
void
keybox_pop_found_state (KEYBOX_HANDLE hd)
{
if (hd->found.blob)
{
_keybox_release_blob (hd->found.blob);
hd->found.blob = NULL;
}
hd->found = hd->saved_found;
hd->saved_found.blob = NULL;
}
const char *
keybox_get_resource_name (KEYBOX_HANDLE hd)
{
if (!hd || !hd->kb)
return NULL;
return hd->kb->fname;
}
int
keybox_set_ephemeral (KEYBOX_HANDLE hd, int yes)
{
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
hd->ephemeral = yes;
return 0;
}
/* Low-level open function to be used for keybox files. This function
* also manages custom buffering. On success 0 is returned and a new
* file pointer stored at RFP; on error an error code is returned and
* NULL is stored at RFP. MODE is one of
* KEYBOX_LL_OPEN_READ(0) := fopen mode is "rb"
* KEYBOX_LL_OPEN_UPDATE := fopen mode is "r+b"
* KEYBOX_LL_OPEN_CREATE := fopen mode is "wb"
*/
gpg_error_t
_keybox_ll_open (estream_t *rfp, const char *fname, unsigned int mode)
{
estream_t fp;
int i;
size_t bufsize;
*rfp = NULL;
fp = es_fopen (fname,
mode == KEYBOX_LL_OPEN_CREATE
? "wb,sysopen,sequential" :
mode == KEYBOX_LL_OPEN_UPDATE
? "r+b,sysopen,sequential" :
"rb,sysopen,sequential");
if (!fp)
return gpg_error_from_syserror ();
if (ll_buffer_size)
{
for (i=0; i < DIM (stream_buffers); i++)
if (!stream_buffers[i].inuse)
{
/* There is a free slot - we can use a larger buffer. */
stream_buffers[i].inuse = 1;
if (!stream_buffers[i].buf)
{
bufsize = ll_buffer_size * 1024;
stream_buffers[i].buf = xtrymalloc (bufsize);
if (stream_buffers[i].buf)
stream_buffers[i].bufsize = bufsize;
else
{
log_info ("can't allocate a large buffer for a kbx file;"
" using default\n");
stream_buffers[i].inuse = 0;
}
}
if (stream_buffers[i].buf)
{
es_setvbuf (fp, stream_buffers[i].buf, _IOFBF,
stream_buffers[i].bufsize);
es_opaque_set (fp, stream_buffers + i);
}
break;
}
}
*rfp = fp;
return 0;
}
/* Wrapper around es_fclose to be used for file opened with
* _keybox_ll_open. */
gpg_error_t
_keybox_ll_close (estream_t fp)
{
gpg_error_t err;
struct stream_buffer_s *sbuf;
int i;
if (!fp)
return 0;
sbuf = ll_buffer_size? es_opaque_get (fp) : NULL;
if (es_fclose (fp))
err = gpg_error_from_syserror ();
else
err = 0;
if (sbuf)
{
for (i=0; i < DIM (stream_buffers); i++)
if (stream_buffers + i == sbuf)
break;
log_assert (i < DIM (stream_buffers));
stream_buffers[i].inuse = 0;
}
return err;
}
/* Close the file of the resource identified by HD. For consistent
results this function closes the files of all handles pointing to
the resource identified by HD. */
void
_keybox_close_file (KEYBOX_HANDLE hd)
{
int idx;
KEYBOX_HANDLE roverhd;
if (!hd || !hd->kb || !hd->kb->handle_table)
return;
for (idx=0; idx < hd->kb->handle_table_size; idx++)
if ((roverhd = hd->kb->handle_table[idx]))
{
if (roverhd->fp)
{
_keybox_ll_close (roverhd->fp);
roverhd->fp = NULL;
}
}
log_assert (!hd->fp);
}
/*
* Lock the keybox at handle HD, or unlock if YES is false. TIMEOUT
* is the value used for dotlock_take. In general -1 should be used
* when taking a lock; use 0 when releasing a lock.
*/
gpg_error_t
keybox_lock (KEYBOX_HANDLE hd, int yes, long timeout)
{
gpg_error_t err = 0;
KB_NAME kb = hd->kb;
if (!keybox_is_writable (kb))
return 0;
/* Make sure the lock handle has been created. */
if (!kb->lockhd)
{
kb->lockhd = dotlock_create (kb->fname, 0);
if (!kb->lockhd)
{
err = gpg_error_from_syserror ();
log_info ("can't allocate lock for '%s'\n", kb->fname );
return err;
}
}
if (yes) /* Take the lock. */
{
if (!kb->is_locked)
{
#ifdef HAVE_W32_SYSTEM
/* Under Windows we need to close the file before we try
* to lock it. This is because another process might have
* taken the lock and is using keybox_file_rename to
* rename the base file. Now if our dotlock_take below is
* waiting for the lock but we have the base file still
* open, keybox_file_rename will never succeed as we are
* in a deadlock. */
_keybox_close_file (hd);
#endif /*HAVE_W32_SYSTEM*/
if (dotlock_take (kb->lockhd, timeout))
{
err = gpg_error_from_syserror ();
if (!timeout && gpg_err_code (err) == GPG_ERR_EACCES)
; /* No diagnostic if we only tried to lock. */
else
log_info ("can't lock '%s'\n", kb->fname );
}
else
kb->is_locked = 1;
}
}
else /* Release the lock. */
{
if (kb->is_locked)
{
if (dotlock_release (kb->lockhd))
{
err = gpg_error_from_syserror ();
log_info ("can't unlock '%s'\n", kb->fname );
}
else
kb->is_locked = 0;
}
}
return err;
}
diff --git a/kbx/keybox-search-desc.h b/kbx/keybox-search-desc.h
index f312da99b..eda299beb 100644
--- a/kbx/keybox-search-desc.h
+++ b/kbx/keybox-search-desc.h
@@ -1,99 +1,99 @@
-/* keybox-search-desc.h - Keybox serch description
+/* keybox-search-desc.h - Keybox search description
* Copyright (C) 2001 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
This file is a temporary kludge until we can come up with solution
to share this description between keybox and the application
specific keydb
*/
#ifndef KEYBOX_SEARCH_DESC_H
#define KEYBOX_SEARCH_DESC_H 1
typedef enum {
KEYDB_SEARCH_MODE_NONE,
KEYDB_SEARCH_MODE_EXACT,
KEYDB_SEARCH_MODE_SUBSTR,
KEYDB_SEARCH_MODE_MAIL,
KEYDB_SEARCH_MODE_MAILSUB,
KEYDB_SEARCH_MODE_MAILEND,
KEYDB_SEARCH_MODE_WORDS,
KEYDB_SEARCH_MODE_SHORT_KID,
KEYDB_SEARCH_MODE_LONG_KID,
KEYDB_SEARCH_MODE_FPR, /* (Length of fpr in .fprlen) */
KEYDB_SEARCH_MODE_ISSUER,
KEYDB_SEARCH_MODE_ISSUER_SN,
KEYDB_SEARCH_MODE_SN,
KEYDB_SEARCH_MODE_SUBJECT,
KEYDB_SEARCH_MODE_KEYGRIP,
KEYDB_SEARCH_MODE_UBID,
KEYDB_SEARCH_MODE_FIRST,
KEYDB_SEARCH_MODE_NEXT
} KeydbSearchMode;
/* Identifiers for the public key types we use in GnuPG. */
enum pubkey_types
{
PUBKEY_TYPE_UNKNOWN = 0,
PUBKEY_TYPE_OPGP = 1,
PUBKEY_TYPE_X509 = 2
};
/* Forward declaration. See g10/packet.h. */
struct gpg_pkt_user_id_s;
typedef struct gpg_pkt_user_id_s *gpg_pkt_user_id_t;
/* A search descriptor. */
struct keydb_search_desc
{
KeydbSearchMode mode;
/* Callback used to filter results. The first parameter is
SKIPFUNCVALUE. The second is the keyid. The third is the
1-based index of the UID packet that matched the search criteria
(or 0, if none).
Return non-zero if the result should be skipped. */
int (*skipfnc)(void *, u32 *, int);
void *skipfncvalue;
const unsigned char *sn;
unsigned short snlen;
union {
const char *name;
unsigned char fpr[32];
u32 kid[2]; /* Note that this is in native endianness. */
unsigned char grip[KEYGRIP_LEN];
unsigned char ubid[UBID_LEN];
} u;
byte name_used;/* The union uses NAME. */
byte snhex; /* SN above is a hexstring and not binary. */
byte fprlen; /* Only used with KEYDB_SEARCH_MODE_FPR. */
int exact; /* Use exactly this key ('!' suffix in gpg). */
};
struct keydb_search_desc;
typedef struct keydb_search_desc KEYDB_SEARCH_DESC;
typedef struct keydb_search_desc KEYBOX_SEARCH_DESC;
#endif /*KEYBOX_SEARCH_DESC_H*/
diff --git a/kbx/keyboxd.c b/kbx/keyboxd.c
index 73905eb7d..ad14d8681 100644
--- a/kbx/keyboxd.c
+++ b/kbx/keyboxd.c
@@ -1,1939 +1,1939 @@
/* keyboxd.c - The GnuPG Keybox Daemon
* Copyright (C) 2000-2020 Free Software Foundation, Inc.
* Copyright (C) 2000-2019 Werner Koch
* Copyright (C) 2015-2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0+
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#include <sys/stat.h>
#ifdef HAVE_W32_SYSTEM
# ifndef WINVER
# define WINVER 0x0500 /* Same as in common/sysutils.c */
# endif
# include <winsock2.h>
#else /*!HAVE_W32_SYSTEM*/
# include <sys/socket.h>
# include <sys/un.h>
#endif /*!HAVE_W32_SYSTEM*/
#include <unistd.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "keyboxd.h"
#include <assuan.h> /* Malloc hooks and socket wrappers. */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "../common/gc-opt-flags.h"
#include "../common/exechelp.h"
#include "../common/comopt.h"
#include "frontend.h"
/* Urrgs: Put this into a separate header - but it needs assuan.h first. */
extern int kbxd_assuan_log_monitor (assuan_context_t ctx, unsigned int cat,
const char *msg);
enum cmd_and_opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugWait,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oStealSocket,
oLogFile,
oServer,
oDaemon,
oFakedSystemTime,
oListenBacklog,
oDisableCheckOwnSocket,
oDummy
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oServer, "server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oStealSocket, "steal-socket", "@"),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_s (oLogFile, "log-file", N_("use a log file for the server")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_n (oDisableCheckOwnSocket, "disable-check-own-socket", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_end () /* End of list */
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ 77, NULL } /* 77 := Do not exit on "help" or "?". */
};
/* CHECK_OWN_SOCKET_INTERVAL defines how often we check our own socket
* in standard socket mode. If that value is 0 we don't check at all.
* Values is in seconds. */
#define CHECK_OWN_SOCKET_INTERVAL (60)
/* CHECK_PROBLEMS_INTERVAL defines how often we check the existence of
* homedir. Value is in seconds. */
#define CHECK_PROBLEMS_INTERVAL (4)
/* The list of open file descriptors at startup. Note that this list
* has been allocated using the standard malloc. */
#ifndef HAVE_W32_SYSTEM
static int *startup_fd_list;
#endif
/* The signal mask at startup and a flag telling whether it is valid. */
#ifdef HAVE_SIGPROCMASK
static sigset_t startup_signal_mask;
static int startup_signal_mask_valid;
#endif
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* Flag indicating to start the daemon even if one already runs. */
static int steal_socket;
/* Flag to monitor problems. */
static int problem_detected;
#define KEYBOXD_PROBLEM_SOCKET_TAKEOVER (1 << 0)
#define KEYBOXD_PROBLEM_HOMEDIR_REMOVED (1 << 1)
/* Flag to indicate that we shall not watch our own socket. */
static int disable_check_own_socket;
/* Flag to inhibit socket removal in cleanup. */
static int inhibit_socket_removal;
/* Name of the communication socket used for client requests. */
static char *socket_name;
/* We need to keep track of the server's nonces (these are dummies for
* POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. We use the same value for
* all sockets - 64 is on current Linux half of the default maximum.
* Let's try this as default. Change at runtime with --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
/* The event to break the select call. */
static HANDLE the_event2;
#elif defined(HAVE_PSELECT_NO_EINTR)
/* An FD to break the select call. */
static int event_pipe_fd;
#else
/* PID of the main thread. */
static pid_t main_thread_pid;
#endif
/* Name of a config file, which will be reread on a HUP if it is not NULL. */
static char *config_filename;
/* Keep track of the current log file so that we can avoid updating
* the log file after a SIGHUP if it didn't changed. Malloced. */
static char *current_logfile;
/* Number of active connections. */
static int active_connections;
/* This object is used to dispatch progress messages from Libgcrypt to
* the right thread. Given that we will have at max only a few dozen
* connections at a time, using a linked list is the easiest way to
* handle this. */
struct progress_dispatch_s
{
struct progress_dispatch_s *next;
/* The control object of the connection. If this is NULL no
* connection is associated with this item and it is free for reuse
* by new connections. */
ctrl_t ctrl;
/* The thread id of (npth_self) of the connection. */
npth_t tid;
/* The callback set by the connection. This is similar to the
* Libgcrypt callback but with the control object passed as the
* first argument. */
void (*cb)(ctrl_t ctrl,
const char *what, int printchar,
int current, int total);
};
struct progress_dispatch_s *progress_dispatch_list;
/*
* Local prototypes.
*/
static char *create_socket_name (char *standard_name, int with_homedir);
static gnupg_fd_t create_server_socket (char *name, int cygwin,
assuan_sock_nonce_t *nonce);
static void create_directories (void);
static void kbxd_libgcrypt_progress_cb (void *data, const char *what,
int printchar,
int current, int total);
static void kbxd_init_default_ctrl (ctrl_t ctrl);
static void kbxd_deinit_default_ctrl (ctrl_t ctrl);
static void handle_connections (gnupg_fd_t listen_fd);
static int check_for_running_kbxd (int silent);
#if CHECK_OWN_SOCKET_INTERVAL > 0
static void *check_own_socket_thread (void *arg);
#endif
static void *check_others_thread (void *arg);
/*
* Functions.
*/
/* Allocate a string describing a library version by calling a GETFNC.
* This function is expected to be called only once. GETFNC is
* expected to have a semantic like gcry_check_version (). */
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
return xstrconcat (libname, " ", getfnc (NULL), NULL);
}
/* Return strings describing this program. The case values are
* described in Libgpg-error. The values here override the default
* values given by strusage. */
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "keyboxd (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
/* TRANSLATORS: @EMAIL@ will get replaced by the actual bug
reporting address. This is so that we can change the
reporting address without breaking the translations. */
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: keyboxd [options] (-h for help)");
break;
case 41: p = _("Syntax: keyboxd [options] [command [args]]\n"
"Public key management for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
/* Setup the debugging. Note that we don't fail here, because it is
* important to keep keyboxd running even after re-reading the options
* due to a SIGHUP. */
static void
set_debug (void)
{
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
/* Helper for cleanup to remove one socket with NAME. */
static void
remove_socket (char *name)
{
if (name && *name)
{
gnupg_remove (name);
*name = 0;
}
}
/* Cleanup code for this program. This is either called has an atexit
handler or directly. */
static void
cleanup (void)
{
static int done;
if (done)
return;
done = 1;
if (!inhibit_socket_removal)
remove_socket (socket_name);
}
/* Handle options which are allowed to be reset after program start.
* Return true when the current option in PARGS could be handled and
* false if not. As a special feature, passing a value of NULL for
* PARGS, resets the options to the default. REREAD should be set
* true if it is not the initial option parsing. */
static int
parse_rereadable_options (gpgrt_argparse_t *pargs, int reread)
{
if (!pargs)
{ /* reset mode */
opt.quiet = 0;
opt.verbose = 0;
opt.debug = 0;
disable_check_own_socket = 0;
return 1;
}
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags);
break;
case oDebugAll: opt.debug = ~0; break;
case oLogFile:
if (!reread)
return 0; /* not handled */
if (!current_logfile || !pargs->r.ret_str
|| strcmp (current_logfile, pargs->r.ret_str))
{
log_set_file (pargs->r.ret_str);
xfree (current_logfile);
current_logfile = xtrystrdup (pargs->r.ret_str);
}
break;
case oDisableCheckOwnSocket: disable_check_own_socket = 1; break;
default:
return 0; /* not handled */
}
return 1; /* handled */
}
/* Fixup some options after all have been processed. */
static void
finalize_rereadable_options (void)
{
}
static void
thread_init_once (void)
{
static int npth_initialized = 0;
if (!npth_initialized)
{
npth_initialized++;
npth_init ();
}
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
/* Now that we have set the syscall clamp we need to tell Libgcrypt
* that it should get them from libgpg-error. Note that Libgcrypt
* has already been initialized but at that point nPth was not
* initialized and thus Libgcrypt could not set its system call
* clamp. */
gcry_control (GCRYCTL_REINIT_SYSCALL_CLAMP, 0, 0);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
}
static void
initialize_modules (void)
{
thread_init_once ();
}
/* The main entry point. */
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
char *configname = NULL;
int debug_argparser = 0;
int pipe_server = 0;
int is_daemon = 0;
int nodetach = 0;
char *logfile = NULL;
int gpgconf_list = 0;
int debug_wait = 0;
struct assuan_malloc_hooks malloc_hooks;
early_system_init ();
/* Before we do anything else we save the list of currently open
* file descriptors and the signal mask. This info is required to
* do the exec call properly. We don't need it on Windows. */
#ifndef HAVE_W32_SYSTEM
startup_fd_list = get_all_open_fds ();
#endif /*!HAVE_W32_SYSTEM*/
#ifdef HAVE_SIGPROCMASK
if (!sigprocmask (SIG_UNBLOCK, NULL, &startup_signal_mask))
startup_signal_mask_valid = 1;
#endif /*HAVE_SIGPROCMASK*/
/* Set program name etc. */
gpgrt_set_strusage (my_strusage);
log_set_prefix ("keyboxd", GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
gcry_control (GCRYCTL_DISABLE_SECMEM, 0);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, kbxd_assuan_log_monitor);
setup_libgcrypt_logging ();
gcry_set_progress_handler (kbxd_libgcrypt_progress_cb, NULL);
/* Set default options. */
parse_rereadable_options (NULL, 0); /* Reset them to default values. */
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, "keyboxd" EXTSEP_S "conf"))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
continue;
}
if (parse_rereadable_options (&pargs, 0))
continue; /* Already handled */
switch (pargs.r_opt)
{
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oNoGreeting: /* Dummy option. */ break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oStealSocket: steal_socket = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oServer: pipe_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL);
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
"keyboxd" EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit (2);
/* Get a default log file from common.conf. */
if (!logfile && !parse_comopt (GNUPG_MODULE_NAME_KEYBOXD, debug_argparser))
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
finalize_rereadable_options ();
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
#ifdef ENABLE_NLS
/* keyboxd usually does not output any messages because it runs in
* the background. For log files it is acceptable to have messages
* always encoded in utf-8. We switch here to utf-8, so that
* commands like --help still give native messages. It is far
* easier to switch only once instead of for every message and it
* actually helps when more then one thread is active (avoids an
* extra copy step). */
bind_textdomain_codeset (PACKAGE_GT, "UTF-8");
#endif
if (!pipe_server && !is_daemon && !gpgconf_list)
{
/* We have been called without any command and thus we merely
* check whether an instance of us is already running. We do
* this right here so that we don't clobber a logfile with this
* check but print the status directly to stderr. */
opt.debug = 0;
set_debug ();
check_for_running_kbxd (0);
kbxd_exit (0);
}
set_debug ();
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
/* Try to create missing directories. */
create_directories ();
if (debug_wait && pipe_server)
{
thread_init_once ();
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (gpgconf_list == 2)
kbxd_exit (0);
else if (gpgconf_list)
{
kbxd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
current_logfile = xstrdup (logfile);
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
initialize_modules ();
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
/* kbxd_set_database (ctrl, "pubring.kbx", 0); */
kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_start_command_handler (ctrl, GNUPG_INVALID_FD, 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
else if (!is_daemon)
; /* NOTREACHED */
else
{ /* Regular daemon mode. */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
#endif
/* Create the sockets. */
socket_name = create_socket_name (KEYBOXD_SOCK_NAME, 1);
fd = create_server_socket (socket_name, 0, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)nodetach;
initialize_modules ();
#else /*!HAVE_W32_SYSTEM*/
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* We are the parent */
/* Close the socket FD. */
close (fd);
/* The signal mask might not be correct right now and thus
* we restore it. That is not strictly necessary but some
* programs falsely assume a cleared signal mask. */
#ifdef HAVE_SIGPROCMASK
if (startup_signal_mask_valid)
{
if (sigprocmask (SIG_SETMASK, &startup_signal_mask, NULL))
log_error ("error restoring signal mask: %s\n",
strerror (errno));
}
else
log_info ("no saved signal mask\n");
#endif /*HAVE_SIGPROCMASK*/
*socket_name = 0; /* Don't let cleanup() remove the socket -
the child should do this from now on */
exit (0);
/*NOTREACHED*/
} /* End parent */
/*
* This is the child
*/
initialize_modules ();
/* Detach from tty and put process into a new session */
if (!nodetach)
{
int i;
unsigned int oldflags;
/* Close stdin, stdout and stderr unless it is the log stream */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( ! close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
log_get_prefix (&oldflags);
log_set_prefix (NULL, oldflags | GPGRT_LOG_RUN_DETACHED);
opt.running_detached = 1;
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
{
ctrl_t ctrl;
ctrl = xtrycalloc (1, sizeof *ctrl);
if (!ctrl)
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
kbxd_exit (1);
}
kbxd_init_default_ctrl (ctrl);
/* kbxd_set_database (ctrl, "pubring.kbx", 0); */
kbxd_set_database (ctrl, "pubring.db", 0);
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
}
log_info ("%s %s started\n", gpgrt_strusage(11), gpgrt_strusage(13));
handle_connections (fd);
assuan_sock_close (fd);
}
return 0;
}
/* Exit entry point. This function should be called instead of a
plain exit. */
void
kbxd_exit (int rc)
{
/* As usual we run our cleanup handler. */
cleanup ();
/* at this time a bit annoying */
if ((opt.debug & DBG_MEMSTAT_VALUE))
gcry_control (GCRYCTL_DUMP_MEMORY_STATS );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
/* This is our callback function for gcrypt progress messages. It is
* set once at startup and dispatches progress messages to the
* corresponding threads of ours. */
static void
kbxd_libgcrypt_progress_cb (void *data, const char *what, int printchar,
int current, int total)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
(void)data;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch && dispatch->cb)
dispatch->cb (dispatch->ctrl, what, printchar, current, total);
}
/* If a progress dispatcher callback has been associated with the
* current connection unregister it. */
static void
unregister_progress_cb (void)
{
struct progress_dispatch_s *dispatch;
npth_t mytid = npth_self ();
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (dispatch)
{
dispatch->ctrl = NULL;
dispatch->cb = NULL;
}
}
/* Setup a progress callback CB for the current connection. Using a
* CB of NULL disables the callback. */
void
kbxd_set_progress_cb (void (*cb)(ctrl_t ctrl, const char *what,
int printchar, int current, int total),
ctrl_t ctrl)
{
struct progress_dispatch_s *dispatch, *firstfree;
npth_t mytid = npth_self ();
firstfree = NULL;
for (dispatch = progress_dispatch_list; dispatch; dispatch = dispatch->next)
{
if (dispatch->ctrl && dispatch->tid == mytid)
break;
if (!dispatch->ctrl && !firstfree)
firstfree = dispatch;
}
if (!dispatch) /* None allocated: Reuse or allocate a new one. */
{
if (firstfree)
{
dispatch = firstfree;
}
else if ((dispatch = xtrycalloc (1, sizeof *dispatch)))
{
dispatch->next = progress_dispatch_list;
progress_dispatch_list = dispatch;
}
else
{
log_error ("error allocating new progress dispatcher slot: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
return;
}
dispatch->ctrl = ctrl;
dispatch->tid = mytid;
}
dispatch->cb = cb;
}
/* Each thread has its own local variables conveyed by a control
* structure usually identified by an argument named CTRL. This
* function is called immediately after allocating the control
* structure. Its purpose is to setup the default values for that
* structure. Note that some values may have already been set. */
static void
kbxd_init_default_ctrl (ctrl_t ctrl)
{
ctrl->magic = SERVER_CONTROL_MAGIC;
}
/* Release all resources allocated by default in the control
structure. This is the counterpart to kbxd_init_default_ctrl. */
static void
kbxd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
kbxd_release_session_info (ctrl);
ctrl->magic = 0xdeadbeef;
unregister_progress_cb ();
xfree (ctrl->lc_messages);
}
/* Reread parts of the configuration. Note, that this function is
* obviously not thread-safe and should only be called from the PTH
* signal handler.
*
* Fixme: Due to the way the argument parsing works, we create a
* memory leak here for all string type arguments. There is currently
* no clean way to tell whether the memory for the argument has been
* allocated or points into the process's original arguments. Unless
* we have a mechanism to tell this, we need to live on with this. */
static void
reread_configuration (void)
{
gpgrt_argparse_t pargs;
char *twopart;
int dummy;
int logfile_seen = 0;
if (!config_filename)
goto finish; /* No config file. */
twopart = strconcat ("keyboxd" EXTSEP_S "conf" PATHSEP_S,
config_filename, NULL);
if (!twopart)
return; /* Out of core. */
parse_rereadable_options (NULL, 1); /* Start from the default values. */
memset (&pargs, 0, sizeof pargs);
dummy = 0;
pargs.argc = &dummy;
pargs.flags = (ARGPARSE_FLAG_KEEP
|ARGPARSE_FLAG_SYS
|ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, twopart))
{
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
}
else if (pargs.r_opt < -1)
pargs.err = ARGPARSE_PRINT_WARNING;
else /* Try to parse this option - ignore unchangeable ones. */
{
if (pargs.r_opt == oLogFile)
logfile_seen = 1;
parse_rereadable_options (&pargs, 1);
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
xfree (twopart);
finalize_rereadable_options ();
set_debug ();
finish:
/* Get a default log file from common.conf. */
if (!logfile_seen && !parse_comopt (GNUPG_MODULE_NAME_KEYBOXD, !!opt.debug))
{
if (!current_logfile || !comopt.logfile
|| strcmp (current_logfile, comopt.logfile))
{
log_set_file (comopt.logfile);
xfree (current_logfile);
current_logfile = comopt.logfile? xtrystrdup (comopt.logfile) : NULL;
}
}
}
/* Return the file name of the socket we are using for requests. */
const char *
get_kbxd_socket_name (void)
{
const char *s = socket_name;
return (s && *s)? s : NULL;
}
/* Return the number of active connections. */
int
get_kbxd_active_connection_count (void)
{
return active_connections;
}
/* Under W32, this function returns the handle of the scdaemon
notification event. Calling it the first time creates that
event. */
#if defined(HAVE_W32_SYSTEM)
static void *
create_an_event (void)
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
/* We need to use a manual reset event object due to the way our
w32-pth wait function works: If we would use an automatic
reset event we are not able to figure out which handle has
been signaled because at the time we single out the signaled
handles using WFSO the event has already been reset due to
the WFMO. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create an event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for an event failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
return h2;
}
return INVALID_HANDLE_VALUE;
}
#endif /*HAVE_W32_SYSTEM*/
/* Create a name for the socket in the home directory as using
* STANDARD_NAME. We also check for valid characters as well as
* against a maximum allowed length for a Unix domain socket is done.
* The function terminates the process in case of an error. The
* function returns a pointer to an allocated string with the absolute
* name of the socket used. */
static char *
create_socket_name (char *standard_name, int with_homedir)
{
char *name;
if (with_homedir)
name = make_filename (gnupg_socketdir (), standard_name, NULL);
else
name = make_filename (standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
kbxd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
* or terminates the process in case of an error. If CYGWIN is set a
* Cygwin compatible socket is created (Windows only). */
static gnupg_fd_t
create_server_socket (char *name, int cygwin, assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == ASSUAN_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
kbxd_exit (2);
}
if (cygwin)
assuan_sock_set_flag (fd, "cygwin", 1);
unaddr = xmalloc (sizeof *unaddr);
addr = (struct sockaddr*)unaddr;
if (assuan_sock_set_sockaddr_un (name, addr, NULL))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1
&& (errno == EADDRINUSE
#ifdef HAVE_W32_SYSTEM
|| errno == EEXIST
#endif
))
{
/* Check whether a keyboxd is already running. */
if (!check_for_running_kbxd (1))
{
if (steal_socket)
log_info (N_("trying to steal socket from running %s\n"),
"keyboxd");
else
{
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX);
log_set_file (NULL);
log_error (_("a keyboxd is already running - "
"not starting a new one\n"));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
}
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1 && (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
/* We use gpg_strerror here because it allows us to get strings
for some W32 socket error codes. */
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
*name = 0; /* Inhibit removal of the socket by cleanup(). */
xfree (unaddr);
kbxd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog ) == -1)
{
log_error ("listen(fd,%d) failed: %s\n", listen_backlog, strerror (errno));
*name = 0; /* Inhibit removal of the socket by cleanup(). */
assuan_sock_close (fd);
xfree (unaddr);
kbxd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
xfree (unaddr);
return fd;
}
/* Check that the directory for storing the public keys exists and
* create it if not. This function won't fail as it is only a
* convenience function and not strictly necessary. */
static void
create_public_keys_directory (const char *home)
{
char *fname;
struct stat statbuf;
fname = make_filename (home, GNUPG_PUBLIC_KEYS_DIR, NULL);
if (gnupg_stat (fname, &statbuf) && errno == ENOENT)
{
if (gnupg_mkdir (fname, "-rwxr-x"))
log_error (_("can't create directory '%s': %s\n"),
fname, strerror (errno) );
else if (!opt.quiet)
log_info (_("directory '%s' created\n"), fname);
}
if (gnupg_chmod (fname, "-rwxr-x"))
log_error (_("can't set permissions of '%s': %s\n"),
fname, strerror (errno));
xfree (fname);
}
/* Create the directory only if the supplied directory name is the
* same as the default one. This way we avoid to create arbitrary
* directories when a non-default home directory is used. To cope
* with HOME, we compare only the suffix if we see that the default
* homedir does start with a tilde. We don't stop here in case of
* problems because other functions will throw an error anyway.*/
static void
create_directories (void)
{
struct stat statbuf;
const char *defhome = standard_homedir ();
char *home;
home = make_filename (gnupg_homedir (), NULL);
if (gnupg_stat (home, &statbuf))
{
if (errno == ENOENT)
{
if (
#ifdef HAVE_W32_SYSTEM
( !compare_filenames (home, defhome) )
#else
(*defhome == '~'
&& (strlen (home) >= strlen (defhome+1)
&& !strcmp (home + strlen(home)
- strlen (defhome+1), defhome+1)))
|| (*defhome != '~' && !strcmp (home, defhome) )
#endif
)
{
if (gnupg_mkdir (home, "-rwx"))
log_error (_("can't create directory '%s': %s\n"),
home, strerror (errno) );
else
{
if (!opt.quiet)
log_info (_("directory '%s' created\n"), home);
create_public_keys_directory (home);
}
}
}
else
log_error (_("stat() failed for '%s': %s\n"), home, strerror (errno));
}
else if ( !S_ISDIR(statbuf.st_mode))
{
log_error (_("can't use '%s' as home directory\n"), home);
}
else /* exists and is a directory. */
{
create_public_keys_directory (home);
}
xfree (home);
}
/* A global function which allows us to call the reload stuff from
* other places too. This is only used when build for W32. */
void
kbxd_sighup_action (void)
{
log_info ("SIGHUP received - "
"re-reading configuration and flushing cache\n");
reread_configuration ();
}
/* A helper function to handle SIGUSR2. */
static void
kbxd_sigusr2_action (void)
{
if (opt.verbose)
log_info ("SIGUSR2 received - no action\n");
/* Nothing to do right now. */
}
#ifndef HAVE_W32_SYSTEM
/* The signal handler for this program. It is expected to be run in
* its own thread and not in the context of a signal handler. */
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
kbxd_sighup_action ();
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
break;
case SIGUSR2:
kbxd_sigusr2_action ();
break;
case SIGCONT:
/* Do nothing, but break the syscall. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i open connections\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13) );
cleanup ();
kbxd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
kbxd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif
/* Check the nonce on a new connection. This is a NOP unless we
are using our Unix domain socket emulation under Windows. */
static int
check_nonce (ctrl_t ctrl, assuan_sock_nonce_t *nonce)
{
if (assuan_sock_check_nonce (ctrl->thread_startup.fd, nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return -1;
}
else
return 0;
}
static void *
do_start_connection_thread (ctrl_t ctrl)
{
static unsigned int last_session_id;
unsigned int session_id;
active_connections++;
kbxd_init_default_ctrl (ctrl);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d started\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
session_id = ++last_session_id;
if (!session_id)
session_id = ++last_session_id;
kbxd_start_command_handler (ctrl, ctrl->thread_startup.fd, session_id);
if (opt.verbose && !DBG_IPC)
log_info (_("handler 0x%lx for fd %d terminated\n"),
(unsigned long) npth_self(), FD_DBG (ctrl->thread_startup.fd));
kbxd_deinit_default_ctrl (ctrl);
xfree (ctrl);
active_connections--;
return NULL;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (check_nonce (ctrl, &socket_nonce))
{
log_error ("handler 0x%lx nonce check FAILED\n",
(unsigned long) npth_self());
return NULL;
}
return do_start_connection_thread (ctrl);
}
static void
keyboxd_kick_the_loop (void)
{
/* Kick the select loop. */
#ifdef HAVE_W32_SYSTEM
int ret = SetEvent (the_event2);
if (ret == 0)
log_error ("SetEvent for agent_kick_the_loop failed: %s\n",
w32_strerror (-1));
#else
# ifdef HAVE_PSELECT_NO_EINTR
write (event_pipe_fd, "", 1);
# else
int ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("sending signal for agent_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
# endif
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
* thread after accepting a connection. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
gpg_error_t err;
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int ret;
gnupg_fd_t fd;
int nfd;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
# ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
# endif
#endif
int sock_inotify_fd = -1;
int home_inotify_fd = -1;
struct {
const char *name;
void *(*func) (void *arg);
gnupg_fd_t l_fd;
} listentbl[] = {
{ "std", start_connection_thread },
};
int have_homedir_inotify = 0;
ret = npth_attr_init(&tattr);
if (ret)
log_fatal ("error allocating thread attributes: %s\n", strerror (ret));
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
# ifdef HAVE_PSELECT_NO_EINTR
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
event_pipe_fd = pipe_fd[1];
# else
main_thread_pid = getpid ();
# endif
#else
events[0] = the_event2 = create_an_event ();
events[1] = INVALID_HANDLE_VALUE;
#endif
if (disable_check_own_socket)
sock_inotify_fd = -1;
else if ((err = gnupg_inotify_watch_socket (&sock_inotify_fd, socket_name)))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by socket removal: %s\n",
gpg_strerror (err));
}
if ((err = gnupg_inotify_watch_delete_self (&home_inotify_fd,
gnupg_homedir ())))
{
if (gpg_err_code (err) != GPG_ERR_NOT_SUPPORTED)
log_info ("error enabling daemon termination by homedir removal: %s\n",
gpg_strerror (err));
}
else
have_homedir_inotify = 1;
#if CHECK_OWN_SOCKET_INTERVAL > 0
if (!disable_check_own_socket && sock_inotify_fd == -1)
{
npth_t thread;
err = npth_create (&thread, &tattr, check_own_socket_thread, NULL);
if (err)
log_error ("error spawning check_own_socket_thread: %s\n", strerror (err));
}
#endif
if (!have_homedir_inotify)
{
npth_t thread;
err = npth_create (&thread, &tattr, check_others_thread, NULL);
if (err)
log_error ("error spawning check_others_thread: %s\n", strerror (err));
}
FD_ZERO (&fdset);
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2NUM (listen_fd);
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
if (sock_inotify_fd > nfd)
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
listentbl[0].l_fd = listen_fd;
for (;;)
{
/* Shutdown test. */
if (shutdown_pending)
{
if (!active_connections)
break; /* ready */
/* Do not accept new connections but keep on running the
* loop to cope with the timer events.
*
* Note that we do not close the listening socket because a
* client trying to connect to that socket would instead
* restart a new keyboxd instance - which is unlikely the
* intention of a shutdown. */
FD_ZERO (&fdset);
nfd = -1;
if (sock_inotify_fd != -1)
{
FD_SET (sock_inotify_fd, &fdset);
nfd = sock_inotify_fd;
}
if (home_inotify_fd != -1)
{
FD_SET (home_inotify_fd, &fdset);
if (home_inotify_fd > nfd)
nfd = home_inotify_fd;
}
}
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (nfd < pipe_fd[0])
nfd = pipe_fd[0];
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (nfd+1, &read_fdset, NULL, NULL, NULL,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, NULL,
events, &events_set);
saved_errno = errno;
/* This is valid even if npth_eselect returns an error. */
if ((events_set & 1))
kbxd_sigusr2_action ();
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if ((problem_detected & KEYBOXD_PROBLEM_SOCKET_TAKEOVER))
{
/* We may not remove the socket as it is now in use by another
server. */
inhibit_socket_removal = 1;
shutdown_pending = 2;
log_info ("this process is useless - shutting down\n");
}
if ((problem_detected & KEYBOXD_PROBLEM_HOMEDIR_REMOVED))
{
shutdown_pending = 1;
log_info ("homedir has been removed - shutting down\n");
}
if (ret <= 0)
{
/* Interrupt or timeout. Will be handled when calculating the
* next timeout. */
continue;
}
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
/* The inotify fds are set even when a shutdown is pending (see
* above). So we must handle them in any case. To avoid that
* they trigger a second time we close them immediately. */
if (sock_inotify_fd != -1
&& FD_ISSET (sock_inotify_fd, &read_fdset)
&& gnupg_inotify_has_name (sock_inotify_fd, KEYBOXD_SOCK_NAME))
{
shutdown_pending = 1;
close (sock_inotify_fd);
sock_inotify_fd = -1;
log_info ("socket file has been removed - shutting down\n");
}
if (home_inotify_fd != -1
&& FD_ISSET (home_inotify_fd, &read_fdset))
{
shutdown_pending = 1;
close (home_inotify_fd);
home_inotify_fd = -1;
log_info ("homedir has been removed - shutting down\n");
}
if (!shutdown_pending)
{
int idx;
ctrl_t ctrl;
npth_t thread;
for (idx=0; idx < DIM(listentbl); idx++)
{
if (listentbl[idx].l_fd == GNUPG_INVALID_FD)
continue;
if (!FD_ISSET (FD2INT (listentbl[idx].l_fd), &read_fdset))
continue;
plen = sizeof paddr;
fd = assuan_sock_accept (listentbl[idx].l_fd,
(struct sockaddr *)&paddr, &plen);
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed for %s: %s\n",
listentbl[idx].name, strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)))
{
log_error ("error allocating connection data for %s: %s\n",
listentbl[idx].name, strerror (errno) );
assuan_sock_close (fd);
}
else
{
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr,
listentbl[idx].func, ctrl);
if (ret)
{
log_error ("error spawning connection handler for %s:"
" %s\n", listentbl[idx].name, strerror (ret));
assuan_sock_close (fd);
xfree (ctrl);
}
}
}
}
}
if (sock_inotify_fd != -1)
close (sock_inotify_fd);
if (home_inotify_fd != -1)
close (home_inotify_fd);
#ifdef HAVE_W32_SYSTEM
if (the_event2 != INVALID_HANDLE_VALUE)
CloseHandle (the_event2);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
npth_attr_destroy (&tattr);
}
#if CHECK_OWN_SOCKET_INTERVAL > 0
/* Helper for check_own_socket. */
static gpg_error_t
check_own_socket_pid_cb (void *opaque, const void *buffer, size_t length)
{
membuf_t *mb = opaque;
put_membuf (mb, buffer, length);
return 0;
}
/* Check whether we are still listening on our own socket. In case
another gpg-agent process started after us has taken ownership of
our socket, we would linger around without any real task. Thus we
better check once in a while whether we are really needed. */
static int
do_check_own_socket (const char *sockname)
{
int rc;
assuan_context_t ctx = NULL;
membuf_t mb;
char *buffer;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("can't allocate assuan context: %s\n", gpg_strerror (rc));
goto leave;
}
assuan_set_flag (ctx, ASSUAN_NO_LOGGING, 1);
rc = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
if (rc)
{
log_error ("can't connect my own socket: %s\n", gpg_strerror (rc));
goto leave;
}
init_membuf (&mb, 100);
rc = assuan_transact (ctx, "GETINFO pid", check_own_socket_pid_cb, &mb,
NULL, NULL, NULL, NULL);
put_membuf (&mb, "", 1);
buffer = get_membuf (&mb, NULL);
if (rc || !buffer)
{
log_error ("sending command \"%s\" to my own socket failed: %s\n",
"GETINFO pid", gpg_strerror (rc));
rc = 1;
}
else if ( (pid_t)strtoul (buffer, NULL, 10) != getpid ())
{
log_error ("socket is now serviced by another server\n");
rc = 1;
}
else if (opt.verbose > 1)
log_error ("socket is still served by this server\n");
xfree (buffer);
leave:
if (ctx)
assuan_release (ctx);
return rc;
}
/* The thread running the actual check. */
static void *
check_own_socket_thread (void *arg)
{
char *sockname;
(void)arg;
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return NULL; /* Out of memory. */
while (!problem_detected)
{
if (shutdown_pending)
goto leave;
gnupg_sleep (CHECK_OWN_SOCKET_INTERVAL);
if (do_check_own_socket (sockname))
problem_detected |= KEYBOXD_PROBLEM_SOCKET_TAKEOVER;
}
keyboxd_kick_the_loop ();
leave:
xfree (sockname);
return NULL;
}
#endif
/* The thread running other checks. */
static void *
check_others_thread (void *arg)
{
const char *homedir = gnupg_homedir ();
(void)arg;
while (!problem_detected)
{
struct stat statbuf;
if (shutdown_pending)
goto leave;
gnupg_sleep (CHECK_PROBLEMS_INTERVAL);
/* Check whether the homedir is still available. */
if (gnupg_stat (homedir, &statbuf) && errno == ENOENT)
problem_detected |= KEYBOXD_PROBLEM_HOMEDIR_REMOVED;
}
keyboxd_kick_the_loop ();
leave:
return NULL;
}
/* Figure out whether a keyboxd is available and running. Prints an
* error if not. If SILENT is true, no messages are printed. Returns
* 0 if the agent is running. */
static int
check_for_running_kbxd (int silent)
{
gpg_error_t err;
char *sockname;
assuan_context_t ctx = NULL;
sockname = make_filename_try (gnupg_socketdir (), KEYBOXD_SOCK_NAME, NULL);
if (!sockname)
return gpg_error_from_syserror ();
err = assuan_new (&ctx);
if (!err)
err = assuan_socket_connect (ctx, sockname, (pid_t)(-1), 0);
xfree (sockname);
if (err)
{
if (!silent)
log_error (_("no keyboxd running in this session\n"));
if (ctx)
assuan_release (ctx);
return -1;
}
if (!opt.quiet && !silent)
log_info ("keyboxd running and available\n");
assuan_release (ctx);
return 0;
}
diff --git a/m4/gpg-error.m4 b/m4/gpg-error.m4
index 7fa52b127..27e770c97 100644
--- a/m4/gpg-error.m4
+++ b/m4/gpg-error.m4
@@ -1,225 +1,225 @@
# gpg-error.m4 - autoconf macro to detect libgpg-error.
# Copyright (C) 2002, 2003, 2004, 2011, 2014, 2018, 2020, 2021, 2022
# g10 Code GmbH
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Last-changed: 2023-04-01
dnl
dnl Find gpg-error-config, for backward compatibility
dnl
dnl _AM_PATH_POSSIBLE_GPG_ERROR_CONFIG
AC_DEFUN([_AM_PATH_POSSIBLE_GPG_ERROR_CONFIG],[dnl
gpg_error_config_prefix=""
dnl --with-libgpg-error-prefix=PFX is the preferred name for this option,
dnl since that is consistent with how our three siblings use the directory/
dnl package name in --with-$dir_name-prefix=PFX.
AC_ARG_WITH(libgpg-error-prefix,
AS_HELP_STRING([--with-libgpg-error-prefix=PFX],
[prefix where GPG Error is installed (optional)]),
[gpg_error_config_prefix="$withval"])
dnl Accept --with-gpg-error-prefix and make it work the same as
dnl --with-libgpg-error-prefix above, for backwards compatibility,
dnl but do not document this old, inconsistently-named option.
AC_ARG_WITH(gpg-error-prefix,,
[gpg_error_config_prefix="$withval"])
if test x"${GPG_ERROR_CONFIG}" = x ; then
if test x"${gpg_error_config_prefix}" != x ; then
GPG_ERROR_CONFIG="${gpg_error_config_prefix}/bin/gpg-error-config"
else
case "${SYSROOT}" in
/*)
if test -x "${SYSROOT}/bin/gpg-error-config" ; then
GPG_ERROR_CONFIG="${SYSROOT}/bin/gpg-error-config"
fi
;;
'')
;;
*)
AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.])
;;
esac
fi
fi
AC_PATH_PROG(GPG_ERROR_CONFIG, gpg-error-config, no)
])
dnl
dnl Find gpgrt-config, which uses .pc file
dnl (minimum pkg-config functionality, supporting cross build)
dnl
dnl _AM_PATH_GPGRT_CONFIG
AC_DEFUN([_AM_PATH_GPGRT_CONFIG],[dnl
AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no, [$prefix/bin:$PATH])
if test "$GPGRT_CONFIG" != "no"; then
# Determine gpgrt_libdir
#
# Get the prefix of gpgrt-config assuming it's something like:
# <PREFIX>/bin/gpgrt-config
gpgrt_prefix=${GPGRT_CONFIG%/*/*}
possible_libdir1=${gpgrt_prefix}/lib
# Determine by using system libdir-format with CC, it's like:
# Normal style: /usr/lib
# GNU cross style: /usr/<triplet>/lib
# Debian style: /usr/lib/<multiarch-name>
# Fedora/openSUSE style: /usr/lib, /usr/lib32 or /usr/lib64
# It is assumed that CC is specified to the one of host on cross build.
if libdir_candidates=$(${CC:-cc} -print-search-dirs | \
sed -n -e "/^libraries/{s/libraries: =//;s/:/\\
/g;p;}"); then
# From the output of -print-search-dirs, select valid pkgconfig dirs.
libdir_candidates=$(for dir in $libdir_candidates; do
if p=$(cd $dir 2>/dev/null && pwd); then
test -d "$p/pkgconfig" && echo $p;
fi
done)
for possible_libdir0 in $libdir_candidates; do
# possible_libdir0:
# Fallback candidate, the one of system-installed (by $CC)
# (/usr/<triplet>/lib, /usr/lib/<multiarch-name> or /usr/lib32)
# possible_libdir1:
# Another candidate, user-locally-installed
# (<gpgrt_prefix>/lib)
# possible_libdir2
# Most preferred
# (<gpgrt_prefix>/<triplet>/lib,
# <gpgrt_prefix>/lib/<multiarch-name> or <gpgrt_prefix>/lib32)
if test "${possible_libdir0##*/}" = "lib"; then
possible_prefix0=${possible_libdir0%/lib}
possible_prefix0_triplet=${possible_prefix0##*/}
if test -z "$possible_prefix0_triplet"; then
continue
fi
possible_libdir2=${gpgrt_prefix}/$possible_prefix0_triplet/lib
else
possible_prefix0=${possible_libdir0%%/lib*}
possible_libdir2=${gpgrt_prefix}${possible_libdir0#$possible_prefix0}
fi
if test -f ${possible_libdir2}/pkgconfig/gpg-error.pc; then
gpgrt_libdir=${possible_libdir2}
elif test -f ${possible_libdir1}/pkgconfig/gpg-error.pc; then
gpgrt_libdir=${possible_libdir1}
elif test -f ${possible_libdir0}/pkgconfig/gpg-error.pc; then
gpgrt_libdir=${possible_libdir0}
fi
if test -n "$gpgrt_libdir"; then break; fi
done
fi
if test -z "$gpgrt_libdir"; then
# No valid pkgconfig dir in any of the system directories, fallback
gpgrt_libdir=${possible_libdir1}
fi
else
unset GPGRT_CONFIG
fi
if test -n "$gpgrt_libdir"; then
GPGRT_CONFIG="$GPGRT_CONFIG --libdir=$gpgrt_libdir"
if $GPGRT_CONFIG gpg-error >/dev/null 2>&1; then
GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error"
AC_MSG_NOTICE([Use gpgrt-config with $gpgrt_libdir as gpg-error-config])
gpg_error_config_version=`$GPG_ERROR_CONFIG --modversion`
else
gpg_error_config_version=`$GPG_ERROR_CONFIG --version`
unset GPGRT_CONFIG
fi
elif test "$GPG_ERROR_CONFIG" != "no"; then
gpg_error_config_version=`$GPG_ERROR_CONFIG --version`
unset GPGRT_CONFIG
fi
])
dnl AM_PATH_GPG_ERROR([MINIMUM-VERSION,
dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]])
dnl
dnl Test for libgpg-error and define GPG_ERROR_CFLAGS, GPG_ERROR_LIBS,
dnl GPG_ERROR_MT_CFLAGS, and GPG_ERROR_MT_LIBS. The _MT_ variants are
-dnl used for programs requireing real multi thread support.
+dnl used for programs requiring real multi thread support.
dnl
dnl If a prefix option is not used, the config script is first
dnl searched in $SYSROOT/bin and then along $PATH. If the used
dnl config script does not match the host specification the script
dnl is added to the gpg_config_script_warn variable.
dnl
AC_DEFUN([AM_PATH_GPG_ERROR],[dnl
AC_REQUIRE([AC_CANONICAL_HOST])dnl
AC_REQUIRE([_AM_PATH_POSSIBLE_GPG_ERROR_CONFIG])dnl
AC_REQUIRE([_AM_PATH_GPGRT_CONFIG])dnl
min_gpg_error_version=ifelse([$1], ,1.33,$1)
ok=no
if test "$GPG_ERROR_CONFIG" != "no"; then
req_major=`echo $min_gpg_error_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)/\1/'`
req_minor=`echo $min_gpg_error_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)/\2/'`
major=`echo $gpg_error_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'`
minor=`echo $gpg_error_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'`
if test "$major" -gt "$req_major"; then
ok=yes
else
if test "$major" -eq "$req_major"; then
if test "$minor" -ge "$req_minor"; then
ok=yes
fi
fi
fi
fi
AC_MSG_CHECKING(for GPG Error - version >= $min_gpg_error_version)
if test $ok = yes; then
GPG_ERROR_CFLAGS=`$GPG_ERROR_CONFIG --cflags`
GPG_ERROR_LIBS=`$GPG_ERROR_CONFIG --libs`
if test -z "$GPGRT_CONFIG"; then
GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG --mt --cflags 2>/dev/null`
GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG --mt --libs 2>/dev/null`
else
GPG_ERROR_MT_CFLAGS=`$GPG_ERROR_CONFIG --variable=mtcflags 2>/dev/null`
GPG_ERROR_MT_CFLAGS="$GPG_ERROR_CFLAGS${GPG_ERROR_CFLAGS:+ }$GPG_ERROR_MT_CFLAGS"
GPG_ERROR_MT_LIBS=`$GPG_ERROR_CONFIG --variable=mtlibs 2>/dev/null`
GPG_ERROR_MT_LIBS="$GPG_ERROR_LIBS${GPG_ERROR_LIBS:+ }$GPG_ERROR_MT_LIBS"
fi
AC_MSG_RESULT([yes ($gpg_error_config_version)])
ifelse([$2], , :, [$2])
if test -z "$GPGRT_CONFIG"; then
gpg_error_config_host=`$GPG_ERROR_CONFIG --host 2>/dev/null || echo none`
else
gpg_error_config_host=`$GPG_ERROR_CONFIG --variable=host 2>/dev/null || echo none`
fi
if test x"$gpg_error_config_host" != xnone ; then
if test x"$gpg_error_config_host" != x"$host" ; then
AC_MSG_WARN([[
***
*** The config script "$GPG_ERROR_CONFIG" was
*** built for $gpg_error_config_host and thus may not match the
*** used host $host.
*** You may want to use the configure option --with-libgpg-error-prefix
*** to specify a matching config script or use \$SYSROOT.
***]])
gpg_config_script_warn="$gpg_config_script_warn libgpg-error"
fi
fi
else
GPG_ERROR_CFLAGS=""
GPG_ERROR_LIBS=""
GPG_ERROR_MT_CFLAGS=""
GPG_ERROR_MT_LIBS=""
AC_MSG_RESULT(no)
ifelse([$3], , :, [$3])
fi
AC_SUBST(GPG_ERROR_CFLAGS)
AC_SUBST(GPG_ERROR_LIBS)
AC_SUBST(GPG_ERROR_MT_CFLAGS)
AC_SUBST(GPG_ERROR_MT_LIBS)
])
diff --git a/m4/ksba.m4 b/m4/ksba.m4
index c9dab35de..348e5f92e 100644
--- a/m4/ksba.m4
+++ b/m4/ksba.m4
@@ -1,243 +1,243 @@
# ksba.m4 - autoconf macro to detect ksba
# Copyright (C) 2002, 2018, 2024 g10 Code GmbH
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This file is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
#
# Last-changed: 2024-05-14
dnl
dnl Find gpgrt-config, which uses .pc file
dnl (minimum pkg-config functionality, supporting cross build)
dnl
dnl _AM_PATH_GPGRT_CONFIG
AC_DEFUN([_AM_PATH_GPGRT_CONFIG],[dnl
AC_PATH_PROG(GPGRT_CONFIG, gpgrt-config, no, [$prefix/bin:$PATH])
if test "$GPGRT_CONFIG" != "no"; then
# Determine gpgrt_libdir
#
# Get the prefix of gpgrt-config assuming it's something like:
# <PREFIX>/bin/gpgrt-config
gpgrt_prefix=${GPGRT_CONFIG%/*/*}
possible_libdir1=${gpgrt_prefix}/lib
# Determine by using system libdir-format with CC, it's like:
# Normal style: /usr/lib
# GNU cross style: /usr/<triplet>/lib
# Debian style: /usr/lib/<multiarch-name>
# Fedora/openSUSE style: /usr/lib, /usr/lib32 or /usr/lib64
# It is assumed that CC is specified to the one of host on cross build.
if libdir_candidates=$(${CC:-cc} -print-search-dirs | \
sed -n -e "/^libraries/{s/libraries: =//;s/:/\\
/g;p;}"); then
# From the output of -print-search-dirs, select valid pkgconfig dirs.
libdir_candidates=$(for dir in $libdir_candidates; do
if p=$(cd $dir 2>/dev/null && pwd); then
test -d "$p/pkgconfig" && echo $p;
fi
done)
for possible_libdir0 in $libdir_candidates; do
# possible_libdir0:
# Fallback candidate, the one of system-installed (by $CC)
# (/usr/<triplet>/lib, /usr/lib/<multiarch-name> or /usr/lib32)
# possible_libdir1:
# Another candidate, user-locally-installed
# (<gpgrt_prefix>/lib)
# possible_libdir2
# Most preferred
# (<gpgrt_prefix>/<triplet>/lib,
# <gpgrt_prefix>/lib/<multiarch-name> or <gpgrt_prefix>/lib32)
if test "${possible_libdir0##*/}" = "lib"; then
possible_prefix0=${possible_libdir0%/lib}
possible_prefix0_triplet=${possible_prefix0##*/}
if test -z "$possible_prefix0_triplet"; then
continue
fi
possible_libdir2=${gpgrt_prefix}/$possible_prefix0_triplet/lib
else
possible_prefix0=${possible_libdir0%%/lib*}
possible_libdir2=${gpgrt_prefix}${possible_libdir0#$possible_prefix0}
fi
if test -f ${possible_libdir2}/pkgconfig/gpg-error.pc; then
gpgrt_libdir=${possible_libdir2}
elif test -f ${possible_libdir1}/pkgconfig/gpg-error.pc; then
gpgrt_libdir=${possible_libdir1}
elif test -f ${possible_libdir0}/pkgconfig/gpg-error.pc; then
gpgrt_libdir=${possible_libdir0}
fi
if test -n "$gpgrt_libdir"; then break; fi
done
fi
if test -z "$gpgrt_libdir"; then
# No valid pkgconfig dir in any of the system directories, fallback
gpgrt_libdir=${possible_libdir1}
fi
else
unset GPGRT_CONFIG
fi
if test -n "$gpgrt_libdir"; then
GPGRT_CONFIG="$GPGRT_CONFIG --libdir=$gpgrt_libdir"
if $GPGRT_CONFIG gpg-error >/dev/null 2>&1; then
GPG_ERROR_CONFIG="$GPGRT_CONFIG gpg-error"
AC_MSG_NOTICE([Use gpgrt-config with $gpgrt_libdir as gpg-error-config])
gpg_error_config_version=`$GPG_ERROR_CONFIG --modversion`
else
gpg_error_config_version=`$GPG_ERROR_CONFIG --version`
unset GPGRT_CONFIG
fi
elif test "$GPG_ERROR_CONFIG" != "no"; then
gpg_error_config_version=`$GPG_ERROR_CONFIG --version`
unset GPGRT_CONFIG
fi
])
dnl AM_PATH_KSBA([MINIMUM-VERSION,
dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]])
dnl Test for libksba and define KSBA_CFLAGS and KSBA_LIBS
-dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed
+dnl MINIMUM-VERSION is a string with the version number optionally prefixed
dnl with the API version to also check the API compatibility. Example:
-dnl a MINIMUN-VERSION of 1:1.0.7 won't pass the test unless the installed
+dnl a MINIMUM-VERSION of 1:1.0.7 won't pass the test unless the installed
dnl version of libksba is at least 1.0.7 *and* the API number is 1. Using
dnl this features allows to prevent build against newer versions of libksba
dnl with a changed API.
dnl
AC_DEFUN([AM_PATH_KSBA],
[ AC_REQUIRE([AC_CANONICAL_HOST])dnl
AC_REQUIRE([_AM_PATH_GPGRT_CONFIG])dnl
dnl --with-libksba-prefix=PFX is the preferred name for this option,
dnl since that is consistent with how our three siblings use the directory/
dnl package name in --with-$dir_name-prefix=PFX.
AC_ARG_WITH(libksba-prefix,
AS_HELP_STRING([--with-libksba-prefix=PFX],
[prefix where KSBA is installed (optional)]),
ksba_config_prefix="$withval", ksba_config_prefix="")
dnl Accept --with-ksba-prefix and make it work the same as
dnl --with-libksba-prefix above, for backwards compatibility,
dnl but do not document this old, inconsistently-named option.
AC_ARG_WITH(ksba-prefix,,
ksba_config_prefix="$withval", ksba_config_prefix="")
if test x$ksba_config_prefix != x ; then
if test x${KSBA_CONFIG+set} != xset ; then
KSBA_CONFIG=$ksba_config_prefix/bin/ksba-config
fi
fi
use_gpgrt_config=""
if test x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then
if $GPGRT_CONFIG ksba --exists; then
KSBA_CONFIG="$GPGRT_CONFIG ksba"
AC_MSG_NOTICE([Use gpgrt-config as ksba-config])
use_gpgrt_config=yes
fi
fi
if test -z "$use_gpgrt_config"; then
AC_PATH_PROG(KSBA_CONFIG, ksba-config, no)
fi
tmp=ifelse([$1], ,1:1.0.0,$1)
if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then
req_ksba_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'`
min_ksba_version=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\2/'`
else
req_ksba_api=0
min_ksba_version="$tmp"
fi
AC_MSG_CHECKING(for KSBA - version >= $min_ksba_version)
ok=no
if test "$KSBA_CONFIG" != "no" ; then
req_major=`echo $min_ksba_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'`
req_minor=`echo $min_ksba_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'`
req_micro=`echo $min_ksba_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'`
if test -z "$use_gpgrt_config"; then
ksba_config_version=`$KSBA_CONFIG --version`
else
ksba_config_version=`$KSBA_CONFIG --modversion`
fi
major=`echo $ksba_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'`
minor=`echo $ksba_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'`
micro=`echo $ksba_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'`
if test "$major" -gt "$req_major"; then
ok=yes
else
if test "$major" -eq "$req_major"; then
if test "$minor" -gt "$req_minor"; then
ok=yes
else
if test "$minor" -eq "$req_minor"; then
if test "$micro" -ge "$req_micro"; then
ok=yes
fi
fi
fi
fi
fi
fi
if test $ok = yes; then
AC_MSG_RESULT([yes ($ksba_config_version)])
else
AC_MSG_RESULT(no)
fi
if test $ok = yes; then
# Even if we have a recent libksba, we should check that the
# API is compatible.
if test "$req_ksba_api" -gt 0 ; then
if test -z "$use_gpgrt_config"; then
tmp=`$KSBA_CONFIG --api-version 2>/dev/null || echo 0`
else
tmp=`$KSBA_CONFIG --variable=api_version 2>/dev/null || echo 0`
fi
if test "$tmp" -gt 0 ; then
AC_MSG_CHECKING([KSBA API version])
if test "$req_ksba_api" -eq "$tmp" ; then
AC_MSG_RESULT(okay)
else
ok=no
AC_MSG_RESULT([does not match. want=$req_ksba_api got=$tmp.])
fi
fi
fi
fi
if test $ok = yes; then
KSBA_CFLAGS=`$KSBA_CONFIG --cflags`
KSBA_LIBS=`$KSBA_CONFIG --libs`
ifelse([$2], , :, [$2])
if test -z "$use_gpgrt_config"; then
libksba_config_host=`$KSBA_CONFIG --host 2>/dev/null || echo none`
else
libksba_config_host=`$KSBA_CONFIG --variable=host 2>/dev/null || echo none`
fi
if test x"$libksba_config_host" != xnone ; then
if test x"$libksba_config_host" != x"$host" ; then
AC_MSG_WARN([[
***
*** The config script "$KSBA_CONFIG" was
*** built for $libksba_config_host and thus may not match the
*** used host $host.
*** You may want to use the configure option --with-libksba-prefix
*** to specify a matching config script.
***]])
fi
fi
else
KSBA_CFLAGS=""
KSBA_LIBS=""
ifelse([$3], , :, [$3])
fi
AC_SUBST(KSBA_CFLAGS)
AC_SUBST(KSBA_LIBS)
])
diff --git a/m4/ntbtls.m4 b/m4/ntbtls.m4
index fff425600..e512340e7 100644
--- a/m4/ntbtls.m4
+++ b/m4/ntbtls.m4
@@ -1,163 +1,163 @@
dnl Autoconf macros for NTBTLS
dnl Copyright (C) 2002, 2004, 2011 Free Software Foundation, Inc.
dnl
dnl This file is free software; as a special exception the author gives
dnl unlimited permission to copy and/or distribute it, with or without
dnl modifications, as long as this notice is preserved.
dnl
dnl This file is distributed in the hope that it will be useful, but
dnl WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
dnl implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
dnl
dnl Last-changed: 2022-11-01
dnl AM_PATH_NTBTLS([MINIMUM-VERSION,
dnl [ACTION-IF-FOUND [, ACTION-IF-NOT-FOUND ]]])
dnl
dnl Test for NTBTLS and define NTBTLS_CFLAGS and NTBTLS_LIBS.
-dnl MINIMUN-VERSION is a string with the version number optionalliy prefixed
+dnl MINIMUM-VERSION is a string with the version number optionally prefixed
dnl with the API version to also check the API compatibility. Example:
-dnl a MINIMUN-VERSION of 1:1.2.5 won't pass the test unless the installed
+dnl a MINIMUM-VERSION of 1:1.2.5 won't pass the test unless the installed
dnl version of libgcrypt is at least 1.2.5 *and* the API number is 1. Using
dnl this features allows to prevent build against newer versions of libgcrypt
dnl with a changed API.
dnl
AC_DEFUN([AM_PATH_NTBTLS],
[ AC_REQUIRE([AC_CANONICAL_HOST])
AC_ARG_WITH(ntbtls-prefix,
AS_HELP_STRING([--with-ntbtls-prefix=PFX],
[prefix where NTBTLS is installed (optional)]),
ntbtls_config_prefix="$withval", ntbtls_config_prefix="")
if test x"${NTBTLS_CONFIG}" = x ; then
if test x"${ntbtls_config_prefix}" != x ; then
NTBTLS_CONFIG="${ntbtls_config_prefix}/bin/ntbtls-config"
fi
fi
use_gpgrt_config=""
if test x"$GPGRT_CONFIG" != x -a "$GPGRT_CONFIG" != "no"; then
if $GPGRT_CONFIG ntbtls --exists; then
NTBTLS_CONFIG="$GPGRT_CONFIG ntbtls"
AC_MSG_NOTICE([Use gpgrt-config as ntbtls-config])
use_gpgrt_config=yes
fi
fi
if test -z "$use_gpgrt_config"; then
if test x"${NTBTLS_CONFIG}" = x ; then
case "${SYSROOT}" in
/*)
if test -x "${SYSROOT}/bin/ntbtls-config" ; then
NTBTLS_CONFIG="${SYSROOT}/bin/ntbtls-config"
fi
;;
'')
;;
*)
AC_MSG_WARN([Ignoring \$SYSROOT as it is not an absolute path.])
;;
esac
fi
AC_PATH_PROG(NTBTLS_CONFIG, ntbtls-config, no)
fi
tmp=ifelse([$1], ,1:1.0.0,$1)
if echo "$tmp" | grep ':' >/dev/null 2>/dev/null ; then
req_ntbtls_api=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\1/'`
min_ntbtls_version=`echo "$tmp" | sed 's/\(.*\):\(.*\)/\2/'`
else
req_ntbtls_api=0
min_ntbtls_version="$tmp"
fi
AC_MSG_CHECKING(for NTBTLS - version >= $min_ntbtls_version)
ok=no
if test "$NTBTLS_CONFIG" != "no" ; then
req_major=`echo $min_ntbtls_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\1/'`
req_minor=`echo $min_ntbtls_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\2/'`
req_micro=`echo $min_ntbtls_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\)/\3/'`
if test -z "$use_gpgrt_config"; then
ntbtls_config_version=`$NTBTLS_CONFIG --version`
else
ntbtls_config_version=`$NTBTLS_CONFIG --modversion`
fi
major=`echo $ntbtls_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\1/'`
minor=`echo $ntbtls_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\2/'`
micro=`echo $ntbtls_config_version | \
sed 's/\([[0-9]]*\)\.\([[0-9]]*\)\.\([[0-9]]*\).*/\3/'`
if test "$major" -gt "$req_major"; then
ok=yes
else
if test "$major" -eq "$req_major"; then
if test "$minor" -gt "$req_minor"; then
ok=yes
else
if test "$minor" -eq "$req_minor"; then
if test "$micro" -ge "$req_micro"; then
ok=yes
fi
fi
fi
fi
fi
fi
if test $ok = yes; then
AC_MSG_RESULT([yes ($ntbtls_config_version)])
else
AC_MSG_RESULT(no)
fi
if test $ok = yes; then
# If we have a recent ntbtls, we should also check that the
# API is compatible
if test "$req_ntbtls_api" -gt 0 ; then
if test -z "$use_gpgrt_config"; then
tmp=`$NTBTLS_CONFIG --api-version 2>/dev/null || echo 0`
else
tmp=`$NTBTLS_CONFIG --variable=api_version 2>/dev/null || echo 0`
fi
if test "$tmp" -gt 0 ; then
AC_MSG_CHECKING([NTBTLS API version])
if test "$req_ntbtls_api" -eq "$tmp" ; then
AC_MSG_RESULT([okay])
else
ok=no
AC_MSG_RESULT([does not match. want=$req_ntbtls_api got=$tmp])
fi
fi
fi
fi
if test $ok = yes; then
NTBTLS_CFLAGS=`$NTBTLS_CONFIG --cflags`
NTBTLS_LIBS=`$NTBTLS_CONFIG --libs`
ifelse([$2], , :, [$2])
if test -z "$use_gpgrt_config"; then
ntbtls_config_host=`$NTBTLS_CONFIG --host 2>/dev/null || echo none`
else
ntbtls_config_host=`$NTBTLS_CONFIG --variable=host 2>/dev/null || echo none`
fi
if test x"$ntbtls_config_host" != xnone ; then
if test x"$ntbtls_config_host" != x"$host" ; then
AC_MSG_WARN([[
***
*** The config script "$NTBTLS_CONFIG" was
*** built for $ntbtls_config_host and thus may not match the
*** used host $host.
*** You may want to use the configure option --with-ntbtls-prefix
*** to specify a matching config script or use \$SYSROOT.
***]])
gpg_config_script_warn="$gpg_config_script_warn ntbtls"
fi
fi
else
NTBTLS_CFLAGS=""
NTBTLS_LIBS=""
ifelse([$3], , :, [$3])
fi
AC_SUBST(NTBTLS_CFLAGS)
AC_SUBST(NTBTLS_LIBS)
])
diff --git a/regexp/jimregexp.c b/regexp/jimregexp.c
index d80eabd8e..15985d1c5 100644
--- a/regexp/jimregexp.c
+++ b/regexp/jimregexp.c
@@ -1,1927 +1,1927 @@
/*
* vi:se ts=8:
*
* regcomp and regexec -- regsub and regerror are elsewhere
*
* Copyright (c) 1986 by University of Toronto.
* Written by Henry Spencer. Not derived from licensed software.
*
* Permission is granted to anyone to use this software for any
* purpose on any computer system, and to redistribute it freely,
* subject to the following restrictions:
*
* 1. The author is not responsible for the consequences of use of
* this software, no matter how awful, even if they arise
* from defects in it.
*
* 2. The origin of this software must not be misrepresented, either
* by explicit claim or by omission.
*
* 3. Altered versions must be plainly marked as such, and must not
* be misrepresented as being the original software.
*** THIS IS AN ALTERED VERSION. It was altered by John Gilmore,
*** hoptoad!gnu, on 27 Dec 1986, to add \n as an alternative to |
*** to assist in implementing egrep.
*** THIS IS AN ALTERED VERSION. It was altered by John Gilmore,
*** hoptoad!gnu, on 27 Dec 1986, to add \< and \> for word-matching
*** as in BSD grep and ex.
*** THIS IS AN ALTERED VERSION. It was altered by John Gilmore,
*** hoptoad!gnu, on 28 Dec 1986, to optimize characters quoted with \.
*** THIS IS AN ALTERED VERSION. It was altered by James A. Woods,
*** ames!jaw, on 19 June 1987, to quash a regcomp() redundancy.
*** THIS IS AN ALTERED VERSION. It was altered by Christopher Seiwald
*** seiwald@vix.com, on 28 August 1993, for use in jam. Regmagic.h
*** was moved into regexp.h, and the include of regexp.h now uses "'s
*** to avoid conflicting with the system regexp.h. Const, bless its
*** soul, was removed so it can compile everywhere. The declaration
*** of strchr() was in conflict on AIX, so it was removed (as it is
*** happily defined in string.h).
*** THIS IS AN ALTERED VERSION. It was altered by Christopher Seiwald
*** seiwald@perforce.com, on 20 January 2000, to use function prototypes.
*** THIS IS AN ALTERED VERSION. It was altered by Christopher Seiwald
*** seiwald@perforce.com, on 05 November 2002, to const string literals.
*
* THIS IS AN ALTERED VERSION. It was altered by Steve Bennett <steveb@workware.net.au>
* on 16 October 2010, to remove static state and add better Tcl ARE compatibility.
* This includes counted repetitions, UTF-8 support, character classes,
* shorthand character classes, increased number of parentheses to 100,
* backslash escape sequences. It also removes \n as an alternative to |.
*
*** THIS IS AN ALTERED VERSION. It was altered to offer POSIX-like
*** regular expression routines of regcomp/regexec/regerror/regfree,
*** with UTF-8 support, by NIIBE Yutaka <gniibe@fsij.org> on
*** 2020-02-14.
*
* Beware that some of this code is subtly aware of the way operator
* precedence is structured in regular expressions. Serious changes in
* regular-expression syntax might require a total rethink.
*/
#if defined(JIM_REGEXP)
#include <stdio.h>
#include <ctype.h>
#include <stdlib.h>
#include <string.h>
#include "jimregexp.h"
#include "utf8.h"
#define UCHAR(c) ((unsigned char)(c))
/* An arbitrary limit, but this seems enough. Must be less than 1000. */
#define REG_MAX_PAREN 100
/*
* Structure for regexp "program". This is essentially a linear encoding
* of a nondeterministic finite-state machine (aka syntax charts or
* "railroad normal form" in parsing technology). Each node is an opcode
* plus a "next" pointer, possibly plus an operand. "Next" pointers of
* all nodes except BRANCH implement concatenation; a "next" pointer with
* a BRANCH on both ends of it is connecting two alternatives. (Here we
* have one of the subtle syntax dependencies: an individual BRANCH (as
* opposed to a collection of them) is never concatenated with anything
* because of operator precedence.) The operand of some types of node is
* a literal string; for others, it is a node leading into a sub-FSM. In
* particular, the operand of a BRANCH node is the first node of the branch.
* (NB this is *not* a tree structure: the tail of the branch connects
* to the thing following the set of BRANCHes.) The opcodes are:
*/
/* definition number opnd? meaning */
#define END 0 /* no End of program. */
#define BOL 1 /* no Match "" at beginning of line. */
#define EOL 2 /* no Match "" at end of line. */
#define ANY 3 /* no Match any one character. */
#define ANYOF 4 /* str Match any character in this string. */
#define ANYBUT 5 /* str Match any character not in this string. */
#define BRANCH 6 /* node Match this alternative, or the next... */
#define BACK 7 /* no Match "", "next" ptr points backward. */
#define EXACTLY 8 /* str Match this string. */
#define NOTHING 9 /* no Match empty string. */
#define REP 10 /* max,min Match this (simple) thing [min,max] times. */
#define REPMIN 11 /* max,min Match this (simple) thing [min,max] times, minimal match. */
#define REPX 12 /* max,min Match this (complex) thing [min,max] times. */
#define REPXMIN 13 /* max,min Match this (complex) thing [min,max] times, minimal match. */
#define BOLX 14 /* no Match "" at beginning of input. */
#define EOLX 15 /* no Match "" at end of input. */
#define WORDA 16 /* no Match "" at wordchar, where prev is nonword */
#define WORDZ 17 /* no Match "" at nonwordchar, where prev is word */
#define OPENNC 1000 /* no Non-capturing parentheses - must be OPEN-1 */
#define OPEN 1001 /* no Mark this point in input as start of #n. */
/* OPEN+1 is number 1, etc. */
/* must not be any other opts between OPEN and CLOSE */
#define CLOSENC 2000 /* no Non-capturing parentheses - must be CLOSE-1 */
#define CLOSE 2001 /* no Analogous to OPEN. */
#define CLOSE_END (CLOSE+REG_MAX_PAREN)
/*
* The first word of the regexp internal "program" is actually this magic
* number; the start node begins in the second word.
*/
#define REG_MAGIC 0xFADED00D
/*
* Opcode notes:
*
* BRANCH The set of branches constituting a single choice are hooked
* together with their "next" pointers, since precedence prevents
* anything being concatenated to any individual branch. The
* "next" pointer of the last BRANCH in a choice points to the
* thing following the whole choice. This is also where the
* final "next" pointer of each individual branch points; each
* branch starts with the operand node of a BRANCH node.
*
* BACK Normal "next" pointers all implicitly point forward; BACK
* exists to make loop structures possible.
*
* REP,REPX Repeated matches ('?', '*', '+' and {min,max}) are implemented
* as either simple repeats (REP) or complex repeats (REPX).
* These opcodes include a "min" and "max" count after the opcode.
* This is followed by a fourth "current count" word that is
* only used by REPX, as it implements a recursive match.
* REPMIN and REPXMIN are identical except they implement minimal repeats.
*
* OPEN,CLOSE ...are numbered at compile time.
*/
/*
* A node is one word of opcode followed by one word of "next" pointer.
* The "next" pointer value is a positive offset from the opcode of the node
* containing it.
* An operand, if any, simply follows the node. (Note that much of the
* code generation knows about this implicit relationship.)
*/
#define OP(preg, p) (preg->program[p])
#define NEXT(preg, p) (preg->program[p + 1])
#define OPERAND(p) ((p) + 2)
/*
* See regmagic.h for one further detail of program structure.
*/
/*
* Utility definitions.
*/
#define FAIL(R,M) { (R)->err = (M); return (M); }
#define ISMULT(c) ((c) == '*' || (c) == '+' || (c) == '?' || (c) == '{')
#define META "^$.[()|?{+*"
/*
* Flags to be passed up and down.
*/
#define HASWIDTH 1 /* Known never to match null string. */
#define SIMPLE 2 /* Simple enough to be STAR/PLUS operand. */
#define SPSTART 4 /* Starts with * or +. */
#define WORST 0 /* Worst case. */
#define MAX_REP_COUNT 1000000
/*
* Forward declarations for regcomp()'s friends.
*/
static int reg(regex_t *preg, int paren /* Parenthesized? */, int *flagp );
static int regpiece(regex_t *preg, int *flagp );
static int regbranch(regex_t *preg, int *flagp );
static int regatom(regex_t *preg, int *flagp );
static int regnode(regex_t *preg, int op );
static int regnext(regex_t *preg, int p );
static void regc(regex_t *preg, int b );
static int reginsert(regex_t *preg, int op, int size, int opnd );
static void regtail(regex_t *preg, int p, int val);
static void regoptail(regex_t *preg, int p, int val );
static int regopsize(regex_t *preg, int p );
static int reg_range_find(const int *string, int c);
static const char *str_find(const char *string, int c, int nocase);
static int prefix_cmp(const int *prog, int proglen, const char *string, int nocase);
/*#define DEBUG*/
#ifdef DEBUG
static int regnarrate = 0;
static void regdump(regex_t *preg);
static const char *regprop( int op );
#endif
/**
* Returns the length of the null-terminated integer sequence.
*/
static int str_int_len(const int *seq)
{
int n = 0;
while (*seq++) {
n++;
}
return n;
}
/*
- regcomp - compile a regular expression into internal code
*
* We can't allocate space until we know how big the compiled form will be,
* but we can't compile it (and thus know how big it is) until we've got a
* place to put the code. So we cheat: we compile it twice, once with code
* generation turned off and size counting turned on, and once "for real".
* This also means that we don't allocate space until we are sure that the
* thing really will compile successfully, and we never have to move the
* code and thus invalidate pointers into it. (Note that it has to be in
* one piece because free() must be able to free it all.)
*
* Beware that the optimization-preparation code in here knows about some
* of the structure of the compiled regexp.
*/
int regcomp(regex_t *preg, const char *exp, int cflags)
{
int scan;
int longest;
unsigned len;
int flags;
#ifdef DEBUG
fprintf(stderr, "Compiling: '%s'\n", exp);
#endif
memset(preg, 0, sizeof(*preg));
if (exp == NULL)
FAIL(preg, REG_ERR_NULL_ARGUMENT);
/* First pass: determine size, legality. */
preg->cflags = cflags;
preg->regparse = exp;
/* Allocate space. */
preg->proglen = (strlen(exp) + 1) * 5;
preg->program = malloc(preg->proglen * sizeof(int));
if (preg->program == NULL)
FAIL(preg, REG_ERR_NOMEM);
/* Note that since we store a magic value as the first item in the program,
* program offsets will never be 0
*/
regc(preg, REG_MAGIC);
if (reg(preg, 0, &flags) == 0) {
return preg->err;
}
/* Small enough for pointer-storage convention? */
if (preg->re_nsub >= REG_MAX_PAREN) /* Probably could be 65535L. */
FAIL(preg,REG_ERR_TOO_BIG);
/* Dig out information for optimizations. */
preg->regstart = 0; /* Worst-case defaults. */
preg->reganch = 0;
preg->regmust = 0;
preg->regmlen = 0;
scan = 1; /* First BRANCH. */
if (OP(preg, regnext(preg, scan)) == END) { /* Only one top-level choice. */
scan = OPERAND(scan);
/* Starting-point info. */
if (OP(preg, scan) == EXACTLY) {
preg->regstart = preg->program[OPERAND(scan)];
}
else if (OP(preg, scan) == BOL)
preg->reganch++;
/*
* If there's something expensive in the r.e., find the
* longest literal string that must appear and make it the
* regmust. Resolve ties in favor of later strings, since
* the regstart check works with the beginning of the r.e.
* and avoiding duplication strengthens checking. Not a
* strong reason, but sufficient in the absence of others.
*/
if (flags&SPSTART) {
longest = 0;
len = 0;
for (; scan != 0; scan = regnext(preg, scan)) {
if (OP(preg, scan) == EXACTLY) {
int plen = str_int_len(preg->program + OPERAND(scan));
if (plen >= len) {
longest = OPERAND(scan);
len = plen;
}
}
}
preg->regmust = longest;
preg->regmlen = len;
}
}
#ifdef DEBUG
regdump(preg);
#endif
return 0;
}
/*
- reg - regular expression, i.e. main body or parenthesized thing
*
* Caller must absorb opening parenthesis.
*
* Combining parenthesis handling with the base level of regular expression
* is a trifle forced, but the need to tie the tails of the branches to what
* follows makes it hard to avoid.
*/
static int reg(regex_t *preg, int paren /* Parenthesized? */, int *flagp )
{
int ret;
int br;
int ender;
int parno = 0;
int flags;
*flagp = HASWIDTH; /* Tentatively. */
/* Make an OPEN node, if parenthesized. */
if (paren) {
if (preg->regparse[0] == '?' && preg->regparse[1] == ':') {
/* non-capturing paren */
preg->regparse += 2;
parno = -1;
}
else {
parno = ++preg->re_nsub;
}
ret = regnode(preg, OPEN+parno);
} else
ret = 0;
/* Pick up the branches, linking them together. */
br = regbranch(preg, &flags);
if (br == 0)
return 0;
if (ret != 0)
regtail(preg, ret, br); /* OPEN -> first. */
else
ret = br;
if (!(flags&HASWIDTH))
*flagp &= ~HASWIDTH;
*flagp |= flags&SPSTART;
while (*preg->regparse == '|') {
preg->regparse++;
br = regbranch(preg, &flags);
if (br == 0)
return 0;
regtail(preg, ret, br); /* BRANCH -> BRANCH. */
if (!(flags&HASWIDTH))
*flagp &= ~HASWIDTH;
*flagp |= flags&SPSTART;
}
/* Make a closing node, and hook it on the end. */
ender = regnode(preg, (paren) ? CLOSE+parno : END);
regtail(preg, ret, ender);
/* Hook the tails of the branches to the closing node. */
for (br = ret; br != 0; br = regnext(preg, br))
regoptail(preg, br, ender);
/* Check for proper termination. */
if (paren && *preg->regparse++ != ')') {
preg->err = REG_ERR_UNMATCHED_PAREN;
return 0;
} else if (!paren && *preg->regparse != '\0') {
if (*preg->regparse == ')') {
preg->err = REG_ERR_UNMATCHED_PAREN;
return 0;
} else {
preg->err = REG_ERR_JUNK_ON_END;
return 0;
}
}
return(ret);
}
/*
- regbranch - one alternative of an | operator
*
* Implements the concatenation operator.
*/
static int regbranch(regex_t *preg, int *flagp )
{
int ret;
int chain;
int latest;
int flags;
*flagp = WORST; /* Tentatively. */
ret = regnode(preg, BRANCH);
chain = 0;
while (*preg->regparse != '\0' && *preg->regparse != ')' &&
*preg->regparse != '|') {
latest = regpiece(preg, &flags);
if (latest == 0)
return 0;
*flagp |= flags&HASWIDTH;
if (chain == 0) {/* First piece. */
*flagp |= flags&SPSTART;
}
else {
regtail(preg, chain, latest);
}
chain = latest;
}
if (chain == 0) /* Loop ran zero times. */
(void) regnode(preg, NOTHING);
return(ret);
}
/*
- regpiece - something followed by possible [*+?]
*
* Note that the branching code sequences used for ? and the general cases
* of * and + are somewhat optimized: they use the same NOTHING node as
* both the endmarker for their branch list and the body of the last branch.
* It might seem that this node could be dispensed with entirely, but the
* endmarker role is not redundant.
*/
static int regpiece(regex_t *preg, int *flagp)
{
int ret;
char op;
int next;
int flags;
int min;
int max;
ret = regatom(preg, &flags);
if (ret == 0)
return 0;
op = *preg->regparse;
if (!ISMULT(op)) {
*flagp = flags;
return(ret);
}
if (!(flags&HASWIDTH) && op != '?') {
preg->err = REG_ERR_OPERAND_COULD_BE_EMPTY;
return 0;
}
/* Handle braces (counted repetition) by expansion */
if (op == '{') {
char *end;
min = strtoul(preg->regparse + 1, &end, 10);
if (end == preg->regparse + 1) {
preg->err = REG_ERR_BAD_COUNT;
return 0;
}
if (*end == '}') {
max = min;
}
else if (*end == '\0') {
preg->err = REG_ERR_UNMATCHED_BRACES;
return 0;
}
else {
preg->regparse = end;
max = strtoul(preg->regparse + 1, &end, 10);
if (*end != '}') {
preg->err = REG_ERR_UNMATCHED_BRACES;
return 0;
}
}
if (end == preg->regparse + 1) {
max = MAX_REP_COUNT;
}
else if (max < min || max >= 100) {
preg->err = REG_ERR_BAD_COUNT;
return 0;
}
if (min >= 100) {
preg->err = REG_ERR_BAD_COUNT;
return 0;
}
preg->regparse = strchr(preg->regparse, '}');
}
else {
min = (op == '+');
max = (op == '?' ? 1 : MAX_REP_COUNT);
}
if (preg->regparse[1] == '?') {
preg->regparse++;
next = reginsert(preg, flags & SIMPLE ? REPMIN : REPXMIN, 5, ret);
}
else {
next = reginsert(preg, flags & SIMPLE ? REP: REPX, 5, ret);
}
preg->program[ret + 2] = max;
preg->program[ret + 3] = min;
preg->program[ret + 4] = 0;
*flagp = (min) ? (WORST|HASWIDTH) : (WORST|SPSTART);
if (!(flags & SIMPLE)) {
int back = regnode(preg, BACK);
regtail(preg, back, ret);
regtail(preg, next, back);
}
preg->regparse++;
if (ISMULT(*preg->regparse)) {
preg->err = REG_ERR_NESTED_COUNT;
return 0;
}
return ret;
}
/**
* Add all characters in the inclusive range between lower and upper.
*
* Handles a swapped range (upper < lower).
*/
static void reg_addrange(regex_t *preg, int lower, int upper)
{
if (lower > upper) {
reg_addrange(preg, upper, lower);
}
/* Add a range as length, start */
regc(preg, upper - lower + 1);
regc(preg, lower);
}
/**
* Add a null-terminated literal string as a set of ranges.
*/
static void reg_addrange_str(regex_t *preg, const char *str)
{
while (*str) {
reg_addrange(preg, *str, *str);
str++;
}
}
/**
* Extracts the next unicode char from utf8.
*
* If 'upper' is set, converts the char to uppercase.
*/
static int reg_utf8_tounicode_case(const char *s, int *uc, int upper)
{
int l = utf8_tounicode(s, uc);
if (upper) {
*uc = utf8_upper(*uc);
}
return l;
}
/**
* Converts a hex digit to decimal.
*
* Returns -1 for an invalid hex digit.
*/
static int hexdigitval(int c)
{
if (c >= '0' && c <= '9')
return c - '0';
if (c >= 'a' && c <= 'f')
return c - 'a' + 10;
if (c >= 'A' && c <= 'F')
return c - 'A' + 10;
return -1;
}
/**
* Parses up to 'n' hex digits at 's' and stores the result in *uc.
*
* Returns the number of hex digits parsed.
* If there are no hex digits, returns 0 and stores nothing.
*/
static int parse_hex(const char *s, int n, int *uc)
{
int val = 0;
int k;
for (k = 0; k < n; k++) {
int c = hexdigitval(*s++);
if (c == -1) {
break;
}
val = (val << 4) | c;
}
if (k) {
*uc = val;
}
return k;
}
/**
* Call for chars after a backlash to decode the escape sequence.
*
* Stores the result in *ch.
*
* Returns the number of bytes consumed.
*/
static int reg_decode_escape(const char *s, int *ch)
{
int n;
const char *s0 = s;
*ch = *s++;
switch (*ch) {
case 'b': *ch = '\b'; break;
case 'e': *ch = 27; break;
case 'f': *ch = '\f'; break;
case 'n': *ch = '\n'; break;
case 'r': *ch = '\r'; break;
case 't': *ch = '\t'; break;
case 'v': *ch = '\v'; break;
case 'u':
if (*s == '{') {
/* Expect \u{NNNN} */
n = parse_hex(s + 1, 6, ch);
if (n > 0 && s[n + 1] == '}' && *ch >= 0 && *ch <= 0x1fffff) {
s += n + 2;
}
else {
/* Invalid, so just treat as an escaped 'u' */
*ch = 'u';
}
}
else if ((n = parse_hex(s, 4, ch)) > 0) {
s += n;
}
break;
case 'U':
if ((n = parse_hex(s, 8, ch)) > 0) {
s += n;
}
break;
case 'x':
if ((n = parse_hex(s, 2, ch)) > 0) {
s += n;
}
break;
case '\0':
s--;
*ch = '\\';
break;
}
return s - s0;
}
/*
- regatom - the lowest level
*
* Optimization: gobbles an entire sequence of ordinary characters so that
* it can turn them into a single node, which is smaller to store and
* faster to run. Backslashed characters are exceptions, each becoming a
* separate node; the code is simpler that way and it's not worth fixing.
*/
static int regatom(regex_t *preg, int *flagp)
{
int ret;
int flags;
int nocase = (preg->cflags & REG_ICASE);
int ch;
int n = reg_utf8_tounicode_case(preg->regparse, &ch, nocase);
*flagp = WORST; /* Tentatively. */
preg->regparse += n;
switch (ch) {
/* FIXME: these chars only have meaning at beg/end of pat? */
case '^':
ret = regnode(preg, BOL);
break;
case '$':
ret = regnode(preg, EOL);
break;
case '.':
ret = regnode(preg, ANY);
*flagp |= HASWIDTH|SIMPLE;
break;
case '[': {
const char *pattern = preg->regparse;
if (*pattern == '^') { /* Complement of range. */
ret = regnode(preg, ANYBUT);
pattern++;
} else
ret = regnode(preg, ANYOF);
/* Special case. If the first char is ']' or '-', it is part of the set */
if (*pattern == ']' || *pattern == '-') {
reg_addrange(preg, *pattern, *pattern);
pattern++;
}
while (*pattern != ']') {
/* Is this a range? a-z */
int start;
int end;
enum {
CC_ALPHA, CC_ALNUM, CC_SPACE, CC_BLANK, CC_UPPER, CC_LOWER,
CC_DIGIT, CC_XDIGIT, CC_CNTRL, CC_GRAPH, CC_PRINT, CC_PUNCT,
CC_NUM
};
int cc;
if (!*pattern) {
preg->err = REG_ERR_UNMATCHED_BRACKET;
return 0;
}
pattern += reg_utf8_tounicode_case(pattern, &start, nocase);
if (start == '\\') {
/* First check for class shorthand escapes */
switch (*pattern) {
case 's':
pattern++;
cc = CC_SPACE;
goto cc_switch;
case 'd':
pattern++;
cc = CC_DIGIT;
goto cc_switch;
case 'w':
pattern++;
reg_addrange(preg, '_', '_');
cc = CC_ALNUM;
goto cc_switch;
}
pattern += reg_decode_escape(pattern, &start);
if (start == 0) {
preg->err = REG_ERR_NULL_CHAR;
return 0;
}
if (start == '\\' && *pattern == 0) {
preg->err = REG_ERR_INVALID_ESCAPE;
return 0;
}
}
if (pattern[0] == '-' && pattern[1] && pattern[1] != ']') {
/* skip '-' */
pattern += utf8_tounicode(pattern, &end);
pattern += reg_utf8_tounicode_case(pattern, &end, nocase);
if (end == '\\') {
pattern += reg_decode_escape(pattern, &end);
if (end == 0) {
preg->err = REG_ERR_NULL_CHAR;
return 0;
}
if (end == '\\' && *pattern == 0) {
preg->err = REG_ERR_INVALID_ESCAPE;
return 0;
}
}
reg_addrange(preg, start, end);
continue;
}
if (start == '[' && pattern[0] == ':') {
static const char *character_class[] = {
":alpha:", ":alnum:", ":space:", ":blank:", ":upper:", ":lower:",
":digit:", ":xdigit:", ":cntrl:", ":graph:", ":print:", ":punct:",
};
for (cc = 0; cc < CC_NUM; cc++) {
n = strlen(character_class[cc]);
if (strncmp(pattern, character_class[cc], n) == 0) {
if (pattern[n] != ']') {
preg->err = REG_ERR_UNMATCHED_BRACKET;
return 0;
}
/* Found a character class */
pattern += n + 1;
break;
}
}
if (cc != CC_NUM) {
cc_switch:
switch (cc) {
case CC_ALNUM:
reg_addrange(preg, '0', '9');
/* Fall through */
case CC_ALPHA:
if ((preg->cflags & REG_ICASE) == 0) {
reg_addrange(preg, 'a', 'z');
}
reg_addrange(preg, 'A', 'Z');
break;
case CC_SPACE:
reg_addrange_str(preg, " \t\r\n\f\v");
break;
case CC_BLANK:
reg_addrange_str(preg, " \t");
break;
case CC_UPPER:
reg_addrange(preg, 'A', 'Z');
break;
case CC_LOWER:
reg_addrange(preg, 'a', 'z');
break;
case CC_XDIGIT:
reg_addrange(preg, 'a', 'f');
reg_addrange(preg, 'A', 'F');
/* Fall through */
case CC_DIGIT:
reg_addrange(preg, '0', '9');
break;
case CC_CNTRL:
reg_addrange(preg, 0, 31);
reg_addrange(preg, 127, 127);
break;
case CC_PRINT:
reg_addrange(preg, ' ', '~');
break;
case CC_GRAPH:
reg_addrange(preg, '!', '~');
break;
case CC_PUNCT:
reg_addrange(preg, '!', '/');
reg_addrange(preg, ':', '@');
reg_addrange(preg, '[', '`');
reg_addrange(preg, '{', '~');
break;
}
continue;
}
}
/* Not a range, so just add the char */
reg_addrange(preg, start, start);
}
regc(preg, '\0');
if (*pattern) {
pattern++;
}
preg->regparse = pattern;
*flagp |= HASWIDTH|SIMPLE;
}
break;
case '(':
ret = reg(preg, 1, &flags);
if (ret == 0)
return 0;
*flagp |= flags&(HASWIDTH|SPSTART);
break;
case '\0':
case '|':
case ')':
preg->err = REG_ERR_INTERNAL;
return 0; /* Supposed to be caught earlier. */
case '?':
case '+':
case '*':
case '{':
preg->err = REG_ERR_COUNT_FOLLOWS_NOTHING;
return 0;
case '\\':
ch = *preg->regparse++;
switch (ch) {
case '\0':
preg->err = REG_ERR_INVALID_ESCAPE;
return 0;
case 'A':
ret = regnode(preg, BOLX);
break;
case 'Z':
ret = regnode(preg, EOLX);
break;
case '<':
case 'm':
ret = regnode(preg, WORDA);
break;
case '>':
case 'M':
ret = regnode(preg, WORDZ);
break;
case 'd':
case 'D':
ret = regnode(preg, ch == 'd' ? ANYOF : ANYBUT);
reg_addrange(preg, '0', '9');
regc(preg, '\0');
*flagp |= HASWIDTH|SIMPLE;
break;
case 'w':
case 'W':
ret = regnode(preg, ch == 'w' ? ANYOF : ANYBUT);
if ((preg->cflags & REG_ICASE) == 0) {
reg_addrange(preg, 'a', 'z');
}
reg_addrange(preg, 'A', 'Z');
reg_addrange(preg, '0', '9');
reg_addrange(preg, '_', '_');
regc(preg, '\0');
*flagp |= HASWIDTH|SIMPLE;
break;
case 's':
case 'S':
ret = regnode(preg, ch == 's' ? ANYOF : ANYBUT);
reg_addrange_str(preg," \t\r\n\f\v");
regc(preg, '\0');
*flagp |= HASWIDTH|SIMPLE;
break;
/* FIXME: Someday handle \1, \2, ... */
default:
/* Handle general quoted chars in exact-match routine */
/* Back up to include the backslash */
preg->regparse--;
goto de_fault;
}
break;
de_fault:
default: {
/*
* Encode a string of characters to be matched exactly.
*/
int added = 0;
/* Back up to pick up the first char of interest */
preg->regparse -= n;
ret = regnode(preg, EXACTLY);
/* Note that a META operator such as ? or * consumes the
* preceding char.
* Thus we must be careful to look ahead by 2 and add the
* last char as it's own EXACTLY if necessary
*/
/* Until end of string or a META char is reached */
while (*preg->regparse && strchr(META, *preg->regparse) == NULL) {
n = reg_utf8_tounicode_case(preg->regparse, &ch, (preg->cflags & REG_ICASE));
if (ch == '\\' && preg->regparse[n]) {
/* Non-trailing backslash.
* Is this a special escape, or a regular escape?
*/
if (strchr("<>mMwWdDsSAZ", preg->regparse[n])) {
/* A special escape. All done with EXACTLY */
break;
}
/* Decode it. Note that we add the length for the escape
* sequence to the length for the backlash so we can skip
* the entire sequence, or not as required.
*/
n += reg_decode_escape(preg->regparse + n, &ch);
if (ch == 0) {
preg->err = REG_ERR_NULL_CHAR;
return 0;
}
}
/* Now we have one char 'ch' of length 'n'.
* Check to see if the following char is a MULT
*/
if (ISMULT(preg->regparse[n])) {
/* Yes. But do we already have some EXACTLY chars? */
if (added) {
/* Yes, so return what we have and pick up the current char next time around */
break;
}
/* No, so add this single char and finish */
regc(preg, ch);
added++;
preg->regparse += n;
break;
}
/* No, so just add this char normally */
regc(preg, ch);
added++;
preg->regparse += n;
}
regc(preg, '\0');
*flagp |= HASWIDTH;
if (added == 1)
*flagp |= SIMPLE;
break;
}
break;
}
return(ret);
}
static void reg_grow(regex_t *preg, int n)
{
if (preg->p + n >= preg->proglen) {
preg->proglen = (preg->p + n) * 2;
preg->program = realloc(preg->program, preg->proglen * sizeof(int));
}
}
/*
- regnode - emit a node
*/
/* Location. */
static int regnode(regex_t *preg, int op)
{
reg_grow(preg, 2);
/* The OP followed by a next pointer */
preg->program[preg->p++] = op;
preg->program[preg->p++] = 0;
/* Return the start of the node */
return preg->p - 2;
}
/*
- regc - emit (if appropriate) a byte of code
*/
static void regc(regex_t *preg, int b )
{
reg_grow(preg, 1);
preg->program[preg->p++] = b;
}
/*
- reginsert - insert an operator in front of already-emitted operand
*
* Means relocating the operand.
* Returns the new location of the original operand.
*/
static int reginsert(regex_t *preg, int op, int size, int opnd )
{
reg_grow(preg, size);
/* Move everything from opnd up */
memmove(preg->program + opnd + size, preg->program + opnd, sizeof(int) * (preg->p - opnd));
/* Zero out the new space */
memset(preg->program + opnd, 0, sizeof(int) * size);
preg->program[opnd] = op;
preg->p += size;
return opnd + size;
}
/*
- regtail - set the next-pointer at the end of a node chain
*/
static void regtail(regex_t *preg, int p, int val)
{
int scan;
int temp;
int offset;
/* Find last node. */
scan = p;
for (;;) {
temp = regnext(preg, scan);
if (temp == 0)
break;
scan = temp;
}
if (OP(preg, scan) == BACK)
offset = scan - val;
else
offset = val - scan;
preg->program[scan + 1] = offset;
}
/*
- regoptail - regtail on operand of first argument; nop if operandless
*/
static void regoptail(regex_t *preg, int p, int val )
{
/* "Operandless" and "op != BRANCH" are synonymous in practice. */
if (p != 0 && OP(preg, p) == BRANCH) {
regtail(preg, OPERAND(p), val);
}
}
/*
* regexec and friends
*/
/*
* Forwards.
*/
static int regtry(regex_t *preg, const char *string );
static int regmatch(regex_t *preg, int prog);
static int regrepeat(regex_t *preg, int p, int max);
/*
- regexec - match a regexp against a string
*/
int regexec(regex_t *preg, const char *string, size_t nmatch, regmatch_t pmatch[], int eflags)
{
const char *s;
int scan;
/* Be paranoid... */
if (preg == NULL || preg->program == NULL || string == NULL) {
return REG_ERR_NULL_ARGUMENT;
}
/* Check validity of program. */
if (*preg->program != REG_MAGIC) {
return REG_ERR_CORRUPTED;
}
#ifdef DEBUG
fprintf(stderr, "regexec: %s\n", string);
regdump(preg);
#endif
preg->eflags = eflags;
preg->pmatch = pmatch;
preg->nmatch = nmatch;
preg->start = string; /* All offsets are computed from here */
/* Must clear out the embedded repeat counts of REPX and REPXMIN opcodes */
for (scan = OPERAND(1); scan != 0; scan += regopsize(preg, scan)) {
int op = OP(preg, scan);
if (op == END)
break;
if (op == REPX || op == REPXMIN)
preg->program[scan + 4] = 0;
}
/* If there is a "must appear" string, look for it. */
if (preg->regmust != 0) {
s = string;
while ((s = str_find(s, preg->program[preg->regmust], preg->cflags & REG_ICASE)) != NULL) {
if (prefix_cmp(preg->program + preg->regmust, preg->regmlen, s, preg->cflags & REG_ICASE) >= 0) {
break;
}
s++;
}
if (s == NULL) /* Not present. */
return REG_NOMATCH;
}
/* Mark beginning of line for ^ . */
preg->regbol = string;
/* Simplest case: anchored match need be tried only once (maybe per line). */
if (preg->reganch) {
if (eflags & REG_NOTBOL) {
/* This is an anchored search, but not an BOL, so possibly skip to the next line */
goto nextline;
}
while (1) {
if (regtry(preg, string)) {
return REG_NOERROR;
}
if (*string) {
nextline:
if (preg->cflags & REG_NEWLINE) {
/* Try the next anchor? */
string = strchr(string, '\n');
if (string) {
preg->regbol = ++string;
continue;
}
}
}
return REG_NOMATCH;
}
}
/* Messy cases: unanchored match. */
s = string;
if (preg->regstart != '\0') {
/* We know what char it must start with. */
while ((s = str_find(s, preg->regstart, preg->cflags & REG_ICASE)) != NULL) {
if (regtry(preg, s))
return REG_NOERROR;
s++;
}
}
else
/* We don't -- general case. */
while (1) {
if (regtry(preg, s))
return REG_NOERROR;
if (*s == '\0') {
break;
}
else {
int c;
s += utf8_tounicode(s, &c);
}
}
/* Failure. */
return REG_NOMATCH;
}
/*
- regtry - try match at specific point
*/
/* 0 failure, 1 success */
static int regtry( regex_t *preg, const char *string )
{
int i;
preg->reginput = string;
for (i = 0; i < preg->nmatch; i++) {
if (preg->pmatch) {
preg->pmatch[i].rm_so = -1;
preg->pmatch[i].rm_eo = -1;
}
}
if (regmatch(preg, 1)) {
if (preg->pmatch) {
preg->pmatch[0].rm_so = string - preg->start;
preg->pmatch[0].rm_eo = preg->reginput - preg->start;
}
return(1);
} else
return(0);
}
/**
* Returns bytes matched if 'pattern' is a prefix of 'string'.
*
* If 'nocase' is non-zero, does a case-insensitive match.
*
* Returns -1 on not found.
*/
static int prefix_cmp(const int *prog, int proglen, const char *string, int nocase)
{
const char *s = string;
while (proglen && *s) {
int ch;
int n = reg_utf8_tounicode_case(s, &ch, nocase);
if (ch != *prog) {
return -1;
}
prog++;
s += n;
proglen--;
}
if (proglen == 0) {
return s - string;
}
return -1;
}
/**
- * Searchs for 'c' in the range 'range'.
+ * Searches for 'c' in the range 'range'.
*
* Returns 1 if found, or 0 if not.
*/
static int reg_range_find(const int *range, int c)
{
while (*range) {
/*printf("Checking %d in range [%d,%d]\n", c, range[1], (range[0] + range[1] - 1));*/
if (c >= range[1] && c <= (range[0] + range[1] - 1)) {
return 1;
}
range += 2;
}
return 0;
}
/**
* Search for the character 'c' in the utf-8 string 'string'.
*
* If 'nocase' is set, the 'string' is assumed to be uppercase
* and 'c' is converted to uppercase before matching.
*
* Returns the byte position in the string where the 'c' was found, or
* NULL if not found.
*/
static const char *str_find(const char *string, int c, int nocase)
{
if (nocase) {
/* The "string" should already be converted to uppercase */
c = utf8_upper(c);
}
while (*string) {
int ch;
int n = reg_utf8_tounicode_case(string, &ch, nocase);
if (c == ch) {
return string;
}
string += n;
}
return NULL;
}
/**
* Returns true if 'ch' is an end-of-line char.
*
* In REG_NEWLINE mode, \n is considered EOL in
* addition to \0
*/
static int reg_iseol(regex_t *preg, int ch)
{
if (preg->cflags & REG_NEWLINE) {
return ch == '\0' || ch == '\n';
}
else {
return ch == '\0';
}
}
static int regmatchsimplerepeat(regex_t *preg, int scan, int matchmin)
{
int nextch = '\0';
const char *save;
int no;
int c;
int max = preg->program[scan + 2];
int min = preg->program[scan + 3];
int next = regnext(preg, scan);
/*
* Lookahead to avoid useless match attempts
* when we know what character comes next.
*/
if (OP(preg, next) == EXACTLY) {
nextch = preg->program[OPERAND(next)];
}
save = preg->reginput;
no = regrepeat(preg, scan + 5, max);
if (no < min) {
return 0;
}
if (matchmin) {
/* from min up to no */
max = no;
no = min;
}
/* else from no down to min */
while (1) {
if (matchmin) {
if (no > max) {
break;
}
}
else {
if (no < min) {
break;
}
}
preg->reginput = save + utf8_index(save, no);
reg_utf8_tounicode_case(preg->reginput, &c, (preg->cflags & REG_ICASE));
/* If it could work, try it. */
if (reg_iseol(preg, nextch) || c == nextch) {
if (regmatch(preg, next)) {
return(1);
}
}
if (matchmin) {
/* Couldn't or didn't, add one more */
no++;
}
else {
/* Couldn't or didn't -- back up. */
no--;
}
}
return(0);
}
static int regmatchrepeat(regex_t *preg, int scan, int matchmin)
{
int *scanpt = preg->program + scan;
int max = scanpt[2];
int min = scanpt[3];
/* Have we reached min? */
if (scanpt[4] < min) {
/* No, so get another one */
scanpt[4]++;
if (regmatch(preg, scan + 5)) {
return 1;
}
scanpt[4]--;
return 0;
}
if (scanpt[4] > max) {
return 0;
}
if (matchmin) {
/* minimal, so try other branch first */
if (regmatch(preg, regnext(preg, scan))) {
return 1;
}
/* No, so try one more */
scanpt[4]++;
if (regmatch(preg, scan + 5)) {
return 1;
}
scanpt[4]--;
return 0;
}
/* maximal, so try this branch again */
if (scanpt[4] < max) {
scanpt[4]++;
if (regmatch(preg, scan + 5)) {
return 1;
}
scanpt[4]--;
}
/* At this point we are at max with no match. Try the other branch */
return regmatch(preg, regnext(preg, scan));
}
/*
- regmatch - main matching routine
*
* Conceptually the strategy is simple: check to see whether the current
* node matches, call self recursively to see whether the rest matches,
* and then act accordingly. In practice we make some effort to avoid
* recursion, in particular by going through "ordinary" nodes (that don't
* need to know whether the rest of the match failed) by a loop instead of
* by recursion.
*/
/* 0 failure, 1 success */
static int regmatch(regex_t *preg, int prog)
{
int scan; /* Current node. */
int next; /* Next node. */
const char *save;
scan = prog;
#ifdef DEBUG
if (scan != 0 && regnarrate)
fprintf(stderr, "%s(\n", regprop(scan));
#endif
while (scan != 0) {
int n;
int c;
#ifdef DEBUG
if (regnarrate) {
fprintf(stderr, "%3d: %s...\n", scan, regprop(OP(preg, scan))); /* Where, what. */
}
#endif
next = regnext(preg, scan);
n = reg_utf8_tounicode_case(preg->reginput, &c, (preg->cflags & REG_ICASE));
switch (OP(preg, scan)) {
case BOLX:
if ((preg->eflags & REG_NOTBOL)) {
return(0);
}
/* Fall through */
case BOL:
if (preg->reginput != preg->regbol) {
return(0);
}
break;
case EOLX:
if (c != 0) {
/* For EOLX, only match real end of line, not newline */
return 0;
}
break;
case EOL:
if (!reg_iseol(preg, c)) {
return(0);
}
break;
case WORDA:
/* Must be looking at a letter, digit, or _ */
if ((!isalnum(UCHAR(c))) && c != '_')
return(0);
/* Prev must be BOL or nonword */
if (preg->reginput > preg->regbol &&
(isalnum(UCHAR(preg->reginput[-1])) || preg->reginput[-1] == '_'))
return(0);
break;
case WORDZ:
/* Can't match at BOL */
if (preg->reginput > preg->regbol) {
/* Current must be EOL or nonword */
if (reg_iseol(preg, c) || !(isalnum(UCHAR(c)) || c == '_')) {
c = preg->reginput[-1];
/* Previous must be word */
if (isalnum(UCHAR(c)) || c == '_') {
break;
}
}
}
/* No */
return(0);
case ANY:
if (reg_iseol(preg, c))
return 0;
preg->reginput += n;
break;
case EXACTLY: {
int opnd;
int len;
int slen;
opnd = OPERAND(scan);
len = str_int_len(preg->program + opnd);
slen = prefix_cmp(preg->program + opnd, len, preg->reginput, preg->cflags & REG_ICASE);
if (slen < 0) {
return(0);
}
preg->reginput += slen;
}
break;
case ANYOF:
if (reg_iseol(preg, c) || reg_range_find(preg->program + OPERAND(scan), c) == 0) {
return(0);
}
preg->reginput += n;
break;
case ANYBUT:
if (reg_iseol(preg, c) || reg_range_find(preg->program + OPERAND(scan), c) != 0) {
return(0);
}
preg->reginput += n;
break;
case NOTHING:
break;
case BACK:
break;
case BRANCH:
if (OP(preg, next) != BRANCH) /* No choice. */
next = OPERAND(scan); /* Avoid recursion. */
else {
do {
save = preg->reginput;
if (regmatch(preg, OPERAND(scan))) {
return(1);
}
preg->reginput = save;
scan = regnext(preg, scan);
} while (scan != 0 && OP(preg, scan) == BRANCH);
return(0);
/* NOTREACHED */
}
break;
case REP:
case REPMIN:
return regmatchsimplerepeat(preg, scan, OP(preg, scan) == REPMIN);
case REPX:
case REPXMIN:
return regmatchrepeat(preg, scan, OP(preg, scan) == REPXMIN);
case END:
return 1; /* Success! */
case OPENNC:
case CLOSENC:
return regmatch(preg, next);
default:
if (OP(preg, scan) >= OPEN+1 && OP(preg, scan) < CLOSE_END) {
save = preg->reginput;
if (regmatch(preg, next)) {
if (OP(preg, scan) < CLOSE) {
int no = OP(preg, scan) - OPEN;
if (no < preg->nmatch && preg->pmatch && preg->pmatch[no].rm_so == -1) {
preg->pmatch[no].rm_so = save - preg->start;
}
}
else {
int no = OP(preg, scan) - CLOSE;
if (no < preg->nmatch && preg->pmatch && preg->pmatch[no].rm_eo == -1) {
preg->pmatch[no].rm_eo = save - preg->start;
}
}
return(1);
}
/* Restore input position after failure */
preg->reginput = save;
return(0);
}
return REG_ERR_INTERNAL;
}
scan = next;
}
/*
* We get here only if there's trouble -- normally "case END" is
* the terminating point.
*/
return REG_ERR_INTERNAL;
}
/*
- regrepeat - repeatedly match something simple, report how many
*/
static int regrepeat(regex_t *preg, int p, int max)
{
int count = 0;
const char *scan;
int opnd;
int ch;
int n;
scan = preg->reginput;
opnd = OPERAND(p);
switch (OP(preg, p)) {
case ANY:
while (!reg_iseol(preg, *scan) && count < max) {
count++;
scan += utf8_charlen(*scan);
}
break;
case EXACTLY:
while (count < max) {
n = reg_utf8_tounicode_case(scan, &ch, preg->cflags & REG_ICASE);
if (preg->program[opnd] != ch) {
break;
}
count++;
scan += n;
}
break;
case ANYOF:
while (count < max) {
n = reg_utf8_tounicode_case(scan, &ch, preg->cflags & REG_ICASE);
if (reg_iseol(preg, ch) || reg_range_find(preg->program + opnd, ch) == 0) {
break;
}
count++;
scan += n;
}
break;
case ANYBUT:
while (count < max) {
n = reg_utf8_tounicode_case(scan, &ch, preg->cflags & REG_ICASE);
if (reg_iseol(preg, ch) || reg_range_find(preg->program + opnd, ch) != 0) {
break;
}
count++;
scan += n;
}
break;
default: /* Oh dear. Called inappropriately. */
preg->err = REG_ERR_INTERNAL;
count = 0; /* Best compromise. */
break;
}
preg->reginput = scan;
return(count);
}
/*
- regnext - dig the "next" pointer out of a node
*/
static int regnext(regex_t *preg, int p )
{
int offset;
offset = NEXT(preg, p);
if (offset == 0)
return 0;
if (OP(preg, p) == BACK)
return(p-offset);
else
return(p+offset);
}
/*
- regopsize - returns the size of opcode + operands at 'p' in words
*/
static int regopsize(regex_t *preg, int p )
{
/* Almost all opcodes are 2 words, but some are more */
switch (OP(preg, p)) {
case REP:
case REPMIN:
case REPX:
case REPXMIN:
return 5;
case ANYOF:
case ANYBUT:
case EXACTLY: {
int s = p + 2;
while (preg->program[s++]) {
}
return s - p;
}
}
return 2;
}
#if defined(DEBUG) && !defined(JIM_BOOTSTRAP)
/*
- regdump - dump a regexp onto stdout in vaguely comprehensible form
*/
static void regdump(regex_t *preg)
{
int s;
int op = EXACTLY; /* Arbitrary non-END op. */
int next;
char buf[MAX_UTF8_LEN + 1];
int i;
for (i = 1; i < preg->p; i++) {
printf("%02x ", (unsigned char)preg->program[i]);
if (i % 16 == 0) {
printf("\n");
}
}
printf("\n");
s = 1;
while (op != END && s < preg->p) { /* While that wasn't END last time... */
op = OP(preg, s);
printf("%3d: %s", s, regprop(op)); /* Where, what. */
next = regnext(preg, s);
if (next == 0) /* Next ptr. */
printf("(0)");
else
printf("(%d)", next);
s += 2;
if (op == REP || op == REPMIN || op == REPX || op == REPXMIN) {
int max = preg->program[s];
int min = preg->program[s + 1];
if (max == 65535) {
printf("{%d,*}", min);
}
else {
printf("{%d,%d}", min, max);
}
printf(" %d", preg->program[s + 2]);
s += 3;
}
else if (op == ANYOF || op == ANYBUT) {
/* set of ranges */
while (preg->program[s]) {
int len = preg->program[s++];
int first = preg->program[s++];
buf[utf8_getchars(buf, first)] = 0;
printf("%s", buf);
if (len > 1) {
buf[utf8_getchars(buf, first + len - 1)] = 0;
printf("-%s", buf);
}
}
s++;
}
else if (op == EXACTLY) {
/* Literal string, where present. */
while (preg->program[s]) {
buf[utf8_getchars(buf, preg->program[s])] = 0;
printf("%s", buf);
s++;
}
s++;
}
putchar('\n');
}
if (op == END) {
/* Header fields of interest. */
if (preg->regstart) {
buf[utf8_getchars(buf, preg->regstart)] = 0;
printf("start '%s' ", buf);
}
if (preg->reganch)
printf("anchored ");
if (preg->regmust != 0) {
int i;
printf("must have:");
for (i = 0; i < preg->regmlen; i++) {
putchar(preg->program[preg->regmust + i]);
}
putchar('\n');
}
}
printf("\n");
}
/*
- regprop - printable representation of opcode
*/
static const char *regprop( int op )
{
static char buf[50];
switch (op) {
case BOL:
return "BOL";
case EOL:
return "EOL";
case BOLX:
return "BOLX";
case EOLX:
return "EOLX";
case ANY:
return "ANY";
case ANYOF:
return "ANYOF";
case ANYBUT:
return "ANYBUT";
case BRANCH:
return "BRANCH";
case EXACTLY:
return "EXACTLY";
case NOTHING:
return "NOTHING";
case BACK:
return "BACK";
case END:
return "END";
case REP:
return "REP";
case REPMIN:
return "REPMIN";
case REPX:
return "REPX";
case REPXMIN:
return "REPXMIN";
case WORDA:
return "WORDA";
case WORDZ:
return "WORDZ";
case OPENNC:
return "OPEN";
case CLOSENC:
return "CLOSE";
default:
if (op >= OPEN && op < CLOSE) {
snprintf(buf, sizeof(buf), "OPEN%d", op-OPEN);
}
else if (op >= CLOSE && op < CLOSE_END) {
snprintf(buf, sizeof(buf), "CLOSE%d", op-CLOSE);
}
else {
snprintf(buf, sizeof(buf), "?%d?\n", op);
}
return(buf);
}
}
#endif /* JIM_BOOTSTRAP */
size_t regerror(int errcode, const regex_t *preg, char *errbuf, size_t errbuf_size)
{
static const char *error_strings[] = {
"success",
"no match",
"bad pattern",
"null argument",
"unknown error",
"too big",
"out of memory",
"too many ()",
"parentheses () not balanced",
"braces {} not balanced",
"invalid repetition count(s)",
"extra characters",
"*+ of empty atom",
"nested count",
"internal error",
"count follows nothing",
"invalid escape \\ sequence",
"corrupted program",
"contains null char",
"brackets [] not balanced",
};
const char *err;
(void)preg;
if (errcode < 0 || errcode >= REG_ERR_NUM) {
err = "Bad error code";
}
else {
err = error_strings[errcode];
}
return snprintf(errbuf, errbuf_size, "%s", err);
}
void regfree(regex_t *preg)
{
free(preg->program);
}
#endif
diff --git a/scd/apdu.c b/scd/apdu.c
index 2e38a273a..35b845b40 100644
--- a/scd/apdu.c
+++ b/scd/apdu.c
@@ -1,3522 +1,3522 @@
/* apdu.c - ISO 7816 APDU functions and low level I/O
* Copyright (C) 2003, 2004, 2008, 2009, 2010,
* 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* NOTE: This module is also used by other software, thus the use of
the macro USE_NPTH is mandatory. For GnuPG this macro is
guaranteed to be defined true. */
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#ifdef USE_NPTH
# include <unistd.h>
# include <fcntl.h>
# include <npth.h>
#endif
/* If requested include the definitions for the remote APDU protocol
code. */
#ifdef USE_G10CODE_RAPDU
#include "rapdu.h"
#endif /*USE_G10CODE_RAPDU*/
#if defined(GNUPG_MAJOR_VERSION)
# include "scdaemon.h"
# include "../common/exechelp.h"
#endif /*GNUPG_MAJOR_VERSION*/
#include "../common/host2net.h"
#include "iso7816.h"
#include "apdu.h"
#define CCID_DRIVER_INCLUDE_USB_IDS 1
#include "ccid-driver.h"
struct dev_list {
void *table;
const char *portstr;
int idx;
int idx_max;
};
#define MAX_READER 16 /* Number of readers we support concurrently. */
/* See also MAX_DEVICE in ccid-driver.c. */
#if defined(_WIN32) || defined(__CYGWIN__)
#define DLSTDCALL __stdcall
#else
#define DLSTDCALL
#endif
#if defined(__APPLE__) || defined(_WIN32) || defined(__CYGWIN__)
typedef unsigned int pcsc_dword_t;
#else
typedef unsigned long pcsc_dword_t;
#endif
#ifdef HAVE_W32_SYSTEM
#define HANDLE uintptr_t
#else
#define HANDLE long
#endif
/* PC/SC context to access readers. Shared among all readers. */
static struct pcsc_global_data {
HANDLE context;
int count;
const char *rdrname[MAX_READER];
} pcsc;
/* A structure to collect information pertaining to one reader
slot. */
struct reader_table_s {
int used; /* True if slot is used. */
unsigned short port; /* Port number: 0 = unused, 1 - dev/tty */
/* Function pointers initialized to the various backends. */
int (*connect_card)(int);
int (*disconnect_card)(int);
int (*close_reader)(int);
int (*reset_reader)(int);
int (*get_status_reader)(int, unsigned int *, int);
int (*send_apdu_reader)(int,unsigned char *,size_t,
unsigned char *, size_t *, pininfo_t *);
int (*check_pinpad)(int, int, pininfo_t *);
void (*dump_status_reader)(int);
int (*set_progress_cb)(int, gcry_handler_progress_t, void*);
int (*set_prompt_cb)(int, void (*) (void *, int), void*);
int (*pinpad_verify)(int, int, int, int, int, pininfo_t *);
int (*pinpad_modify)(int, int, int, int, int, pininfo_t *);
struct {
ccid_driver_t handle;
} ccid;
struct {
HANDLE card;
pcsc_dword_t protocol;
pcsc_dword_t verify_ioctl;
pcsc_dword_t modify_ioctl;
int pinmin;
int pinmax;
pcsc_dword_t current_state;
} pcsc;
#ifdef USE_G10CODE_RAPDU
struct {
rapdu_t handle;
} rapdu;
#endif /*USE_G10CODE_RAPDU*/
char *rdrname; /* Name of the connected reader or NULL if unknown. */
unsigned int is_t0:1; /* True if we know that we are running T=0. */
unsigned int pinpad_varlen_supported:1; /* True if we know that the reader
supports variable length pinpad
input. */
unsigned int require_get_status:1;
unsigned char atr[33];
size_t atrlen; /* A zero length indicates that the ATR has
not yet been read; i.e. the card is not
ready for use. */
#ifdef USE_NPTH
npth_mutex_t lock;
#endif
};
typedef struct reader_table_s *reader_table_t;
/* A global table to keep track of active readers. */
static struct reader_table_s reader_table[MAX_READER];
#ifdef USE_NPTH
static npth_mutex_t reader_table_lock;
#endif
/* PC/SC constants and function pointer. */
#define PCSC_SCOPE_USER 0
#define PCSC_SCOPE_TERMINAL 1
#define PCSC_SCOPE_SYSTEM 2
#define PCSC_SCOPE_GLOBAL 3
#define PCSC_PROTOCOL_T0 1
#define PCSC_PROTOCOL_T1 2
#ifdef HAVE_W32_SYSTEM
# define PCSC_PROTOCOL_RAW 0x00010000 /* The active protocol. */
#else
# define PCSC_PROTOCOL_RAW 4
#endif
#define PCSC_SHARE_EXCLUSIVE 1
#define PCSC_SHARE_SHARED 2
#define PCSC_SHARE_DIRECT 3
#define PCSC_LEAVE_CARD 0
#define PCSC_RESET_CARD 1
#define PCSC_UNPOWER_CARD 2
#define PCSC_EJECT_CARD 3
#ifdef HAVE_W32_SYSTEM
# define PCSC_UNKNOWN 0x0000 /* The driver is not aware of the status. */
# define PCSC_ABSENT 0x0001 /* Card is absent. */
# define PCSC_PRESENT 0x0002 /* Card is present. */
# define PCSC_SWALLOWED 0x0003 /* Card is present and electrical connected. */
# define PCSC_POWERED 0x0004 /* Card is powered. */
# define PCSC_NEGOTIABLE 0x0005 /* Card is awaiting PTS. */
# define PCSC_SPECIFIC 0x0006 /* Card is ready for use. */
#else
# define PCSC_UNKNOWN 0x0001
# define PCSC_ABSENT 0x0002 /* Card is absent. */
# define PCSC_PRESENT 0x0004 /* Card is present. */
# define PCSC_SWALLOWED 0x0008 /* Card is present and electrical connected. */
# define PCSC_POWERED 0x0010 /* Card is powered. */
# define PCSC_NEGOTIABLE 0x0020 /* Card is awaiting PTS. */
# define PCSC_SPECIFIC 0x0040 /* Card is ready for use. */
#endif
#define PCSC_STATE_UNAWARE 0x0000 /* Want status. */
#define PCSC_STATE_IGNORE 0x0001 /* Ignore this reader. */
#define PCSC_STATE_CHANGED 0x0002 /* State has changed. */
#define PCSC_STATE_UNKNOWN 0x0004 /* Reader unknown. */
#define PCSC_STATE_UNAVAILABLE 0x0008 /* Status unavailable. */
#define PCSC_STATE_EMPTY 0x0010 /* Card removed. */
#define PCSC_STATE_PRESENT 0x0020 /* Card inserted. */
#define PCSC_STATE_ATRMATCH 0x0040 /* ATR matches card. */
#define PCSC_STATE_EXCLUSIVE 0x0080 /* Exclusive Mode. */
#define PCSC_STATE_INUSE 0x0100 /* Shared mode. */
#define PCSC_STATE_MUTE 0x0200 /* Unresponsive card. */
#ifdef HAVE_W32_SYSTEM
# define PCSC_STATE_UNPOWERED 0x0400 /* Card not powerred up. */
#endif
/* Some PC/SC error codes. */
#define PCSC_E_CANCELLED 0x80100002
#define PCSC_E_CANT_DISPOSE 0x8010000E
#define PCSC_E_INSUFFICIENT_BUFFER 0x80100008
#define PCSC_E_INVALID_ATR 0x80100015
#define PCSC_E_INVALID_HANDLE 0x80100003
#define PCSC_E_INVALID_PARAMETER 0x80100004
#define PCSC_E_INVALID_TARGET 0x80100005
#define PCSC_E_INVALID_VALUE 0x80100011
#define PCSC_E_NO_MEMORY 0x80100006
#define PCSC_E_UNKNOWN_READER 0x80100009
#define PCSC_E_TIMEOUT 0x8010000A
#define PCSC_E_SHARING_VIOLATION 0x8010000B
#define PCSC_E_NO_SMARTCARD 0x8010000C
#define PCSC_E_UNKNOWN_CARD 0x8010000D
#define PCSC_E_PROTO_MISMATCH 0x8010000F
#define PCSC_E_NOT_READY 0x80100010
#define PCSC_E_SYSTEM_CANCELLED 0x80100012
#define PCSC_E_NOT_TRANSACTED 0x80100016
#define PCSC_E_READER_UNAVAILABLE 0x80100017
#define PCSC_E_NO_SERVICE 0x8010001D
#define PCSC_E_SERVICE_STOPPED 0x8010001E
#define PCSC_E_NO_READERS_AVAILABLE 0x8010002E
#define PCSC_W_RESET_CARD 0x80100068
#define PCSC_W_REMOVED_CARD 0x80100069
/* Fix pcsc-lite ABI incompatibility. */
#ifndef SCARD_CTL_CODE
#ifdef _WIN32
#include <winioctl.h>
#define SCARD_CTL_CODE(code) CTL_CODE(FILE_DEVICE_SMARTCARD, (code), \
METHOD_BUFFERED, FILE_ANY_ACCESS)
#else
#define SCARD_CTL_CODE(code) (0x42000000 + (code))
#endif
#endif
#define CM_IOCTL_GET_FEATURE_REQUEST SCARD_CTL_CODE(3400)
#define CM_IOCTL_VENDOR_IFD_EXCHANGE SCARD_CTL_CODE(1)
#define FEATURE_VERIFY_PIN_DIRECT 0x06
#define FEATURE_MODIFY_PIN_DIRECT 0x07
#define FEATURE_GET_TLV_PROPERTIES 0x12
#define PCSCv2_PART10_PROPERTY_bEntryValidationCondition 2
#define PCSCv2_PART10_PROPERTY_bTimeOut2 3
#define PCSCv2_PART10_PROPERTY_bMinPINSize 6
#define PCSCv2_PART10_PROPERTY_bMaxPINSize 7
#define PCSCv2_PART10_PROPERTY_wIdVendor 11
#define PCSCv2_PART10_PROPERTY_wIdProduct 12
/* The PC/SC error is defined as a long as per specs. Due to left
shifts bit 31 will get sign extended. We use this mask to fix
it. */
#define PCSC_ERR_MASK(a) ((a) & 0xffffffff)
struct pcsc_io_request_s
{
#if defined(_WIN32) || defined(__CYGWIN__)
pcsc_dword_t protocol;
pcsc_dword_t pci_len;
#else
unsigned long protocol;
unsigned long pci_len;
#endif
};
typedef struct pcsc_io_request_s *pcsc_io_request_t;
#ifdef __APPLE__
#pragma pack(1)
#endif
struct pcsc_readerstate_s
{
const char *reader;
void *user_data;
pcsc_dword_t current_state;
pcsc_dword_t event_state;
pcsc_dword_t atrlen;
unsigned char atr[33];
};
#ifdef __APPLE__
#pragma pack()
#endif
typedef struct pcsc_readerstate_s *pcsc_readerstate_t;
long (* DLSTDCALL pcsc_establish_context) (pcsc_dword_t scope,
const void *reserved1,
const void *reserved2,
HANDLE *r_context);
long (* DLSTDCALL pcsc_release_context) (HANDLE context);
long (* DLSTDCALL pcsc_cancel) (HANDLE context);
long (* DLSTDCALL pcsc_list_readers) (HANDLE context,
const char *groups,
char *readers, pcsc_dword_t*readerslen);
long (* DLSTDCALL pcsc_get_status_change) (HANDLE context,
pcsc_dword_t timeout,
pcsc_readerstate_t readerstates,
pcsc_dword_t nreaderstates);
long (* DLSTDCALL pcsc_connect) (HANDLE context,
const char *reader,
pcsc_dword_t share_mode,
pcsc_dword_t preferred_protocols,
HANDLE *r_card,
pcsc_dword_t *r_active_protocol);
long (* DLSTDCALL pcsc_reconnect) (HANDLE card,
pcsc_dword_t share_mode,
pcsc_dword_t preferred_protocols,
pcsc_dword_t initialization,
pcsc_dword_t *r_active_protocol);
long (* DLSTDCALL pcsc_disconnect) (HANDLE card,
pcsc_dword_t disposition);
long (* DLSTDCALL pcsc_status) (HANDLE card,
char *reader, pcsc_dword_t *readerlen,
pcsc_dword_t *r_state,
pcsc_dword_t *r_protocol,
unsigned char *atr, pcsc_dword_t *atrlen);
long (* DLSTDCALL pcsc_begin_transaction) (HANDLE card);
long (* DLSTDCALL pcsc_end_transaction) (HANDLE card,
pcsc_dword_t disposition);
long (* DLSTDCALL pcsc_transmit) (HANDLE card,
const pcsc_io_request_t send_pci,
const unsigned char *send_buffer,
pcsc_dword_t send_len,
pcsc_io_request_t recv_pci,
unsigned char *recv_buffer,
pcsc_dword_t *recv_len);
long (* DLSTDCALL pcsc_set_timeout) (HANDLE context,
pcsc_dword_t timeout);
long (* DLSTDCALL pcsc_control) (HANDLE card,
pcsc_dword_t control_code,
const void *send_buffer,
pcsc_dword_t send_len,
void *recv_buffer,
pcsc_dword_t recv_len,
pcsc_dword_t *bytes_returned);
/* Prototypes. */
static int pcsc_vendor_specific_init (int slot);
static int pcsc_get_status (int slot, unsigned int *status, int on_wire);
static int reset_pcsc_reader (int slot);
static int apdu_get_status_internal (int slot, int hang, unsigned int *status,
int on_wire);
static int check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo);
static int pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo);
static int pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo);
/*
* Helper
*/
/* Return true if (BUFFER,LENGTH) consists of only binary zeroes. */
static int
all_zero_p (const void *buffer, size_t length)
{
const unsigned char *p;
for (p=buffer; length; p++, length--)
if (*p)
return 0;
return 1;
}
static int
lock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_lock (&reader_table[slot].lock);
if (err)
{
log_error ("failed to acquire apdu lock: %s\n", strerror (err));
return SW_HOST_LOCKING_FAILED;
}
#endif /*USE_NPTH*/
return 0;
}
static int
trylock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_trylock (&reader_table[slot].lock);
if (err == EBUSY)
return SW_HOST_BUSY;
else if (err)
{
log_error ("failed to acquire apdu lock: %s\n", strerror (err));
return SW_HOST_LOCKING_FAILED;
}
#endif /*USE_NPTH*/
return 0;
}
static void
unlock_slot (int slot)
{
#ifdef USE_NPTH
int err;
err = npth_mutex_unlock (&reader_table[slot].lock);
if (err)
log_error ("failed to release apdu lock: %s\n", strerror (errno));
#endif /*USE_NPTH*/
}
/* Find an unused reader slot for PORTSTR and put it into the reader
table. Return -1 on error or the index into the reader table.
Acquire slot's lock on successful return. Caller needs to unlock it. */
static int
new_reader_slot (void)
{
int i, reader = -1;
for (i=0; i < MAX_READER; i++)
if (!reader_table[i].used)
{
reader = i;
reader_table[reader].used = 1;
break;
}
if (reader == -1)
{
log_error ("new_reader_slot: out of slots\n");
return -1;
}
if (lock_slot (reader))
{
reader_table[reader].used = 0;
return -1;
}
reader_table[reader].connect_card = NULL;
reader_table[reader].disconnect_card = NULL;
reader_table[reader].close_reader = NULL;
reader_table[reader].reset_reader = NULL;
reader_table[reader].get_status_reader = NULL;
reader_table[reader].send_apdu_reader = NULL;
reader_table[reader].check_pinpad = check_pcsc_pinpad;
reader_table[reader].dump_status_reader = NULL;
reader_table[reader].set_progress_cb = NULL;
reader_table[reader].set_prompt_cb = NULL;
reader_table[reader].pinpad_verify = pcsc_pinpad_verify;
reader_table[reader].pinpad_modify = pcsc_pinpad_modify;
reader_table[reader].is_t0 = 1;
reader_table[reader].pinpad_varlen_supported = 0;
reader_table[reader].require_get_status = 1;
reader_table[reader].pcsc.verify_ioctl = 0;
reader_table[reader].pcsc.modify_ioctl = 0;
reader_table[reader].pcsc.pinmin = -1;
reader_table[reader].pcsc.pinmax = -1;
reader_table[reader].pcsc.current_state = PCSC_STATE_UNAWARE;
return reader;
}
static void
dump_reader_status (int slot)
{
if (!opt.verbose)
return;
if (reader_table[slot].dump_status_reader)
reader_table[slot].dump_status_reader (slot);
if (reader_table[slot].atrlen)
{
log_info ("slot %d: ATR=", slot);
log_printhex (reader_table[slot].atr, reader_table[slot].atrlen, "");
}
}
static const char *
host_sw_string (long err)
{
switch (err)
{
case 0: return "okay";
case SW_HOST_OUT_OF_CORE: return "out of core";
case SW_HOST_INV_VALUE: return "invalid value";
case SW_HOST_NO_DRIVER: return "no driver";
case SW_HOST_NOT_SUPPORTED: return "not supported";
case SW_HOST_LOCKING_FAILED: return "locking failed";
case SW_HOST_BUSY: return "busy";
case SW_HOST_NO_CARD: return "no card";
case SW_HOST_CARD_INACTIVE: return "card inactive";
case SW_HOST_CARD_IO_ERROR: return "card I/O error";
case SW_HOST_GENERAL_ERROR: return "general error";
case SW_HOST_NO_READER: return "no reader";
case SW_HOST_ABORTED: return "aborted";
case SW_HOST_NO_PINPAD: return "no pinpad";
case SW_HOST_ALREADY_CONNECTED: return "already connected";
case SW_HOST_CANCELLED: return "cancelled";
case SW_HOST_USB_OTHER: return "USB general error";
case SW_HOST_USB_IO: return "USB I/O error";
case SW_HOST_USB_ACCESS: return "USB permission denied";
case SW_HOST_USB_NO_DEVICE:return "USB no device";
case SW_HOST_USB_BUSY: return "USB busy";
case SW_HOST_USB_TIMEOUT: return "USB timeout";
case SW_HOST_USB_OVERFLOW: return "USB overflow";
default: return "unknown host status error";
}
}
const char *
apdu_strerror (int rc)
{
switch (rc)
{
case SW_EOF_REACHED : return "eof reached";
case SW_TERM_STATE : return "termination state";
case SW_EEPROM_FAILURE : return "eeprom failure";
case SW_ACK_TIMEOUT : return "ACK timeout";
case SW_WRONG_LENGTH : return "wrong length";
case SW_SM_NOT_SUP : return "secure messaging not supported";
case SW_CC_NOT_SUP : return "command chaining not supported";
case SW_FILE_STRUCT : return "command can't be used for file structure.";
case SW_CHV_WRONG : return "CHV wrong";
case SW_CHV_BLOCKED : return "CHV blocked";
case SW_REF_DATA_INV : return "referenced data invalidated";
case SW_USE_CONDITIONS : return "use conditions not satisfied";
case SW_NO_CURRENT_EF : return "no current EF selected";
case SW_BAD_PARAMETER : return "bad parameter";
case SW_NOT_SUPPORTED : return "not supported";
case SW_FILE_NOT_FOUND : return "file not found";
case SW_RECORD_NOT_FOUND:return "record not found";
case SW_NOT_ENOUGH_MEMORY: return "not enough memory space in the file";
case SW_INCONSISTENT_LC: return "Lc inconsistent with TLV structure";
case SW_INCORRECT_P0_P1: return "incorrect parameters P0,P1";
case SW_BAD_LC : return "Lc inconsistent with P0,P1";
case SW_REF_NOT_FOUND : return "reference not found";
case SW_BAD_P0_P1 : return "bad P0,P1";
case SW_EXACT_LENGTH : return "exact length";
case SW_INS_NOT_SUP : return "instruction not supported";
case SW_CLA_NOT_SUP : return "class not supported";
case SW_SUCCESS : return "success";
default:
if ((rc & ~0x00ff) == SW_MORE_DATA)
return "more data available";
if ( (rc & 0x10000) )
return host_sw_string (rc);
return "unknown status error";
}
}
/*
PC/SC Interface
*/
static const char *
pcsc_error_string (long err)
{
const char *s;
if (!err)
return "okay";
if ((err & 0x80100000) != 0x80100000)
return "invalid PC/SC error code";
err &= 0xffff;
switch (err)
{
case 0x0002: s = "cancelled"; break;
case 0x000e: s = "can't dispose"; break;
case 0x0008: s = "insufficient buffer"; break;
case 0x0015: s = "invalid ATR"; break;
case 0x0003: s = "invalid handle"; break;
case 0x0004: s = "invalid parameter"; break;
case 0x0005: s = "invalid target"; break;
case 0x0011: s = "invalid value"; break;
case 0x0006: s = "no memory"; break;
case 0x0013: s = "comm error"; break;
case 0x0001: s = "internal error"; break;
case 0x0014: s = "unknown error"; break;
case 0x0007: s = "waited too long"; break;
case 0x0009: s = "unknown reader"; break;
case 0x000a: s = "timeout"; break;
case 0x000b: s = "sharing violation"; break;
case 0x000c: s = "no smartcard"; break;
case 0x000d: s = "unknown card"; break;
case 0x000f: s = "proto mismatch"; break;
case 0x0010: s = "not ready"; break;
case 0x0012: s = "system cancelled"; break;
case 0x0016: s = "not transacted"; break;
case 0x0017: s = "reader unavailable"; break;
case 0x0065: s = "unsupported card"; break;
case 0x0066: s = "unresponsive card"; break;
case 0x0067: s = "unpowered card"; break;
case 0x0068: s = "reset card"; break;
case 0x0069: s = "removed card"; break;
case 0x006a: s = "inserted card"; break;
case 0x001f: s = "unsupported feature"; break;
case 0x0019: s = "PCI too small"; break;
case 0x001a: s = "reader unsupported"; break;
case 0x001b: s = "duplicate reader"; break;
case 0x001c: s = "card unsupported"; break;
case 0x001d: s = "no service"; break;
case 0x001e: s = "service stopped"; break;
case 0x002e: s = "no readers available"; break;
default: s = "unknown PC/SC error code"; break;
}
return s;
}
/* Map PC/SC error codes to our special host status words. */
static int
pcsc_error_to_sw (long ec)
{
int rc;
switch ( PCSC_ERR_MASK (ec) )
{
case 0: rc = 0; break;
case PCSC_E_CANCELLED: rc = SW_HOST_CANCELLED; break;
case PCSC_E_NO_MEMORY: rc = SW_HOST_OUT_OF_CORE; break;
case PCSC_E_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
case PCSC_E_NO_SERVICE:
case PCSC_E_SERVICE_STOPPED:
case PCSC_E_UNKNOWN_READER: rc = SW_HOST_NO_READER; break;
case PCSC_E_NO_READERS_AVAILABLE:rc = SW_HOST_NO_READER; break;
case PCSC_E_SHARING_VIOLATION: rc = SW_HOST_LOCKING_FAILED; break;
case PCSC_E_NO_SMARTCARD: rc = SW_HOST_NO_CARD; break;
case PCSC_W_REMOVED_CARD: rc = SW_HOST_NO_CARD; break;
case PCSC_E_INVALID_TARGET:
case PCSC_E_INVALID_VALUE:
case PCSC_E_INVALID_HANDLE:
case PCSC_E_INVALID_PARAMETER:
case PCSC_E_INSUFFICIENT_BUFFER: rc = SW_HOST_INV_VALUE; break;
default: rc = SW_HOST_GENERAL_ERROR; break;
}
return rc;
}
static void
dump_pcsc_reader_status (int slot)
{
if (reader_table[slot].pcsc.card)
{
log_info ("reader slot %d: active protocol:", slot);
if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T0))
log_printf (" T0");
else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
log_printf (" T1");
else if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_RAW))
log_printf (" raw");
log_printf ("\n");
}
else
log_info ("reader slot %d: not connected\n", slot);
}
static int
pcsc_get_status (int slot, unsigned int *status, int on_wire)
{
long err;
struct pcsc_readerstate_s rdrstates[1];
(void)on_wire;
memset (rdrstates, 0, sizeof *rdrstates);
rdrstates[0].reader = reader_table[slot].rdrname;
rdrstates[0].current_state = reader_table[slot].pcsc.current_state;
err = pcsc_get_status_change (pcsc.context, 0, rdrstates, 1);
if (err == PCSC_E_TIMEOUT)
err = 0; /* Timeout is no error here. */
if (err)
{
log_error ("pcsc_get_status_change failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return pcsc_error_to_sw (err);
}
if ((rdrstates[0].event_state & PCSC_STATE_CHANGED))
reader_table[slot].pcsc.current_state =
(rdrstates[0].event_state & ~PCSC_STATE_CHANGED);
if (DBG_READER)
log_debug
("pcsc_get_status_change: %s%s%s%s%s%s%s%s%s%s\n",
(rdrstates[0].event_state & PCSC_STATE_IGNORE)? " ignore":"",
(rdrstates[0].event_state & PCSC_STATE_CHANGED)? " changed":"",
(rdrstates[0].event_state & PCSC_STATE_UNKNOWN)? " unknown":"",
(rdrstates[0].event_state & PCSC_STATE_UNAVAILABLE)?" unavail":"",
(rdrstates[0].event_state & PCSC_STATE_EMPTY)? " empty":"",
(rdrstates[0].event_state & PCSC_STATE_PRESENT)? " present":"",
(rdrstates[0].event_state & PCSC_STATE_ATRMATCH)? " atr":"",
(rdrstates[0].event_state & PCSC_STATE_EXCLUSIVE)? " excl":"",
(rdrstates[0].event_state & PCSC_STATE_INUSE)? " inuse":"",
(rdrstates[0].event_state & PCSC_STATE_MUTE)? " mute":"" );
*status = 0;
if ( (reader_table[slot].pcsc.current_state & PCSC_STATE_PRESENT) )
{
*status |= APDU_CARD_PRESENT;
if ( !(reader_table[slot].pcsc.current_state & PCSC_STATE_MUTE) )
*status |= APDU_CARD_ACTIVE;
}
#ifndef HAVE_W32_SYSTEM
/* We indicate a useful card if it is not in use by another
application. This is because we only use exclusive access
mode. */
if ( (*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
== (APDU_CARD_PRESENT|APDU_CARD_ACTIVE)
&& (opt.pcsc_shared
|| !(reader_table[slot].pcsc.current_state & PCSC_STATE_INUSE)))
*status |= APDU_CARD_USABLE;
#else
/* Some winscard drivers may set EXCLUSIVE and INUSE at the same
time when we are the only user (SCM SCR335) under Windows. */
if ((*status & (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
== (APDU_CARD_PRESENT|APDU_CARD_ACTIVE))
*status |= APDU_CARD_USABLE;
#endif
if (!on_wire && (rdrstates[0].event_state & PCSC_STATE_CHANGED))
/* Event like sleep/resume occurs, which requires RESET. */
return SW_HOST_NO_READER;
else
return 0;
}
/* Send the APDU of length APDULEN to SLOT and return a maximum of
*BUFLEN data in BUFFER, the actual returned size will be stored at
BUFLEN. Returns: A status word. */
static int
pcsc_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
long err;
struct pcsc_io_request_s send_pci;
pcsc_dword_t recv_len;
(void)pininfo;
if (!reader_table[slot].atrlen
&& (err = reset_pcsc_reader (slot)))
return err;
if (DBG_CARD_IO)
{
/* Do not dump the PIN in a VERIFY command. */
if (apdulen > 5 && apdu[1] == 0x20 && !opt.debug_allow_pin_logging)
log_debug ("PCSC_data: %02X %02X %02X %02X %02X [hidden]\n",
apdu[0], apdu[1], apdu[2], apdu[3], apdu[4]);
else
log_printhex (apdu, apdulen, "PCSC_data:");
}
if ((reader_table[slot].pcsc.protocol & PCSC_PROTOCOL_T1))
send_pci.protocol = PCSC_PROTOCOL_T1;
else
send_pci.protocol = PCSC_PROTOCOL_T0;
send_pci.pci_len = sizeof send_pci;
recv_len = *buflen;
err = pcsc_transmit (reader_table[slot].pcsc.card,
&send_pci, apdu, apdulen,
NULL, buffer, &recv_len);
*buflen = recv_len;
if (err)
log_error ("pcsc_transmit failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
/* Handle fatal errors which require shutdown of reader. */
if (err == PCSC_E_NOT_TRANSACTED || err == PCSC_W_RESET_CARD
|| err == PCSC_W_REMOVED_CARD)
{
reader_table[slot].pcsc.current_state = PCSC_STATE_UNAWARE;
scd_kick_the_loop ();
}
return pcsc_error_to_sw (err);
}
/* Do some control with the value of IOCTL_CODE to the card inserted
to SLOT. Input buffer is specified by CNTLBUF of length LEN.
Output buffer is specified by BUFFER of length *BUFLEN, and the
actual output size will be stored at BUFLEN. Returns: A status word.
This routine is used for PIN pad input support. */
static int
control_pcsc (int slot, pcsc_dword_t ioctl_code,
const unsigned char *cntlbuf, size_t len,
unsigned char *buffer, pcsc_dword_t *buflen)
{
long err;
err = pcsc_control (reader_table[slot].pcsc.card, ioctl_code,
cntlbuf, len, buffer, buflen? *buflen:0, buflen);
if (err)
{
log_error ("pcsc_control failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return pcsc_error_to_sw (err);
}
return 0;
}
static void
release_pcsc_context (void)
{
/*log_debug ("%s: releasing context\n", __func__);*/
log_assert (pcsc.context != 0);
pcsc_release_context (pcsc.context);
pcsc.context = 0;
}
static int
close_pcsc_reader (int slot)
{
/*log_debug ("%s: count=%d (ctx=%x)\n", __func__, pcsc.count, pcsc.context);*/
(void)slot;
log_assert (pcsc.count > 0);
if (!--pcsc.count)
release_pcsc_context ();
return 0;
}
/* Connect a PC/SC card. */
static int
connect_pcsc_card (int slot)
{
long err;
log_assert (slot >= 0 && slot < MAX_READER);
if (reader_table[slot].pcsc.card)
return SW_HOST_ALREADY_CONNECTED;
reader_table[slot].atrlen = 0;
reader_table[slot].is_t0 = 0;
err = pcsc_connect (pcsc.context,
reader_table[slot].rdrname,
opt.pcsc_shared? PCSC_SHARE_SHARED:PCSC_SHARE_EXCLUSIVE,
PCSC_PROTOCOL_T0|PCSC_PROTOCOL_T1,
&reader_table[slot].pcsc.card,
&reader_table[slot].pcsc.protocol);
if (err)
{
reader_table[slot].pcsc.card = 0;
if (err != PCSC_E_NO_SMARTCARD)
log_error ("pcsc_connect failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
if (err == PCSC_W_REMOVED_CARD && pcsc_cancel)
{
long err2;
if ((err2=pcsc_cancel (pcsc.context)))
log_error ("pcsc_cancel failed: %s (0x%lx)\n",
pcsc_error_string (err2), err2);
else if (opt.verbose)
log_error ("pcsc_cancel succeeded\n");
}
}
else
{
char reader[250];
pcsc_dword_t readerlen, atrlen;
pcsc_dword_t card_state, card_protocol;
pcsc_vendor_specific_init (slot);
atrlen = DIM (reader_table[0].atr);
readerlen = sizeof reader - 1;
err = pcsc_status (reader_table[slot].pcsc.card,
reader, &readerlen,
&card_state, &card_protocol,
reader_table[slot].atr, &atrlen);
if (err)
log_error ("pcsc_status failed: %s (0x%lx) %lu\n",
pcsc_error_string (err), err, (long unsigned int)readerlen);
else
{
if (atrlen > DIM (reader_table[0].atr))
log_bug ("ATR returned by pcsc_status is too large\n");
reader_table[slot].atrlen = atrlen;
reader_table[slot].is_t0 = !!(card_protocol & PCSC_PROTOCOL_T0);
}
}
dump_reader_status (slot);
return pcsc_error_to_sw (err);
}
static int
disconnect_pcsc_card (int slot)
{
long err;
log_assert (slot >= 0 && slot < MAX_READER);
if (!reader_table[slot].pcsc.card)
return 0;
err = pcsc_disconnect (reader_table[slot].pcsc.card, PCSC_LEAVE_CARD);
if (err)
{
log_error ("pcsc_disconnect failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return SW_HOST_CARD_IO_ERROR;
}
reader_table[slot].pcsc.card = 0;
return 0;
}
/* Send an PC/SC reset command and return a status word on error or 0
on success. */
static int
reset_pcsc_reader (int slot)
{
int sw;
sw = disconnect_pcsc_card (slot);
if (!sw)
sw = connect_pcsc_card (slot);
return sw;
}
/* Examine reader specific parameters and initialize. This is mostly
for pinpad input. Called at opening the connection to the reader. */
static int
pcsc_vendor_specific_init (int slot)
{
unsigned char buf[256];
pcsc_dword_t len;
int sw;
int vendor = 0;
int product = 0;
pcsc_dword_t get_tlv_ioctl = (pcsc_dword_t)-1;
unsigned char *p;
len = sizeof (buf);
sw = control_pcsc (slot, CM_IOCTL_GET_FEATURE_REQUEST, NULL, 0, buf, &len);
if (sw)
{
log_error ("pcsc_vendor_specific_init: GET_FEATURE_REQUEST failed: %d\n",
sw);
return SW_NOT_SUPPORTED;
}
else
{
p = buf;
while (p < buf + len)
{
unsigned char code = *p++;
int l = *p++;
unsigned int v = 0;
if (l == 1)
v = p[0];
else if (l == 2)
v = buf16_to_uint (p);
else if (l == 4)
v = buf32_to_uint (p);
if (code == FEATURE_VERIFY_PIN_DIRECT)
reader_table[slot].pcsc.verify_ioctl = v;
else if (code == FEATURE_MODIFY_PIN_DIRECT)
reader_table[slot].pcsc.modify_ioctl = v;
else if (code == FEATURE_GET_TLV_PROPERTIES)
get_tlv_ioctl = v;
if (DBG_CARD_IO)
log_debug ("feature: code=%02X, len=%d, v=%02X\n", code, l, v);
p += l;
}
}
if (get_tlv_ioctl == (pcsc_dword_t)-1)
{
/*
* For system which doesn't support GET_TLV_PROPERTIES,
* we put some heuristics here.
*/
if (reader_table[slot].rdrname)
{
if (strstr (reader_table[slot].rdrname, "SPRx32"))
{
const unsigned char cmd[] = { '\x80', '\x02', '\x00' };
sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE,
cmd, sizeof (cmd), NULL, 0);
/* Even though it's control at IFD level (request to the
* reader, not card), it returns an error when card is
* not active. Just ignore the error.
*/
if (sw)
log_debug ("Ignore control_pcsc failure.\n");
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (strstr (reader_table[slot].rdrname, "ST-2xxx"))
{
reader_table[slot].pcsc.pinmax = 15;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (strstr (reader_table[slot].rdrname, "cyberJack")
|| strstr (reader_table[slot].rdrname, "DIGIPASS")
|| strstr (reader_table[slot].rdrname, "Gnuk")
|| strstr (reader_table[slot].rdrname, "KAAN")
|| strstr (reader_table[slot].rdrname, "Trustica"))
reader_table[slot].pinpad_varlen_supported = 1;
}
return 0;
}
len = sizeof (buf);
sw = control_pcsc (slot, get_tlv_ioctl, NULL, 0, buf, &len);
if (sw)
{
log_error ("pcsc_vendor_specific_init: GET_TLV_IOCTL failed: %d\n", sw);
return SW_NOT_SUPPORTED;
}
p = buf;
while (p < buf + len)
{
unsigned char tag = *p++;
int l = *p++;
unsigned int v = 0;
/* Umm... here is little endian, while the encoding above is big. */
if (l == 1)
v = p[0];
else if (l == 2)
v = (((unsigned int)p[1] << 8) | p[0]);
else if (l == 4)
v = (((unsigned int)p[3] << 24) | (p[2] << 16) | (p[1] << 8) | p[0]);
if (tag == PCSCv2_PART10_PROPERTY_bMinPINSize)
reader_table[slot].pcsc.pinmin = v;
else if (tag == PCSCv2_PART10_PROPERTY_bMaxPINSize)
reader_table[slot].pcsc.pinmax = v;
else if (tag == PCSCv2_PART10_PROPERTY_wIdVendor)
vendor = v;
else if (tag == PCSCv2_PART10_PROPERTY_wIdProduct)
product = v;
if (DBG_CARD_IO)
log_debug ("TLV properties: tag=%02X, len=%d, v=%08X\n", tag, l, v);
p += l;
}
if (vendor == VENDOR_VEGA && product == VEGA_ALPHA)
{
/*
* Please read the comment of ccid_vendor_specific_init in
* ccid-driver.c.
*/
const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE,
cmd, sizeof (cmd), NULL, 0);
if (sw)
return SW_NOT_SUPPORTED;
}
else if (vendor == VENDOR_SCM && product == SCM_SPR532)
{
const unsigned char cmd[] = { '\x80', '\x02', '\x00' };
sw = control_pcsc (slot, CM_IOCTL_VENDOR_IFD_EXCHANGE,
cmd, sizeof (cmd), NULL, 0);
/* Even though it's control at IFD level (request to the
* reader, not card), it returns an error when card is
* not active. Just ignore the error.
*/
if (sw)
log_debug ("Ignore control_pcsc failure.\n");
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (vendor == VENDOR_CHERRY)
{
/* Cherry ST-2xxx (product == 0x003e) supports TPDU level
* exchange. Other products which only support short APDU level
* exchange only work with shorter keys like RSA 1024.
*/
reader_table[slot].pcsc.pinmax = 15;
reader_table[slot].pinpad_varlen_supported = 1;
}
else if (vendor == VENDOR_REINER /* Tested with Reiner cyberJack GO */
|| vendor == VENDOR_VASCO /* Tested with Vasco DIGIPASS 920 */
|| vendor == VENDOR_FSIJ /* Tested with FSIJ Gnuk Token */
|| vendor == VENDOR_KAAN /* Tested with KAAN Advanced??? */
|| (vendor == VENDOR_NXP
&& product == CRYPTOUCAN) /* Tested with Trustica Cryptoucan */)
reader_table[slot].pinpad_varlen_supported = 1;
return 0;
}
static int
pcsc_init (void)
{
static int pcsc_api_loaded;
long err;
/* Lets try the PC/SC API */
if (!pcsc_api_loaded)
{
void *handle;
handle = dlopen (opt.pcsc_driver, RTLD_LAZY);
if (!handle)
{
log_error ("apdu_open_reader: failed to open driver '%s': %s\n",
opt.pcsc_driver, dlerror ());
return -1;
}
pcsc_establish_context = dlsym (handle, "SCardEstablishContext");
pcsc_release_context = dlsym (handle, "SCardReleaseContext");
pcsc_cancel = dlsym (handle, "SCardCancel");
pcsc_list_readers = dlsym (handle, "SCardListReaders");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_list_readers)
pcsc_list_readers = dlsym (handle, "SCardListReadersA");
#endif
pcsc_get_status_change = dlsym (handle, "SCardGetStatusChange");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_get_status_change)
pcsc_get_status_change = dlsym (handle, "SCardGetStatusChangeA");
#endif
pcsc_connect = dlsym (handle, "SCardConnect");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_connect)
pcsc_connect = dlsym (handle, "SCardConnectA");
#endif
pcsc_reconnect = dlsym (handle, "SCardReconnect");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_reconnect)
pcsc_reconnect = dlsym (handle, "SCardReconnectA");
#endif
pcsc_disconnect = dlsym (handle, "SCardDisconnect");
pcsc_status = dlsym (handle, "SCardStatus");
#if defined(_WIN32) || defined(__CYGWIN__)
if (!pcsc_status)
pcsc_status = dlsym (handle, "SCardStatusA");
#endif
pcsc_begin_transaction = dlsym (handle, "SCardBeginTransaction");
pcsc_end_transaction = dlsym (handle, "SCardEndTransaction");
pcsc_transmit = dlsym (handle, "SCardTransmit");
pcsc_set_timeout = dlsym (handle, "SCardSetTimeout");
pcsc_control = dlsym (handle, "SCardControl");
if (!pcsc_establish_context
|| !pcsc_release_context
|| !pcsc_list_readers
|| !pcsc_get_status_change
|| !pcsc_connect
|| !pcsc_reconnect
|| !pcsc_disconnect
|| !pcsc_status
|| !pcsc_begin_transaction
|| !pcsc_end_transaction
|| !pcsc_transmit
|| !pcsc_control
/* || !pcsc_set_timeout */)
{
/* Note that set_timeout is currently not used and also not
available under Windows. */
log_error ("apdu_open_reader: invalid PC/SC driver "
"(%d%d%d%d%d%d%d%d%d%d%d%d%d)\n",
!!pcsc_establish_context,
!!pcsc_release_context,
!!pcsc_list_readers,
!!pcsc_get_status_change,
!!pcsc_connect,
!!pcsc_reconnect,
!!pcsc_disconnect,
!!pcsc_status,
!!pcsc_begin_transaction,
!!pcsc_end_transaction,
!!pcsc_transmit,
!!pcsc_set_timeout,
!!pcsc_control );
dlclose (handle);
return -1;
}
pcsc_api_loaded = 1;
}
err = pcsc_establish_context (PCSC_SCOPE_SYSTEM, NULL, NULL,
&pcsc.context);
if (err)
{
log_error ("pcsc_establish_context failed: %s (0x%lx)\n",
pcsc_error_string (err), err);
return -1;
}
return 0;
}
/* Open the PC/SC reader. Returns -1 on error or a slot number for
the reader. */
static int
open_pcsc_reader (const char *rdrname)
{
int slot;
slot = new_reader_slot ();
if (slot == -1)
return -1;
pcsc.count++;
reader_table[slot].rdrname = xtrystrdup (rdrname);
if (!reader_table[slot].rdrname)
{
log_error ("error allocating memory for reader name\n");
close_pcsc_reader (slot);
reader_table[slot].used = 0;
unlock_slot (slot);
return -1;
}
reader_table[slot].pcsc.card = 0;
reader_table[slot].atrlen = 0;
reader_table[slot].connect_card = connect_pcsc_card;
reader_table[slot].disconnect_card = disconnect_pcsc_card;
reader_table[slot].close_reader = close_pcsc_reader;
reader_table[slot].reset_reader = reset_pcsc_reader;
reader_table[slot].get_status_reader = pcsc_get_status;
reader_table[slot].send_apdu_reader = pcsc_send_apdu;
reader_table[slot].dump_status_reader = dump_pcsc_reader_status;
dump_reader_status (slot);
unlock_slot (slot);
return slot;
}
/* Check whether the reader supports the ISO command code COMMAND
on the pinpad. Return 0 on success. */
static int
check_pcsc_pinpad (int slot, int command, pininfo_t *pininfo)
{
int r;
if (reader_table[slot].pcsc.pinmin >= 0)
pininfo->minlen = reader_table[slot].pcsc.pinmin;
if (reader_table[slot].pcsc.pinmax >= 0)
pininfo->maxlen = reader_table[slot].pcsc.pinmax;
if (!pininfo->minlen)
pininfo->minlen = 1;
if (!pininfo->maxlen)
pininfo->maxlen = 15;
if ((command == ISO7816_VERIFY && reader_table[slot].pcsc.verify_ioctl != 0)
|| (command == ISO7816_CHANGE_REFERENCE_DATA
&& reader_table[slot].pcsc.modify_ioctl != 0))
r = 0; /* Success */
else
r = SW_NOT_SUPPORTED;
if (DBG_CARD_IO)
log_debug ("check_pcsc_pinpad: command=%02X, r=%d\n",
(unsigned int)command, r);
if (reader_table[slot].pinpad_varlen_supported)
pininfo->fixedlen = 0;
return r;
}
#define PIN_VERIFY_STRUCTURE_SIZE 24
static int
pcsc_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
int sw;
unsigned char *pin_verify;
int len = PIN_VERIFY_STRUCTURE_SIZE + pininfo->fixedlen;
/*
* The result buffer is only expected to have two-byte result on
* return. However, some implementation uses this buffer for lower
* layer too and it assumes that there is enough space for lower
* layer communication. Such an implementation fails for TPDU
* readers with "insufficient buffer", as it needs header and
* trailer. Six is the number for header + result + trailer (TPDU).
*/
unsigned char result[6];
pcsc_dword_t resultlen = 6;
if (!reader_table[slot].atrlen
&& (sw = reset_pcsc_reader (slot)))
return sw;
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return SW_NOT_SUPPORTED;
pin_verify = xtrymalloc (len);
if (!pin_verify)
return SW_HOST_OUT_OF_CORE;
pin_verify[0] = 0x00; /* bTimeOut */
pin_verify[1] = 0x00; /* bTimeOut2 */
pin_verify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
pin_verify[3] = pininfo->fixedlen; /* bmPINBlockString */
pin_verify[4] = 0x00; /* bmPINLengthFormat */
pin_verify[5] = pininfo->maxlen; /* wPINMaxExtraDigit */
pin_verify[6] = pininfo->minlen; /* wPINMaxExtraDigit */
pin_verify[7] = 0x02; /* bEntryValidationCondition: Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
pin_verify[7] |= 0x01; /* Max size reached. */
pin_verify[8] = 0x01; /* bNumberMessage: One message */
pin_verify[9] = 0x09; /* wLangId: 0x0409: US English */
pin_verify[10] = 0x04; /* wLangId: 0x0409: US English */
pin_verify[11] = 0x00; /* bMsgIndex */
pin_verify[12] = 0x00; /* bTeoPrologue[0] */
pin_verify[13] = 0x00; /* bTeoPrologue[1] */
pin_verify[14] = pininfo->fixedlen + 0x05; /* bTeoPrologue[2] */
pin_verify[15] = pininfo->fixedlen + 0x05; /* ulDataLength */
pin_verify[16] = 0x00; /* ulDataLength */
pin_verify[17] = 0x00; /* ulDataLength */
pin_verify[18] = 0x00; /* ulDataLength */
pin_verify[19] = class; /* abData[0] */
pin_verify[20] = ins; /* abData[1] */
pin_verify[21] = p0; /* abData[2] */
pin_verify[22] = p1; /* abData[3] */
pin_verify[23] = pininfo->fixedlen; /* abData[4] */
if (pininfo->fixedlen)
memset (&pin_verify[24], 0xff, pininfo->fixedlen);
if (DBG_CARD_IO)
log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
class, ins, p0, p1, len, pininfo->maxlen);
sw = control_pcsc (slot, reader_table[slot].pcsc.verify_ioctl,
pin_verify, len, result, &resultlen);
xfree (pin_verify);
if (sw || resultlen < 2)
{
log_error ("control_pcsc failed: %d\n", sw);
return sw? sw: SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (DBG_CARD_IO)
log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
return sw;
}
#define PIN_MODIFY_STRUCTURE_SIZE 29
static int
pcsc_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
int sw;
unsigned char *pin_modify;
int len = PIN_MODIFY_STRUCTURE_SIZE + 2 * pininfo->fixedlen;
unsigned char result[6]; /* See the comment at pinpad_verify. */
pcsc_dword_t resultlen = 6;
if (!reader_table[slot].atrlen
&& (sw = reset_pcsc_reader (slot)))
return sw;
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return SW_NOT_SUPPORTED;
pin_modify = xtrymalloc (len);
if (!pin_modify)
return SW_HOST_OUT_OF_CORE;
pin_modify[0] = 0x00; /* bTimeOut */
pin_modify[1] = 0x00; /* bTimeOut2 */
pin_modify[2] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
pin_modify[3] = pininfo->fixedlen; /* bmPINBlockString */
pin_modify[4] = 0x00; /* bmPINLengthFormat */
pin_modify[5] = 0x00; /* bInsertionOffsetOld */
pin_modify[6] = pininfo->fixedlen; /* bInsertionOffsetNew */
pin_modify[7] = pininfo->maxlen; /* wPINMaxExtraDigit */
pin_modify[8] = pininfo->minlen; /* wPINMaxExtraDigit */
pin_modify[9] = (p0 == 0 ? 0x03 : 0x01);
/* bConfirmPIN
* 0x00: new PIN once
* 0x01: new PIN twice (confirmation)
* 0x02: old PIN and new PIN once
* 0x03: old PIN and new PIN twice (confirmation)
*/
pin_modify[10] = 0x02; /* bEntryValidationCondition: Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
pin_modify[10] |= 0x01; /* Max size reached. */
pin_modify[11] = 0x03; /* bNumberMessage: Three messages */
pin_modify[12] = 0x09; /* wLangId: 0x0409: US English */
pin_modify[13] = 0x04; /* wLangId: 0x0409: US English */
pin_modify[14] = 0x00; /* bMsgIndex1 */
pin_modify[15] = 0x01; /* bMsgIndex2 */
pin_modify[16] = 0x02; /* bMsgIndex3 */
pin_modify[17] = 0x00; /* bTeoPrologue[0] */
pin_modify[18] = 0x00; /* bTeoPrologue[1] */
pin_modify[19] = 2 * pininfo->fixedlen + 0x05; /* bTeoPrologue[2] */
pin_modify[20] = 2 * pininfo->fixedlen + 0x05; /* ulDataLength */
pin_modify[21] = 0x00; /* ulDataLength */
pin_modify[22] = 0x00; /* ulDataLength */
pin_modify[23] = 0x00; /* ulDataLength */
pin_modify[24] = class; /* abData[0] */
pin_modify[25] = ins; /* abData[1] */
pin_modify[26] = p0; /* abData[2] */
pin_modify[27] = p1; /* abData[3] */
pin_modify[28] = 2 * pininfo->fixedlen; /* abData[4] */
if (pininfo->fixedlen)
memset (&pin_modify[29], 0xff, 2 * pininfo->fixedlen);
if (DBG_CARD_IO)
log_debug ("send secure: c=%02X i=%02X p1=%02X p2=%02X len=%d pinmax=%d\n",
class, ins, p0, p1, len, (int)pininfo->maxlen);
sw = control_pcsc (slot, reader_table[slot].pcsc.modify_ioctl,
pin_modify, len, result, &resultlen);
xfree (pin_modify);
if (sw || resultlen < 2)
{
log_error ("control_pcsc failed: %d\n", sw);
return sw? sw : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (DBG_CARD_IO)
log_debug (" response: sw=%04X datalen=%d\n", sw, (unsigned int)resultlen);
return sw;
}
#ifdef HAVE_LIBUSB
/*
Internal CCID driver interface.
*/
static void
dump_ccid_reader_status (int slot)
{
log_info ("reader slot %d: using ccid driver\n", slot);
}
static int
close_ccid_reader (int slot)
{
ccid_close_reader (reader_table[slot].ccid.handle);
reader_table[slot].ccid.handle = NULL;
return 0;
}
static int
reset_ccid_reader (int slot)
{
int err;
reader_table_t slotp = reader_table + slot;
unsigned char atr[33];
size_t atrlen;
err = ccid_get_atr (slotp->ccid.handle, atr, sizeof atr, &atrlen);
if (err)
return err;
/* If the reset was successful, update the ATR. */
log_assert (sizeof slotp->atr >= sizeof atr);
slotp->atrlen = atrlen;
memcpy (slotp->atr, atr, atrlen);
dump_reader_status (slot);
return 0;
}
static int
set_progress_cb_ccid_reader (int slot, gcry_handler_progress_t cb, void *cb_arg)
{
reader_table_t slotp = reader_table + slot;
return ccid_set_progress_cb (slotp->ccid.handle, cb, cb_arg);
}
static int
set_prompt_cb_ccid_reader (int slot, void (*cb) (void *, int ), void *cb_arg)
{
reader_table_t slotp = reader_table + slot;
return ccid_set_prompt_cb (slotp->ccid.handle, cb, cb_arg);
}
static int
get_status_ccid (int slot, unsigned int *status, int on_wire)
{
int rc;
int bits;
rc = ccid_slot_status (reader_table[slot].ccid.handle, &bits, on_wire);
if (rc)
return rc;
if (bits == 0)
*status = (APDU_CARD_USABLE|APDU_CARD_PRESENT|APDU_CARD_ACTIVE);
else if (bits == 1)
*status = APDU_CARD_PRESENT;
else
*status = 0;
return 0;
}
/* Actually send the APDU of length APDULEN to SLOT and return a
maximum of *BUFLEN data in BUFFER, the actual returned size will be
set to BUFLEN. Returns: Internal CCID driver error code. */
static int
send_apdu_ccid (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
long err;
size_t maxbuflen;
/* If we don't have an ATR, we need to reset the reader first. */
if (!reader_table[slot].atrlen
&& (err = reset_ccid_reader (slot)))
return err;
if (DBG_CARD_IO)
{
/* Do not dump the PIN in a VERIFY command. */
if (apdulen > 5 && apdu[1] == 0x20 && !opt.debug_allow_pin_logging)
log_debug (" raw apdu: %02x%02x%02x%02x%02x [hidden]\n",
apdu[0], apdu[1], apdu[2], apdu[3], apdu[4]);
else
log_printhex (apdu, apdulen, " raw apdu:");
}
maxbuflen = *buflen;
if (pininfo)
err = ccid_transceive_secure (reader_table[slot].ccid.handle,
apdu, apdulen, pininfo,
buffer, maxbuflen, buflen);
else
err = ccid_transceive (reader_table[slot].ccid.handle,
apdu, apdulen,
buffer, maxbuflen, buflen);
if (err)
log_error ("ccid_transceive failed: (0x%lx)\n",
err);
return err;
}
/* Check whether the CCID reader supports the ISO command code COMMAND
on the pinpad. Return 0 on success. For a description of the pin
parameters, see ccid-driver.c */
static int
check_ccid_pinpad (int slot, int command, pininfo_t *pininfo)
{
unsigned char apdu[] = { 0, 0, 0, 0x81 };
apdu[1] = command;
return ccid_transceive_secure (reader_table[slot].ccid.handle, apdu,
sizeof apdu, pininfo, NULL, 0, NULL);
}
static int
ccid_pinpad_operation (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
unsigned char apdu[4];
int err, sw;
unsigned char result[2];
size_t resultlen = 2;
apdu[0] = class;
apdu[1] = ins;
apdu[2] = p0;
apdu[3] = p1;
err = ccid_transceive_secure (reader_table[slot].ccid.handle,
apdu, sizeof apdu, pininfo,
result, 2, &resultlen);
if (err)
return err;
if (resultlen < 2)
return SW_HOST_INCOMPLETE_CARD_RESPONSE;
sw = (result[resultlen-2] << 8) | result[resultlen-1];
return sw;
}
/* Open the reader and try to read an ATR. */
static int
open_ccid_reader (struct dev_list *dl, int *r_cciderr)
{
int err;
int slot;
int require_get_status;
reader_table_t slotp;
*r_cciderr = 0;
slot = new_reader_slot ();
if (slot == -1)
return -1;
slotp = reader_table + slot;
err = ccid_open_reader (dl->portstr, dl->idx, dl->table,
&slotp->ccid.handle, &slotp->rdrname);
if (!err)
{
err = ccid_get_atr (slotp->ccid.handle,
slotp->atr, sizeof slotp->atr, &slotp->atrlen);
if (err)
{
ccid_close_reader (slotp->ccid.handle);
slotp->ccid.handle = NULL;
}
}
if (err)
{
slotp->used = 0;
unlock_slot (slot);
*r_cciderr = err;
return -1;
}
require_get_status = ccid_require_get_status (slotp->ccid.handle);
reader_table[slot].close_reader = close_ccid_reader;
reader_table[slot].reset_reader = reset_ccid_reader;
reader_table[slot].get_status_reader = get_status_ccid;
reader_table[slot].send_apdu_reader = send_apdu_ccid;
reader_table[slot].check_pinpad = check_ccid_pinpad;
reader_table[slot].dump_status_reader = dump_ccid_reader_status;
reader_table[slot].set_progress_cb = set_progress_cb_ccid_reader;
reader_table[slot].set_prompt_cb = set_prompt_cb_ccid_reader;
reader_table[slot].pinpad_verify = ccid_pinpad_operation;
reader_table[slot].pinpad_modify = ccid_pinpad_operation;
/* Our CCID reader code does not support T=0 at all, thus reset the
flag. */
reader_table[slot].is_t0 = 0;
reader_table[slot].require_get_status = require_get_status;
dump_reader_status (slot);
unlock_slot (slot);
return slot;
}
#endif /* HAVE_LIBUSB */
#ifdef USE_G10CODE_RAPDU
/*
The Remote APDU Interface.
This uses the Remote APDU protocol to contact a reader.
The port number is actually an index into the list of ports as
returned via the protocol.
*/
static int
rapdu_status_to_sw (int status)
{
int rc;
switch (status)
{
case RAPDU_STATUS_SUCCESS: rc = 0; break;
case RAPDU_STATUS_INVCMD:
case RAPDU_STATUS_INVPROT:
case RAPDU_STATUS_INVSEQ:
case RAPDU_STATUS_INVCOOKIE:
case RAPDU_STATUS_INVREADER: rc = SW_HOST_INV_VALUE; break;
case RAPDU_STATUS_TIMEOUT: rc = SW_HOST_CARD_IO_ERROR; break;
case RAPDU_STATUS_CARDIO: rc = SW_HOST_CARD_IO_ERROR; break;
case RAPDU_STATUS_NOCARD: rc = SW_HOST_NO_CARD; break;
case RAPDU_STATUS_CARDCHG: rc = SW_HOST_NO_CARD; break;
case RAPDU_STATUS_BUSY: rc = SW_HOST_BUSY; break;
case RAPDU_STATUS_NEEDRESET: rc = SW_HOST_CARD_INACTIVE; break;
default: rc = SW_HOST_GENERAL_ERROR; break;
}
return rc;
}
static int
close_rapdu_reader (int slot)
{
rapdu_release (reader_table[slot].rapdu.handle);
return 0;
}
static int
reset_rapdu_reader (int slot)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
slotp = reader_table + slot;
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
if (err)
{
log_error ("sending rapdu command RESET failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command RESET failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
if (msg->datalen > DIM (slotp->atr))
{
log_error ("ATR returned by the RAPDU layer is too large\n");
rapdu_msg_release (msg);
return SW_HOST_INV_VALUE;
}
slotp->atrlen = msg->datalen;
memcpy (slotp->atr, msg->data, msg->datalen);
rapdu_msg_release (msg);
return 0;
}
static int
my_rapdu_get_status (int slot, unsigned int *status, int on_wire)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
int oldslot;
(void)on_wire;
slotp = reader_table + slot;
oldslot = rapdu_set_reader (slotp->rapdu.handle, slot);
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_STATUS);
rapdu_set_reader (slotp->rapdu.handle, oldslot);
if (err)
{
log_error ("sending rapdu command GET_STATUS failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command GET_STATUS failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
*status = msg->data[0];
rapdu_msg_release (msg);
return 0;
}
/* Actually send the APDU of length APDULEN to SLOT and return a
maximum of *BUFLEN data in BUFFER, the actual returned size will be
set to BUFLEN. Returns: APDU error code. */
static int
my_rapdu_send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen,
pininfo_t *pininfo)
{
int err;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
size_t maxlen = *buflen;
slotp = reader_table + slot;
*buflen = 0;
if (DBG_CARD_IO)
log_printhex (apdu, apdulen, " APDU_data:");
if (apdulen < 4)
{
log_error ("rapdu_send_apdu: APDU is too short\n");
return SW_HOST_INV_VALUE;
}
err = rapdu_send_apdu (slotp->rapdu.handle, apdu, apdulen);
if (err)
{
log_error ("sending rapdu command APDU failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_error ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
rapdu_msg_release (msg);
return rapdu_status_to_sw (err);
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
int sw = rapdu_status_to_sw (msg->cmd);
log_error ("rapdu command APDU failed: %s\n",
rapdu_strerror (msg->cmd));
rapdu_msg_release (msg);
return sw;
}
if (msg->datalen > maxlen)
{
log_error ("rapdu response apdu too large\n");
rapdu_msg_release (msg);
return SW_HOST_INV_VALUE;
}
*buflen = msg->datalen;
memcpy (buffer, msg->data, msg->datalen);
rapdu_msg_release (msg);
return 0;
}
static int
open_rapdu_reader (int portno,
const unsigned char *cookie, size_t length,
int (*readfnc) (void *opaque,
void *buffer, size_t size),
void *readfnc_value,
int (*writefnc) (void *opaque,
const void *buffer, size_t size),
void *writefnc_value,
void (*closefnc) (void *opaque),
void *closefnc_value)
{
int err;
int slot;
reader_table_t slotp;
rapdu_msg_t msg = NULL;
slot = new_reader_slot ();
if (slot == -1)
return -1;
slotp = reader_table + slot;
slotp->rapdu.handle = rapdu_new ();
if (!slotp->rapdu.handle)
{
slotp->used = 0;
unlock_slot (slot);
return -1;
}
rapdu_set_reader (slotp->rapdu.handle, portno);
rapdu_set_iofunc (slotp->rapdu.handle,
readfnc, readfnc_value,
writefnc, writefnc_value,
closefnc, closefnc_value);
rapdu_set_cookie (slotp->rapdu.handle, cookie, length);
/* First try to get the current ATR, but if the card is inactive
issue a reset instead. */
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_GET_ATR);
if (err == RAPDU_STATUS_NEEDRESET)
err = rapdu_send_cmd (slotp->rapdu.handle, RAPDU_CMD_RESET);
if (err)
{
log_info ("sending rapdu command GET_ATR/RESET failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
goto failure;
}
err = rapdu_read_msg (slotp->rapdu.handle, &msg);
if (err)
{
log_info ("receiving rapdu message failed: %s\n",
err < 0 ? strerror (errno): rapdu_strerror (err));
goto failure;
}
if (msg->cmd != RAPDU_STATUS_SUCCESS || !msg->datalen)
{
log_info ("rapdu command GET ATR failed: %s\n",
rapdu_strerror (msg->cmd));
goto failure;
}
if (msg->datalen > DIM (slotp->atr))
{
log_error ("ATR returned by the RAPDU layer is too large\n");
goto failure;
}
slotp->atrlen = msg->datalen;
memcpy (slotp->atr, msg->data, msg->datalen);
reader_table[slot].close_reader = close_rapdu_reader;
reader_table[slot].reset_reader = reset_rapdu_reader;
reader_table[slot].get_status_reader = my_rapdu_get_status;
reader_table[slot].send_apdu_reader = my_rapdu_send_apdu;
reader_table[slot].check_pinpad = NULL;
reader_table[slot].dump_status_reader = NULL;
reader_table[slot].pinpad_verify = NULL;
reader_table[slot].pinpad_modify = NULL;
dump_reader_status (slot);
rapdu_msg_release (msg);
unlock_slot (slot);
return slot;
failure:
rapdu_msg_release (msg);
rapdu_release (slotp->rapdu.handle);
slotp->used = 0;
unlock_slot (slot);
return -1;
}
#endif /*USE_G10CODE_RAPDU*/
/*
Driver Access
*/
gpg_error_t
apdu_dev_list_start (const char *portstr, struct dev_list **l_p)
{
struct dev_list *dl = xtrymalloc (sizeof (struct dev_list));
gpg_error_t err;
*l_p = NULL;
if (!dl)
return gpg_error_from_syserror ();
dl->table = NULL;
dl->portstr = portstr;
dl->idx = 0;
dl->idx_max = 0;
#ifdef HAVE_LIBUSB
if (!opt.disable_ccid)
{
err = ccid_dev_scan (&dl->idx_max, &dl->table);
if (err)
{
xfree (dl);
return err;
}
if (dl->idx_max == 0)
{
if (DBG_READER)
log_debug ("leave: apdu_open_reader => slot=-1 (no ccid)\n");
xfree (dl);
return gpg_error (GPG_ERR_ENODEV);
}
}
else
#endif
{ /* PC/SC readers. */
long r;
pcsc_dword_t nreader = 0;
char *p = NULL;
if (!pcsc.context)
{
/* log_debug ("%s: No context - calling init\n", __func__); */
if (pcsc_init () < 0)
{
xfree (dl);
return gpg_error (GPG_ERR_NO_SERVICE);
}
}
r = pcsc_list_readers (pcsc.context, NULL, NULL, &nreader);
if (!r)
{
p = xtrymalloc (nreader);
if (!p)
{
err = gpg_error_from_syserror ();
log_error ("error allocating memory for reader list\n");
if (pcsc.count == 0)
release_pcsc_context ();
xfree (dl);
return err;
}
r = pcsc_list_readers (pcsc.context, NULL, p, &nreader);
}
if (r)
{
log_error ("pcsc_list_readers failed: %s (0x%lx)\n",
pcsc_error_string (r), r);
xfree (p);
if (pcsc.count == 0)
release_pcsc_context ();
xfree (dl);
return iso7816_map_sw (pcsc_error_to_sw (r));
}
dl->table = p;
dl->idx_max = 0;
while (nreader > 0)
{
size_t n;
if (!*p)
break;
for (n = 0; n < nreader; n++)
if (!p[n])
break;
if (n >= nreader)
{
log_error ("invalid response from pcsc_list_readers\n");
break;
}
log_info ("detected reader '%s'\n", p);
pcsc.rdrname[dl->idx_max] = p;
nreader -= n + 1;
p += n + 1;
dl->idx_max++;
if (dl->idx_max >= MAX_READER)
{
log_error ("too many readers from pcsc_list_readers\n");
dl->idx_max--;
break;
}
}
/*
* Increment PCSC.COUNT artificially, so that PCSC.CONTEXT can
* be kept (not released) until apdu_dev_list_finish will be
* called.
*/
pcsc.count++;
}
*l_p = dl;
return 0;
}
void
apdu_dev_list_finish (struct dev_list *dl)
{
#ifdef HAVE_LIBUSB
if (!opt.disable_ccid)
{
if (dl->table)
ccid_dev_scan_finish (dl->table, dl->idx_max);
}
else
#endif
{ /* PC/SC readers. */
int i;
xfree (dl->table);
for (i = 0; i < MAX_READER; i++)
pcsc.rdrname[i] = NULL;
npth_mutex_lock (&reader_table_lock);
if (--pcsc.count == 0)
release_pcsc_context ();
npth_mutex_unlock (&reader_table_lock);
}
xfree (dl);
}
int
apdu_open_reader (struct dev_list *dl)
{
int slot;
int readerno;
if (!dl->table)
return -1;
#ifdef HAVE_LIBUSB
/* See whether we want to use the reader ID string or a reader
number. A readerno of -1 indicates that the reader ID string is
to be used. */
if (dl->portstr)
{
if (!opt.disable_ccid || strchr (dl->portstr, ':'))
readerno = -1; /* We want to use the readerid. */
else
{
readerno = atoi (dl->portstr);
if (readerno < 0 || readerno >= dl->idx_max)
return -1;
npth_mutex_lock (&reader_table_lock);
/* If already opened HANDLE, return -1. */
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used)
{
npth_mutex_unlock (&reader_table_lock);
return -1;
}
npth_mutex_unlock (&reader_table_lock);
dl->idx = readerno;
dl->portstr = NULL;
}
}
else
readerno = 0; /* Default. */
#else
if (dl->portstr)
readerno = -1; /* We want to use the readerid. */
else
readerno = 0; /* Default. */
#endif
#ifdef HAVE_LIBUSB
if (!opt.disable_ccid)
{ /* CCID readers. */
int cciderr;
if (readerno > 0)
{ /* Use single, the specific reader. */
slot = open_ccid_reader (dl, &cciderr);
/* And stick the reader and no scan. */
dl->idx = dl->idx_max;
return slot;
}
npth_mutex_lock (&reader_table_lock);
while (dl->idx < dl->idx_max)
{
unsigned int bai = ccid_get_BAI (dl->idx, dl->table);
if (DBG_READER)
log_debug ("apdu_open_reader: BAI=%x\n", bai);
/* Check identity by BAI against already opened HANDLEs. */
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used
&& reader_table[slot].ccid.handle
&& ccid_compare_BAI (reader_table[slot].ccid.handle, bai))
break;
if (slot == MAX_READER)
{ /* Found a new device. */
if (DBG_READER)
log_debug ("apdu_open_reader: new device=%x\n", bai);
slot = open_ccid_reader (dl, &cciderr);
dl->idx++;
if (slot >= 0)
{
npth_mutex_unlock (&reader_table_lock);
return slot;
}
else
{
/* Skip this reader. */
log_error ("ccid open error: skip\n");
if (cciderr == CCID_DRIVER_ERR_USB_ACCESS)
log_info ("check permission of USB device at"
" Bus %03d Device %03d\n",
((bai >> 16) & 0xff),
((bai >> 8) & 0xff));
continue;
}
}
else
dl->idx++;
}
npth_mutex_unlock (&reader_table_lock);
/* Not found. */
slot = -1;
}
else
#endif
{ /* PC/SC readers. */
if (readerno > 0)
{ /* Use single, the specific reader. */
slot = open_pcsc_reader (pcsc.rdrname[readerno]);
/* And stick the reader and no scan. */
dl->idx = dl->idx_max;
return slot;
}
npth_mutex_lock (&reader_table_lock);
while (dl->idx < dl->idx_max)
{
const char *rdrname = pcsc.rdrname[dl->idx++];
if (DBG_READER)
log_debug ("apdu_open_reader: %s\n", rdrname);
/* Check the identity of reader against already opened one. */
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used
&& !strcmp (reader_table[slot].rdrname, rdrname))
break;
if (slot == MAX_READER)
{ /* Found a new device. */
if (DBG_READER)
log_debug ("apdu_open_reader: new device=%s\n", rdrname);
/* When reader string is specified, check if it is the one. */
if (readerno < 0
&& strncmp (rdrname, dl->portstr, strlen (dl->portstr)) != 0)
continue;
slot = open_pcsc_reader (rdrname);
if (slot >= 0)
{
npth_mutex_unlock (&reader_table_lock);
return slot;
}
else
{
/* Skip this reader. */
log_error ("pcsc open error: skip\n");
continue;
}
}
}
npth_mutex_unlock (&reader_table_lock);
/* Not found. */
slot = -1;
}
return slot;
}
/* Open an remote reader and return an internal slot number or -1 on
error. This function is an alternative to apdu_open_reader and used
with remote readers only. Note that the supplied CLOSEFNC will
- only be called once and the slot will not be valid afther this.
+ only be called once and the slot will not be valid after this.
If PORTSTR is NULL we default to the first available port.
*/
int
apdu_open_remote_reader (const char *portstr,
const unsigned char *cookie, size_t length,
int (*readfnc) (void *opaque,
void *buffer, size_t size),
void *readfnc_value,
int (*writefnc) (void *opaque,
const void *buffer, size_t size),
void *writefnc_value,
void (*closefnc) (void *opaque),
void *closefnc_value)
{
#ifdef USE_G10CODE_RAPDU
return open_rapdu_reader (portstr? atoi (portstr) : 0,
cookie, length,
readfnc, readfnc_value,
writefnc, writefnc_value,
closefnc, closefnc_value);
#else
(void)portstr;
(void)cookie;
(void)length;
(void)readfnc;
(void)readfnc_value;
(void)writefnc;
(void)writefnc_value;
(void)closefnc;
(void)closefnc_value;
#ifdef _WIN32
errno = ENOENT;
#else
errno = ENOSYS;
#endif
return -1;
#endif
}
int
apdu_close_reader (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_close_reader: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_close_reader => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
sw = apdu_disconnect (slot);
if (sw)
{
/*
* When the reader/token was removed it might come here.
* It should go through to call CLOSE_READER even if we got an error.
*/
if (DBG_READER)
log_debug ("apdu_close_reader => 0x%x (apdu_disconnect)\n", sw);
}
if (reader_table[slot].close_reader)
{
npth_mutex_lock (&reader_table_lock);
sw = reader_table[slot].close_reader (slot);
xfree (reader_table[slot].rdrname);
reader_table[slot].rdrname = NULL;
reader_table[slot].used = 0;
npth_mutex_unlock (&reader_table_lock);
if (DBG_READER)
log_debug ("leave: apdu_close_reader => 0x%x (close_reader)\n", sw);
return sw;
}
npth_mutex_lock (&reader_table_lock);
xfree (reader_table[slot].rdrname);
reader_table[slot].rdrname = NULL;
reader_table[slot].used = 0;
npth_mutex_unlock (&reader_table_lock);
if (DBG_READER)
log_debug ("leave: apdu_close_reader => SW_HOST_NOT_SUPPORTED\n");
return SW_HOST_NOT_SUPPORTED;
}
/* Function suitable for a cleanup function to close all reader. It
should not be used if the reader will be opened again. The reason
for implementing this to properly close USB devices so that they
will startup the next time without error. */
void
apdu_prepare_exit (void)
{
static int sentinel;
int slot;
if (!sentinel)
{
sentinel = 1;
npth_mutex_lock (&reader_table_lock);
for (slot = 0; slot < MAX_READER; slot++)
if (reader_table[slot].used)
{
apdu_disconnect (slot);
if (reader_table[slot].close_reader)
reader_table[slot].close_reader (slot);
xfree (reader_table[slot].rdrname);
reader_table[slot].rdrname = NULL;
reader_table[slot].used = 0;
}
npth_mutex_unlock (&reader_table_lock);
sentinel = 0;
}
}
/* Enumerate all readers and return information on whether this reader
is in use. The caller should start with SLOT set to 0 and
increment it with each call until an error is returned. */
int
apdu_enum_reader (int slot, int *used)
{
if (slot < 0 || slot >= MAX_READER)
return SW_HOST_NO_DRIVER;
*used = reader_table[slot].used;
return 0;
}
/* Connect a card. This is used to power up the card and make sure
that an ATR is available. Depending on the reader backend it may
return an error for an inactive card or if no card is available.
Return -1 on error. Return 1 if reader requires get_status to
watch card removal. Return 0 if it's a token (always with a card),
or it supports INTERRUPT endpoint to watch card removal.
*/
int
apdu_connect (int slot)
{
int sw = 0;
unsigned int status = 0;
if (DBG_READER)
log_debug ("enter: apdu_connect: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_connect => SW_HOST_NO_DRIVER\n");
return -1;
}
/* Only if the access method provides a connect function we use it.
If not, we expect that the card has been implicitly connected by
apdu_open_reader. */
if (reader_table[slot].connect_card)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].connect_card (slot);
unlock_slot (slot);
}
}
/* We need to call apdu_get_status_internal, so that the last-status
machinery gets setup properly even if a card is inserted while
scdaemon is fired up and apdu_get_status has not yet been called.
Without that we would force a reset of the card with the next
call to apdu_get_status. */
if (!sw)
sw = apdu_get_status_internal (slot, 1, &status, 1);
if (sw)
;
else if (!(status & APDU_CARD_PRESENT))
sw = SW_HOST_NO_CARD;
else if ((status & APDU_CARD_PRESENT) && !(status & APDU_CARD_ACTIVE))
sw = SW_HOST_CARD_INACTIVE;
if (sw == SW_HOST_CARD_INACTIVE)
{
/* Try power it up again. */
sw = apdu_reset (slot);
}
if (DBG_READER)
log_debug ("leave: apdu_connect => sw=0x%x\n", sw);
if (sw)
return -1;
return reader_table[slot].require_get_status;
}
int
apdu_disconnect (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_disconnect: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_disconnect => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
if (reader_table[slot].disconnect_card)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].disconnect_card (slot);
unlock_slot (slot);
}
}
else
sw = 0;
if (DBG_READER)
log_debug ("leave: apdu_disconnect => sw=0x%x\n", sw);
return sw;
}
/* Set the progress callback of SLOT to CB and its args to CB_ARG. If
CB is NULL the progress callback is removed. */
int
apdu_set_progress_cb (int slot, gcry_handler_progress_t cb, void *cb_arg)
{
int sw;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].set_progress_cb)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].set_progress_cb (slot, cb, cb_arg);
unlock_slot (slot);
}
}
else
sw = 0;
return sw;
}
int
apdu_set_prompt_cb (int slot, void (*cb) (void *, int), void *cb_arg)
{
int sw;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].set_prompt_cb)
{
sw = lock_slot (slot);
if (!sw)
{
sw = reader_table[slot].set_prompt_cb (slot, cb, cb_arg);
unlock_slot (slot);
}
}
else
sw = 0;
return sw;
}
/* Do a reset for the card in reader at SLOT. */
int
apdu_reset (int slot)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_reset: slot=%d\n", slot);
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("leave: apdu_reset => SW_HOST_NO_DRIVER\n");
return SW_HOST_NO_DRIVER;
}
if ((sw = lock_slot (slot)))
{
if (DBG_READER)
log_debug ("leave: apdu_reset => sw=0x%x (lock_slot)\n", sw);
return sw;
}
if (reader_table[slot].reset_reader)
sw = reader_table[slot].reset_reader (slot);
unlock_slot (slot);
if (DBG_READER)
log_debug ("leave: apdu_reset => sw=0x%x\n", sw);
return sw;
}
/* Return the ATR or NULL if none is available. On success the length
of the ATR is stored at ATRLEN. The caller must free the returned
value. */
unsigned char *
apdu_get_atr (int slot, size_t *atrlen)
{
unsigned char *buf;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
{
if (DBG_READER)
log_debug ("apdu_get_atr => NULL (bad slot)\n");
return NULL;
}
if (!reader_table[slot].atrlen)
{
if (DBG_READER)
log_debug ("apdu_get_atr => NULL (no ATR)\n");
return NULL;
}
buf = xtrymalloc (reader_table[slot].atrlen);
if (!buf)
{
if (DBG_READER)
log_debug ("apdu_get_atr => NULL (out of core)\n");
return NULL;
}
memcpy (buf, reader_table[slot].atr, reader_table[slot].atrlen);
*atrlen = reader_table[slot].atrlen;
return buf;
}
/* Retrieve the status for SLOT. The function does only wait for the
card to become available if HANG is set to true. On success the
bits in STATUS will be set to
APDU_CARD_USABLE (bit 0) = card present and usable
APDU_CARD_PRESENT (bit 1) = card present
APDU_CARD_ACTIVE (bit 2) = card active
(bit 3) = card access locked [not yet implemented]
For most applications, testing bit 0 is sufficient.
*/
static int
apdu_get_status_internal (int slot, int hang, unsigned int *status, int on_wire)
{
int sw;
unsigned int s = 0;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if ((sw = hang? lock_slot (slot) : trylock_slot (slot)))
return sw;
if (reader_table[slot].get_status_reader)
sw = reader_table[slot].get_status_reader (slot, &s, on_wire);
unlock_slot (slot);
if (sw)
{
if (on_wire)
reader_table[slot].atrlen = 0;
s = 0;
}
if (status)
*status = s;
return sw;
}
/* See above for a description. */
int
apdu_get_status (int slot, int hang, unsigned int *status)
{
int sw;
if (DBG_READER)
log_debug ("enter: apdu_get_status: slot=%d hang=%d\n", slot, hang);
sw = apdu_get_status_internal (slot, hang, status, 0);
if (DBG_READER)
{
if (status)
log_debug ("leave: apdu_get_status => sw=0x%x status=%u\n",
sw, *status);
else
log_debug ("leave: apdu_get_status => sw=0x%x\n", sw);
}
return sw;
}
/* Check whether the reader supports the ISO command code COMMAND on
the pinpad. Return 0 on success. For a description of the pin
parameters, see ccid-driver.c */
int
apdu_check_pinpad (int slot, int command, pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (opt.enable_pinpad_varlen)
pininfo->fixedlen = 0;
if (reader_table[slot].check_pinpad)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].check_pinpad (slot, command, pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
int
apdu_pinpad_verify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].pinpad_verify)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].pinpad_verify (slot, class, ins, p0, p1,
pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
int
apdu_pinpad_modify (int slot, int class, int ins, int p0, int p1,
pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].pinpad_modify)
{
int sw;
if ((sw = lock_slot (slot)))
return sw;
sw = reader_table[slot].pinpad_modify (slot, class, ins, p0, p1,
pininfo);
unlock_slot (slot);
return sw;
}
else
return SW_HOST_NOT_SUPPORTED;
}
/* Dispatcher for the actual send_apdu function. Note, that this
function should be called in locked state. */
static int
send_apdu (int slot, unsigned char *apdu, size_t apdulen,
unsigned char *buffer, size_t *buflen, pininfo_t *pininfo)
{
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (reader_table[slot].send_apdu_reader)
return reader_table[slot].send_apdu_reader (slot,
apdu, apdulen,
buffer, buflen,
pininfo);
else
return SW_HOST_NOT_SUPPORTED;
}
/* Core APDU transceiver function. Parameters are described at
apdu_send_le with the exception of PININFO which indicates pinpad
related operations if not NULL. If EXTENDED_MODE is not 0
command chaining or extended length will be used according to these
values:
n < 0 := Use command chaining with the data part limited to -n
in each chunk. If -1 is used a default value is used.
n == 0 := No extended mode or command chaining.
n == 1 := Use extended length for input and output without a
length limit.
n > 1 := Use extended length with up to N bytes.
*/
static int
send_le (int slot, int class, int ins, int p0, int p1,
int lc, const char *data, int le,
unsigned char **retbuf, size_t *retbuflen,
pininfo_t *pininfo, int extended_mode)
{
#define SHORT_RESULT_BUFFER_SIZE 258
/* We allocate 8 extra bytes as a safety margin towards a driver bug. */
unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
unsigned char *result_buffer = NULL;
size_t result_buffer_size;
unsigned char *result;
size_t resultlen;
unsigned char short_apdu_buffer[5+256+1];
unsigned char *apdu_buffer = NULL;
size_t apdu_buffer_size;
unsigned char *apdu;
size_t apdulen;
int sw;
long rc; /* We need a long here due to PC/SC. */
int did_exact_length_hack = 0;
int use_chaining = 0;
int use_extended_length = 0;
int lc_chunk;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (DBG_CARD_IO)
log_debug ("send apdu: c=%02X i=%02X p1=%02X p2=%02X lc=%d le=%d em=%d\n",
class, ins, p0, p1, lc, le, extended_mode);
if (lc != -1 && (lc > 255 || lc < 0))
{
/* Data does not fit into an APDU. What we do now depends on
the EXTENDED_MODE parameter. */
if (!extended_mode)
return SW_WRONG_LENGTH; /* No way to send such an APDU. */
else if (extended_mode > 0)
use_extended_length = 1;
else if (extended_mode < 0)
{
/* Send APDU using chaining mode. */
if (lc > 16384)
return SW_WRONG_LENGTH; /* Sanity check. */
if ((class&0xf0) != 0)
return SW_HOST_INV_VALUE; /* Upper 4 bits need to be 0. */
use_chaining = extended_mode == -1? 255 : -extended_mode;
use_chaining &= 0xff;
}
else
return SW_HOST_INV_VALUE;
}
else if (lc == -1 && extended_mode > 0)
use_extended_length = 1;
if (le != -1 && (le > (extended_mode > 0? 255:256) || le < 0))
{
/* Expected Data does not fit into an APDU. What we do now
depends on the EXTENDED_MODE parameter. Note that a check
for command chaining does not make sense because we are
looking at Le. */
if (!extended_mode)
return SW_WRONG_LENGTH; /* No way to send such an APDU. */
else if (use_extended_length)
; /* We are already using extended length. */
else if (extended_mode > 0)
use_extended_length = 1;
else
return SW_HOST_INV_VALUE;
}
if ((!data && lc != -1) || (data && lc == -1))
return SW_HOST_INV_VALUE;
if (use_extended_length)
{
if (reader_table[slot].is_t0)
return SW_HOST_NOT_SUPPORTED;
/* Space for: cls/ins/p1/p2+Z+2_byte_Lc+Lc+2_byte_Le. */
apdu_buffer_size = 4 + 1 + (lc >= 0? (2+lc):0) + 2;
apdu_buffer = xtrymalloc (apdu_buffer_size + 10);
if (!apdu_buffer)
return SW_HOST_OUT_OF_CORE;
apdu = apdu_buffer;
}
else
{
apdu_buffer_size = sizeof short_apdu_buffer;
apdu = short_apdu_buffer;
}
if (use_extended_length && (le > 256 || le < 0))
{
/* Two more bytes are needed for status bytes. */
result_buffer_size = le < 0? 4096 : (le + 2);
result_buffer = xtrymalloc (result_buffer_size);
if (!result_buffer)
{
xfree (apdu_buffer);
return SW_HOST_OUT_OF_CORE;
}
result = result_buffer;
}
else
{
result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
result = short_result_buffer;
}
#undef SHORT_RESULT_BUFFER_SIZE
if ((sw = lock_slot (slot)))
{
xfree (apdu_buffer);
xfree (result_buffer);
return sw;
}
do
{
if (use_extended_length)
{
use_chaining = 0;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = ins;
apdu[apdulen++] = p0;
apdu[apdulen++] = p1;
if (lc > 0)
{
apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
apdu[apdulen++] = ((lc >> 8) & 0xff);
apdu[apdulen++] = (lc & 0xff);
memcpy (apdu+apdulen, data, lc);
data += lc;
apdulen += lc;
}
if (le != -1)
{
if (lc <= 0)
apdu[apdulen++] = 0; /* Z byte: Extended length marker. */
apdu[apdulen++] = ((le >> 8) & 0xff);
apdu[apdulen++] = (le & 0xff);
}
}
else
{
apdulen = 0;
apdu[apdulen] = class;
if (use_chaining && lc > 255)
{
apdu[apdulen] |= 0x10;
log_assert (use_chaining < 256);
lc_chunk = use_chaining;
lc -= use_chaining;
}
else
{
use_chaining = 0;
lc_chunk = lc;
}
apdulen++;
apdu[apdulen++] = ins;
apdu[apdulen++] = p0;
apdu[apdulen++] = p1;
if (lc_chunk != -1)
{
apdu[apdulen++] = lc_chunk;
memcpy (apdu+apdulen, data, lc_chunk);
data += lc_chunk;
apdulen += lc_chunk;
/* T=0 does not allow the use of Lc together with Le;
thus disable Le in this case. */
if (reader_table[slot].is_t0)
le = -1;
}
if (le != -1 && !use_chaining)
apdu[apdulen++] = le; /* Truncation is okay (0 means 256). */
}
exact_length_hack:
/* As a safeguard don't pass any garbage to the driver. */
log_assert (apdulen <= apdu_buffer_size);
memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, pininfo);
if (rc || resultlen < 2)
{
log_info ("apdu_send_simple(%d) failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (apdu_buffer);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
if (!use_extended_length
&& !did_exact_length_hack && SW_EXACT_LENGTH_P (sw))
{
apdu[apdulen-1] = (sw & 0x00ff);
did_exact_length_hack = 1;
goto exact_length_hack;
}
}
while (use_chaining && sw == SW_SUCCESS);
if (apdu_buffer)
{
xfree (apdu_buffer);
apdu_buffer = NULL;
}
/* Store away the returned data but strip the statusword. */
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" response: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
{
if (!resultlen)
;
else if (all_zero_p (result, resultlen))
log_debug (" dump: [all zero]\n");
else
log_printhex (result, resultlen, " dump:");
}
}
if (sw == SW_SUCCESS || sw == SW_EOF_REACHED)
{
if (retbuf)
{
*retbuf = xtrymalloc (resultlen? resultlen : 1);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
*retbuflen = resultlen;
memcpy (*retbuf, result, resultlen);
}
}
else if ((sw & 0xff00) == SW_MORE_DATA)
{
unsigned char *p = NULL, *tmp;
size_t bufsize = 4096;
/* It is likely that we need to return much more data, so we
start off with a large buffer. */
if (retbuf)
{
*retbuf = p = xtrymalloc (bufsize);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
log_assert (resultlen < bufsize);
memcpy (p, result, resultlen);
p += resultlen;
}
do
{
int len = (sw & 0x00ff);
if (DBG_CARD_IO)
log_debug ("apdu_send_simple(%d): %d more bytes available\n",
slot, len);
apdu_buffer_size = sizeof short_apdu_buffer;
apdu = short_apdu_buffer;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = 0xC0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 0;
apdu[apdulen++] = len;
log_assert (apdulen <= apdu_buffer_size);
memset (apdu+apdulen, 0, apdu_buffer_size - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
if (rc || resultlen < 2)
{
log_error ("apdu_send_simple(%d) for get response failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" more: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
{
if (all_zero_p (result, resultlen))
log_debug ( " dump: [all zero]\n");
else
log_printhex (result, resultlen, " dump:");
}
}
if ((sw & 0xff00) == SW_MORE_DATA
|| sw == SW_SUCCESS
|| sw == SW_EOF_REACHED )
{
if (retbuf && resultlen)
{
if (p - *retbuf + resultlen > bufsize)
{
bufsize += resultlen > 4096? resultlen: 4096;
tmp = xtryrealloc (*retbuf, bufsize);
if (!tmp)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
p = tmp + (p - *retbuf);
*retbuf = tmp;
}
memcpy (p, result, resultlen);
p += resultlen;
}
}
else
log_info ("apdu_send_simple(%d) "
"got unexpected status %04X from get response\n",
slot, sw);
}
while ((sw & 0xff00) == SW_MORE_DATA);
if (retbuf)
{
*retbuflen = p - *retbuf;
tmp = xtryrealloc (*retbuf, *retbuflen);
if (tmp)
*retbuf = tmp;
}
}
unlock_slot (slot);
xfree (result_buffer);
if (DBG_CARD_IO && retbuf && sw == SW_SUCCESS)
{
if (all_zero_p (*retbuf, *retbuflen))
log_debug (" dump: [all zero]\n");
else
log_printhex (*retbuf, *retbuflen, " dump:");
}
return sw;
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA, LE. A value of -1
for LC won't sent this field and the data field; in this case DATA
must also be passed as NULL. If EXTENDED_MODE is not 0 command
chaining or extended length will be used; see send_le for details.
The return value is the status word or -1 for an invalid SLOT or
other non card related error. If RETBUF is not NULL, it will
receive an allocated buffer with the returned data. The length of
that data will be put into *RETBUFLEN. The caller is responsible
for releasing the buffer even in case of errors. */
int
apdu_send_le(int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data, int le,
unsigned char **retbuf, size_t *retbuflen)
{
return send_le (slot, class, ins, p0, p1,
lc, data, le,
retbuf, retbuflen,
NULL, extended_mode);
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
or extended length will be used; see send_le for details. The
return value is the status word or -1 for an invalid SLOT or other
non card related error. If RETBUF is not NULL, it will receive an
allocated buffer with the returned data. The length of that data
will be put into *RETBUFLEN. The caller is responsible for
releasing the buffer even in case of errors. */
int
apdu_send (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data, unsigned char **retbuf, size_t *retbuflen)
{
return send_le (slot, class, ins, p0, p1, lc, data, 256,
retbuf, retbuflen, NULL, extended_mode);
}
/* Send an APDU to the card in SLOT. The APDU is created from all
given parameters: CLASS, INS, P0, P1, LC, DATA. A value of -1 for
LC won't sent this field and the data field; in this case DATA must
also be passed as NULL. If EXTENDED_MODE is not 0 command chaining
or extended length will be used; see send_le for details. The
return value is the status word or -1 for an invalid SLOT or other
non card related error. No data will be returned. */
int
apdu_send_simple (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const char *data)
{
return send_le (slot, class, ins, p0, p1, lc, data, -1, NULL, NULL, NULL,
extended_mode);
}
/* This is a more generic version of the apdu sending routine. It
* takes an already formatted APDU in APDUDATA or length APDUDATALEN
* and returns with an APDU including the status word. With
* HANDLE_MORE set to true this function will handle the MORE DATA
* status and return all APDUs concatenated with one status word at
* the end. If EXTENDED_LENGTH is != 0 extended lengths are allowed
* with a max. result data length of EXTENDED_LENGTH bytes. The
* function does not return a regular status word but 0 on success.
* If the slot is locked, the function returns immediately with an
* error.
*
* Out of historical reasons the function returns 0 on success and
* outs the status word at the end of the result to be able to get the
* status word in the case of a not provided RETBUF, R_SW can be used
* to store the SW. But note that R_SW will only be set if the
* function returns 0. */
int
apdu_send_direct (int slot, size_t extended_length,
const unsigned char *apdudata, size_t apdudatalen,
int handle_more, unsigned int *r_sw,
unsigned char **retbuf, size_t *retbuflen)
{
#define SHORT_RESULT_BUFFER_SIZE 258
unsigned char short_result_buffer[SHORT_RESULT_BUFFER_SIZE+10];
unsigned char *result_buffer = NULL;
size_t result_buffer_size;
unsigned char *result;
size_t resultlen;
unsigned char short_apdu_buffer[5+256+10];
unsigned char *apdu_buffer = NULL;
unsigned char *apdu;
size_t apdulen;
int sw;
long rc; /* we need a long here due to PC/SC. */
int class;
if (slot < 0 || slot >= MAX_READER || !reader_table[slot].used )
return SW_HOST_NO_DRIVER;
if (apdudatalen > 65535)
return SW_HOST_INV_VALUE;
if (apdudatalen > sizeof short_apdu_buffer - 5)
{
apdu_buffer = xtrymalloc (apdudatalen + 5);
if (!apdu_buffer)
return SW_HOST_OUT_OF_CORE;
apdu = apdu_buffer;
}
else
{
apdu = short_apdu_buffer;
}
apdulen = apdudatalen;
memcpy (apdu, apdudata, apdudatalen);
class = apdulen? *apdu : 0;
if (extended_length >= 256 && extended_length <= 65536)
{
result_buffer_size = extended_length;
result_buffer = xtrymalloc (result_buffer_size + 10);
if (!result_buffer)
{
xfree (apdu_buffer);
return SW_HOST_OUT_OF_CORE;
}
result = result_buffer;
}
else
{
result_buffer_size = SHORT_RESULT_BUFFER_SIZE;
result = short_result_buffer;
}
#undef SHORT_RESULT_BUFFER_SIZE
if ((sw = lock_slot (slot)))
{
xfree (apdu_buffer);
xfree (result_buffer);
return sw;
}
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
xfree (apdu_buffer);
apdu_buffer = NULL;
if (rc || resultlen < 2)
{
log_error ("apdu_send_direct(%d) failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
/* Store away the returned data but strip the statusword. */
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" response: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if ( !retbuf && (sw == SW_SUCCESS || (sw & 0xff00) == SW_MORE_DATA))
log_printhex (result, resultlen, " dump: ");
}
if (handle_more && (sw & 0xff00) == SW_MORE_DATA)
{
unsigned char *p = NULL, *tmp;
size_t bufsize = 4096;
/* It is likely that we need to return much more data, so we
start off with a large buffer. */
if (retbuf)
{
*retbuf = p = xtrymalloc (bufsize + 2);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
log_assert (resultlen < bufsize);
memcpy (p, result, resultlen);
p += resultlen;
}
do
{
int len = (sw & 0x00ff);
if (DBG_CARD_IO)
log_debug ("apdu_send_direct(%d): %d more bytes available\n",
slot, len);
apdu = short_apdu_buffer;
apdulen = 0;
apdu[apdulen++] = class;
apdu[apdulen++] = 0xC0;
apdu[apdulen++] = 0;
apdu[apdulen++] = 0;
apdu[apdulen++] = len;
memset (apdu+apdulen, 0, sizeof (short_apdu_buffer) - apdulen);
resultlen = result_buffer_size;
rc = send_apdu (slot, apdu, apdulen, result, &resultlen, NULL);
if (rc || resultlen < 2)
{
log_error ("apdu_send_direct(%d) for get response failed: %s\n",
slot, apdu_strerror (rc));
unlock_slot (slot);
xfree (result_buffer);
return rc ? rc : SW_HOST_INCOMPLETE_CARD_RESPONSE;
}
sw = (result[resultlen-2] << 8) | result[resultlen-1];
resultlen -= 2;
if (DBG_CARD_IO)
{
log_debug (" more: sw=%04X datalen=%d\n",
sw, (unsigned int)resultlen);
if (!retbuf && (sw==SW_SUCCESS || (sw&0xff00)==SW_MORE_DATA))
log_printhex (result, resultlen, " dump: ");
}
if ((sw & 0xff00) == SW_MORE_DATA
|| sw == SW_SUCCESS
|| sw == SW_EOF_REACHED )
{
if (retbuf && resultlen)
{
if (p - *retbuf + resultlen > bufsize)
{
bufsize += resultlen > 4096? resultlen: 4096;
tmp = xtryrealloc (*retbuf, bufsize + 2);
if (!tmp)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
p = tmp + (p - *retbuf);
*retbuf = tmp;
}
memcpy (p, result, resultlen);
p += resultlen;
}
}
else
log_info ("apdu_send_direct(%d) "
"got unexpected status %04X from get response\n",
slot, sw);
}
while ((sw & 0xff00) == SW_MORE_DATA);
if (retbuf)
{
*retbuflen = p - *retbuf;
tmp = xtryrealloc (*retbuf, *retbuflen + 2);
if (tmp)
*retbuf = tmp;
}
}
else
{
if (retbuf)
{
*retbuf = xtrymalloc ((resultlen? resultlen : 1)+2);
if (!*retbuf)
{
unlock_slot (slot);
xfree (result_buffer);
return SW_HOST_OUT_OF_CORE;
}
*retbuflen = resultlen;
memcpy (*retbuf, result, resultlen);
}
}
unlock_slot (slot);
xfree (result_buffer);
/* Append the status word. Note that we reserved the two extra
bytes while allocating the buffer. */
if (retbuf)
{
(*retbuf)[(*retbuflen)++] = (sw >> 8);
(*retbuf)[(*retbuflen)++] = sw;
}
if (r_sw)
*r_sw = sw;
if (DBG_CARD_IO && retbuf)
log_printhex (*retbuf, *retbuflen, " dump: ");
return 0;
}
const char *
apdu_get_reader_name (int slot)
{
return reader_table[slot].rdrname;
}
gpg_error_t
apdu_init (void)
{
#ifdef USE_NPTH
gpg_error_t err;
int i;
pcsc.count = 0;
pcsc.context = 0;
for (i = 0; i < MAX_READER; i++)
pcsc.rdrname[i] = NULL;
if (npth_mutex_init (&reader_table_lock, NULL))
goto leave;
for (i = 0; i < MAX_READER; i++)
if (npth_mutex_init (&reader_table[i].lock, NULL))
goto leave;
/* All done well. */
return 0;
leave:
err = gpg_error_from_syserror ();
log_error ("apdu: error initializing mutex: %s\n", gpg_strerror (err));
return err;
#endif /*USE_NPTH*/
return 0;
}
diff --git a/scd/app-nks.c b/scd/app-nks.c
index c207fd500..7c0eedfd0 100644
--- a/scd/app-nks.c
+++ b/scd/app-nks.c
@@ -1,2602 +1,2602 @@
/* app-nks.c - The Telesec NKS card application.
* Copyright (C) 2004, 2007-2009 Free Software Foundation, Inc.
* Copyright (C) 2004, 2007-2009, 2013-2015, 2020, 2022 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Notes:
*
* - We are now targeting TCOS 3 cards and it may happen that there is
* a regression towards TCOS 2 cards. Please report.
*
* - The NKS3 AUT key is not used. It seems that it is only useful for
* the internal authentication command and not accessible by other
* applications. The key itself is in the encryption class but the
* corresponding certificate has only the digitalSignature
* capability.
* Update: This changed for the Signature Card V2 (nks version 15)
*
* - If required, we automagically switch between the NKS application
* and the SigG or eSign application. This avoids to use the DINSIG
* application which is somewhat limited, has no support for Secure
* Messaging as required by TCOS 3 and has no way to change the PIN
* or even set the NullPIN. With the Signature Card v2 (nks version
* 15) the Esign application is used instead of the SigG.
*
* - We use the prefix NKS-DF01 for TCOS 2 cards and NKS-NKS3 for newer
* cards. This is because the NKS application has moved to DF02 with
* TCOS 3 and thus we better use a DF independent tag.
*
* - We use only the global PINs for the NKS application.
*
*
*
* Here is a table with PIN stati collected from 3 cards.
*
* | app | pwid | NKS3 | SIG_B | SIG_N |
* |-----+------+-----------+-----------+-----------|
* | NKS | 0x00 | null - | - - | - - |
* | | 0x01 | 0 3 | - - | - - |
* | | 0x02 | 3 null | 15 3 | 15 null |
* | | 0x03 | - 3 | null - | 3 - |
* | | 0x04 | | null 0 | 3 3 |
* | SIG | 0x00 | null - | - - | - - |
* | | 0x01 | 0 null | - null | - null |
* | | 0x02 | 3 null | 15 0 | 15 0 |
* | | 0x03 | - 0 | null null | null null |
* - SIG is either SIGG or ESIGN.
* - "-" indicates reference not found (SW 6A88).
* - "null" indicates a NULLPIN (SW 6985).
* - The first value in each cell is the global PIN;
* the second is the local PIN (high bit of pwid set).
* - The NKS3 card is some older test card.
* - The SIG_B is a Signature Card V2.0 with Brainpool curves.
* Here the PIN 0x82 has been changed from the NULLPIN.
* - The SIG_N is a Signature Card V2.0 with NIST curves.
* The PIN was enabled using the TCOS Windows tool.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/i18n.h"
#include "iso7816.h"
#include "../common/tlv.h"
#include "apdu.h"
#include "../common/host2net.h"
static char const aid_nks[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x01, 0x02 };
static char const aid_sigg[] = { 0xD2, 0x76, 0x00, 0x00, 0x66, 0x01 };
static char const aid_esign[] =
{ 0xA0, 0x00, 0x00, 0x01, 0x67, 0x45, 0x53, 0x49, 0x47, 0x4E };
static char const aid_idlm[] = { 0xD2, 0x76, 0x00, 0x00, 0x03, 0x0c, 0x01 };
/* The ids of the different apps on our TCOS cards. */
#define NKS_APP_NKS 0
#define NKS_APP_SIGG 1
#define NKS_APP_ESIGN 2
#define NKS_APP_IDLM 3
static struct
{
int nks_app_id;/* One of NKS_APP_*. Keep them sorted so that no
* unnecessary application switching is needed. */
int fid; /* File ID. */
int nks_ver; /* 0 for NKS version 2, 3 for version 3, etc. */
int certtype; /* Type of certificate or 0 if it is not a certificate. */
int iskeypair; /* If true has the FID of the corresponding certificate.
* If no certificate is known a value of -1 is used. */
int isauthkey; /* True if file is a key usable for authentication. */
int issignkey; /* True if file is a key usable for signing. */
int isencrkey; /* True if file is a key usable for decryption. */
unsigned char kid; /* Corresponding key references. */
} filelist[] = {
{ 0, 0x4531, 0, 0, 0xC000, 1,1,0, 0x80}, /* EF_PK.NKS.SIG */
/* */ /* nks15: EF.PK.NKS.ADS */
{ 0, 0xC000, 0, 101 }, /* EF_C.NKS.SIG */
/* */ /* nks15: EF.C.ICC.ADS (sign key) */
{ 0, 0x4331, 0, 100 }, /* Unnamed. */
/* */ /* nks15: EF.C.ICC.RFU1 */
/* */ /* (second cert for sign key) */
{ 0, 0x4332, 0, 100 },
{ 0, 0xB000, 0, 110 }, /* EF_PK.RCA.NKS */
{ 0, 0x45B1, 0, 0, 0xC200, 0,0,1, 0x81}, /* EF_PK.NKS.ENC */
/* */ /* nks15: EF.PK.ICC.ENC1 */
{ 0, 0xC200, 0, 101 }, /* EF_C.NKS.ENC */
/* nks15: EF.C.ICC.ENC1 (Cert-encr) */
{ 0, 0x43B1, 0, 100 }, /* Unnamed */
/* */ /* nks15: EF.C.ICC.RFU2 */
/* */ /* (second cert for enc1 key) */
{ 0, 0x43B2, 0, 100 },
{ 0, 0x4371,15, 100 }, /* EF.C.ICC.RFU3 */
/* */ /* (second cert for auth key) */
{ 0, 0x45B2, 3, 0, 0xC201, 0,0,1, 0x83}, /* EF_PK.NKS.ENC1024 */
/* */ /* nks15: EF.PK.ICC.ENC2 */
{ 0, 0xC201, 3, 101 }, /* EF_C.NKS.ENC1024 */
{ 0, 0xC20E,15, 111 }, /* EF.C.CSP.RCA1 (RootCA 1) */
{ 0, 0xC208,15, 101 }, /* EF.C.CSP.SCA1 (SubCA 1) */
{ 0, 0xC10E,15, 111 }, /* EF.C.CSP.RCA2 (RootCA 2) */
{ 0, 0xC108,15, 101 }, /* EF.C.CSP.SCA2 (SubCA 2) */
{ 0, 0x4571,15, 0, 0xC500, 1,0,0, 0x82}, /* EF.PK.ICC.AUT */
{ 0, 0xC500,15, 101 }, /* EF.C.ICC.AUT (Cert-auth) */
{ 0, 0xC201,15, 101 }, /* EF.C.ICC.ENC2 (Cert-encr) */
/* (empty on delivery) */
{ 1, 0x4531, 3, 0, 0xC000, 0,1,1, 0x84}, /* EF_PK.CH.SIG */
{ 1, 0xC000, 0, 101 }, /* EF_C.CH.SIG */
{ 1, 0xC008, 3, 101 }, /* EF_C.CA.SIG */
{ 1, 0xC00E, 3, 111 }, /* EF_C.RCA.SIG */
{ 2, 0x4531, 15, 0, 0xC001, 0,1,0, 0x84}, /* EF_PK.CH.SIG */
{ 2, 0xC000, 15,101 }, /* EF.C.SCA.QES (SubCA) */
{ 2, 0xC001, 15,100 }, /* EF.C.ICC.QES (Cert) */
{ 2, 0xC00E, 15,111 }, /* EF.C.RCA.QES (RootCA */
{ 3, 0x4E03, 3, 0, -1 }, /* EK_PK_03 */
{ 3, 0x4E04, 3, 0, -1 }, /* EK_PK_04 */
{ 3, 0x4E05, 3, 0, -1 }, /* EK_PK_05 */
{ 3, 0x4E06, 3, 0, -1 }, /* EK_PK_06 */
{ 3, 0x4E07, 3, 0, -1 }, /* EK_PK_07 */
{ 3, 0x4E08, 3, 0, -1 }, /* EK_PK_08 */
{ 0, 0 }
};
/* Object to cache information gathered from FIDs. */
struct fid_cache_s {
struct fid_cache_s *next;
int nks_app_id;
int fid; /* Zero for an unused slot. */
unsigned int got_keygrip:1; /* The keygrip and algo are valid. */
int algo;
char *algostr; /* malloced. */
char keygripstr[2*KEYGRIP_LEN+1];
};
/* Object with application (i.e. NKS) specific data. */
struct app_local_s {
int active_nks_app; /* One of the NKS_APP_ constants. */
int only_idlm; /* The application is fixed to IDLM (IDKey card). */
int qes_app_id; /* Either NKS_APP_SIGG or NKS_APP_ESIGN. */
int sigg_msig_checked;/* True if we checked for a mass signature card. */
int sigg_is_msig; /* True if this is a mass signature card. */
int need_app_select; /* Need to re-select the application. */
struct fid_cache_s *fid_cache; /* Linked list with cached infos. */
};
static gpg_error_t readcert_from_ef (app_t app, int fid,
unsigned char **cert, size_t *certlen);
static gpg_error_t switch_application (app_t app, int nks_app_id);
static const char *parse_pwidstr (app_t app, const char *pwidstr, int new_mode,
int *r_nks_app_id, int *r_pwid);
static gpg_error_t verify_pin (app_t app, int pwid, const char *desc,
gpg_error_t (*pincb)(void*, const char *,
char **),
void *pincb_arg);
static gpg_error_t parse_keyref (app_t app, const char *keyref,
int want_keypair, int *r_fididx);
static void
flush_fid_cache (app_t app)
{
while (app->app_local->fid_cache)
{
struct fid_cache_s *next = app->app_local->fid_cache->next;
if (app->app_local->fid_cache)
xfree (app->app_local->fid_cache->algostr);
xfree (app->app_local->fid_cache);
app->app_local->fid_cache = next;
}
}
/* Release local data. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
flush_fid_cache (app);
xfree (app->app_local);
app->app_local = NULL;
}
}
static int
all_zero_p (void *buffer, size_t length)
{
char *p;
for (p=buffer; length; length--, p++)
if (*p)
return 0;
return 1;
}
/* Return an allocated string with the serial number in a format to be
* show to the user. May return NULL on malloc problem. */
static char *
get_dispserialno (app_t app)
{
char *result;
/* We only need to strip the last zero which is not printed on the
* card. */
result = app_get_serialno (app);
if (result && *result && result[strlen(result)-1] == '0')
result[strlen(result)-1] = 0;
return result;
}
static gpg_error_t
pubkey_from_pk_file (app_t app, int pkfid, int cfid,
unsigned char **r_pk, size_t *r_pklen)
{
gpg_error_t err;
unsigned char *buffer[2];
size_t buflen[2];
int i;
int offset[2] = { 0, 0 };
*r_pk = NULL;
*r_pklen = 0;
if (app->appversion == 15)
{
/* Signature Card v2 - get keygrip from the certificate. */
unsigned char *cert;
size_t certlen;
if (cfid == -1)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* Fall back to certificate reading. */
err = readcert_from_ef (app, cfid, &cert, &certlen);
if (err)
{
log_error ("nks: error reading certificate %04X: %s\n",
cfid, gpg_strerror (err));
return err;
}
err = app_help_pubkey_from_cert (cert, certlen, r_pk, r_pklen);
xfree (cert);
if (err)
log_error ("nks: error parsing certificate %04X: %s\n",
cfid, gpg_strerror (err));
return err;
}
err = iso7816_select_file (app_get_slot (app), pkfid, 0);
if (err)
return err;
err = iso7816_read_record (app_get_slot (app), 1, 1, 0,
&buffer[0], &buflen[0]);
if (err)
return err;
err = iso7816_read_record (app_get_slot (app), 2, 1, 0,
&buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
return err;
}
if (app->appversion < 3)
{
/* Old versions of NKS store the values in a TLV encoded format.
We need to do some checks. */
for (i=0; i < 2; i++)
{
/* Check that the value appears like an integer encoded as
Simple-TLV. We don't check the tag because the tests cards I
have use 1 for both, the modulus and the exponent - the
example in the documentation gives 2 for the exponent. */
if (buflen[i] < 3)
err = gpg_error (GPG_ERR_TOO_SHORT);
else if (buffer[i][1] != buflen[i]-2 )
err = gpg_error (GPG_ERR_INV_OBJ);
else
offset[i] = 2;
if (err)
{
xfree (buffer[0]);
xfree (buffer[1]);
return err;
}
}
}
else
{
/* Remove leading zeroes to get a correct keygrip. Take care of
negative numbers. We should also fix it the same way in
libgcrypt but we can't yet rely on it yet. */
for (i=0; i < 2; i++)
{
while (buflen[i]-offset[i] > 1
&& !buffer[i][offset[i]]
&& !(buffer[i][offset[i]+1] & 0x80))
offset[i]++;
}
}
/* Check whether negative values are not prefixed with a zero and
fix that. */
for (i=0; i < 2; i++)
{
if ((buflen[i]-offset[i]) && (buffer[i][offset[i]] & 0x80))
{
unsigned char *newbuf;
size_t newlen;
newlen = 1 + buflen[i] - offset[i];
newbuf = xtrymalloc (newlen);
if (!newbuf)
{
err = gpg_error_from_syserror ();
xfree (buffer[0]);
xfree (buffer[1]);
return err;
}
newbuf[0] = 0;
memcpy (newbuf+1, buffer[i]+offset[i], buflen[i] - offset[i]);
xfree (buffer[i]);
buffer[i] = newbuf;
buflen[i] = newlen;
offset[i] = 0;
}
}
*r_pk = make_canon_sexp_from_rsa_pk (buffer[0]+offset[0], buflen[0]-offset[0],
buffer[1]+offset[1], buflen[1]-offset[1],
r_pklen);
xfree (buffer[0]);
xfree (buffer[1]);
return err;
}
/* Read the file with PKFID, assume it contains a public key and
* return its keygrip in the caller provided 41 byte buffer R_GRIPSTR.
* This works only for RSA card. For the Signature Card v2 ECC is
* used and Read Record needs to be replaced by read binary. Given
* all the ECC parameters required, we don't do that but rely that the
* corresponding certificate at CFID is already available and get the
* public key from there. Note that a CFID of 1 is indicates that a
* certificate is not known. If R_ALGO is not NULL the public key
* algorithm for the returned KEYGRIP is stored there. If R_ALGOSTR
* is not NULL the public key algo string (e.g. "rsa2048") is stored
* there. */
static gpg_error_t
keygripstr_from_pk_file (app_t app, int pkfid, int cfid, char *r_gripstr,
int *r_algo, char **r_algostr)
{
gpg_error_t err;
int algo = 0; /* Public key algo. */
char *algostr = NULL; /* Public key algo string. */
struct fid_cache_s *ci;
unsigned char *pk;
size_t pklen;
for (ci = app->app_local->fid_cache; ci; ci = ci->next)
if (ci->fid && ci->nks_app_id == app->app_local->active_nks_app
&& ci->fid == pkfid)
{
if (!ci->got_keygrip)
return gpg_error (GPG_ERR_NOT_FOUND);
if (r_algostr && !ci->algostr)
break; /* Not in the cache - try w/o cache. */
memcpy (r_gripstr, ci->keygripstr, 2*KEYGRIP_LEN+1);
if (r_algo)
*r_algo = ci->algo;
if (r_algostr)
{
*r_algostr = xtrystrdup (ci->algostr);
if (!*r_algostr)
return gpg_error_from_syserror ();
}
return 0; /* Found in cache. */
}
err = pubkey_from_pk_file (app, pkfid, cfid, &pk, &pklen);
if (!err)
err = app_help_get_keygrip_string_pk (pk, pklen, r_gripstr, NULL,
&algo, &algostr);
xfree (pk);
if (!err)
{
if (r_algo)
*r_algo = algo;
if (r_algostr)
{
*r_algostr = algostr;
algostr = NULL;
}
/* FIXME: We need to implement not_found caching. */
for (ci = app->app_local->fid_cache; ci; ci = ci->next)
if (ci->fid
&& ci->nks_app_id == app->app_local->active_nks_app
&& ci->fid == pkfid)
{
/* Update the keygrip. */
memcpy (ci->keygripstr, r_gripstr, 2*KEYGRIP_LEN+1);
ci->algo = algo;
xfree (ci->algostr);
ci->algostr = algostr? xtrystrdup (algostr) : NULL;
ci->got_keygrip = 1;
break;
}
if (!ci)
{
for (ci = app->app_local->fid_cache; ci; ci = ci->next)
if (!ci->fid)
break;
if (!ci)
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
; /* Out of memory - it is a cache, so we ignore it. */
else
{
ci->nks_app_id = app->app_local->active_nks_app;
ci->fid = pkfid;
memcpy (ci->keygripstr, r_gripstr, 2*KEYGRIP_LEN+1);
ci->algo = algo;
ci->got_keygrip = 1;
ci->next = app->app_local->fid_cache;
app->app_local->fid_cache = ci;
}
}
}
xfree (algostr);
return err;
}
/* Parse KEYREF and return the index into the FILELIST at R_IDX.
* Returns 0 on success and switches to the requested application.
* The public key algo is stored at R_ALGO unless it is NULL. */
static gpg_error_t
find_fid_by_keyref (app_t app, const char *keyref, int *r_idx, int *r_algo)
{
gpg_error_t err;
int idx;
char keygripstr[2*KEYGRIP_LEN+1];
if (!keyref || !keyref[0])
err = gpg_error (GPG_ERR_INV_ID);
else if (keyref[0] != 'N' && strlen (keyref) == 40) /* This is a keygrip. */
{
struct fid_cache_s *ci;
/* FIXME: Our cache structure needs to be revised. It doesn't
* take the app_id into account and we don't have a way to
* directly access the FID item if there are several of them
* with different app_ids. We disable the cache for now. */
for (ci = app->app_local->fid_cache ; ci; ci = ci->next)
if (ci->fid && ci->got_keygrip && !strcmp (ci->keygripstr, keyref))
break;
if (ci && 0 ) /* Cached (disabled) */
{
for (idx=0; filelist[idx].fid; idx++)
if (filelist[idx].fid == ci->fid)
break;
if (!filelist[idx].fid)
{
- log_debug ("nks: Ooops: Unkown FID cached!\n");
+ log_debug ("nks: Ooops: Unknown FID cached!\n");
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
err = switch_application (app, filelist[idx].nks_app_id);
if (err)
goto leave;
if (r_algo)
*r_algo = ci->algo;
}
else /* Not cached. */
{
for (idx=0; filelist[idx].fid; idx++)
{
if (!filelist[idx].iskeypair)
continue;
if (app->app_local->only_idlm)
{
if (filelist[idx].nks_app_id != NKS_APP_IDLM)
continue;
}
else
{
if (filelist[idx].nks_app_id != NKS_APP_NKS
&& filelist[idx].nks_app_id != app->app_local->qes_app_id)
continue;
err = switch_application (app, filelist[idx].nks_app_id);
if (err)
goto leave;
}
err = keygripstr_from_pk_file (app, filelist[idx].fid,
filelist[idx].iskeypair,
keygripstr, r_algo, NULL);
if (err)
{
log_info ("nks: no keygrip for FID 0x%04X: %s - ignored\n",
filelist[idx].fid, gpg_strerror (err));
continue;
}
if (!strcmp (keygripstr, keyref))
break; /* Found */
}
if (!filelist[idx].fid)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* (No need to switch the app as that has already been done
* in the loop.) */
}
*r_idx = idx;
err = 0;
}
else /* This is a usual keyref. */
{
err = parse_keyref (app, keyref, 1, &idx);
if (err)
goto leave;
*r_idx = idx;
err = switch_application (app, filelist[idx].nks_app_id);
if (err)
goto leave;
if (r_algo)
{
/* We need to get the public key algo. */
err = keygripstr_from_pk_file (app, filelist[idx].fid,
filelist[idx].iskeypair,
keygripstr, r_algo, NULL);
if (err)
log_error ("nks: no keygrip for FID 0x%04X: %s\n",
filelist[idx].fid, gpg_strerror (err));
}
}
leave:
return err;
}
/* TCOS responds to a verify with empty data (i.e. without the Lc
* byte) with the status of the PIN. PWID is the PIN ID. NKS_APP_ID
* gives the application to first switch to. Returns:
* ISO7816_VERIFY_* codes or non-negative number of verification
* attempts left. */
static int
get_chv_status (app_t app, int nks_app_id, int pwid)
{
if (switch_application (app, nks_app_id))
return (nks_app_id == NKS_APP_NKS
? ISO7816_VERIFY_ERROR
: ISO7816_VERIFY_NO_PIN);
return iso7816_verify_status (app_get_slot (app), pwid);
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int special;
} table[] = {
{ "$AUTHKEYID", 1 },
{ "$ENCRKEYID", 2 },
{ "$SIGNKEYID", 3 },
{ "NKS-VERSION", 4 }, /* Legacy (printed decimal) */
{ "CHV-STATUS", 5 },
{ "$DISPSERIALNO",6 },
{ "SERIALNO", 0 }
};
gpg_error_t err = 0;
int idx;
char *p, *p2;
char buffer[100];
int nksver = app->appversion;
err = switch_application (app, NKS_APP_NKS);
if (err)
return err;
for (idx=0; (idx < DIM(table)
&& ascii_strcasecmp (table[idx].name, name)); idx++)
;
if (!(idx < DIM (table)))
return gpg_error (GPG_ERR_INV_NAME);
switch (table[idx].special)
{
case 0: /* SERIALNO */
{
p = app_get_serialno (app);
if (p)
{
send_status_direct (ctrl, "SERIALNO", p);
xfree (p);
}
}
break;
case 1: /* $AUTHKEYID */
{
/* NetKey 3.0 cards define an authentication key but according
to the specs this key is only usable for encryption and not
signing. it might work anyway but it has not yet been
tested - fixme. Thus for now we use the NKS signature key
for authentication for netkey 3. For the Signature Card
V2.0 the auth key is defined and thus we use it. */
const char *tmp = nksver == 15? "NKS-NKS3.4571" : "NKS-NKS3.4531";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 2: /* $ENCRKEYID */
{
char const tmp[] = "NKS-NKS3.45B1";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 3: /* $SIGNKEYID */
{
char const tmp[] = "NKS-NKS3.4531";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
break;
case 4: /* NKS-VERSION */
snprintf (buffer, sizeof buffer, "%d", app->appversion);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
break;
case 5: /* CHV-STATUS */
{
/* Return the status for the the PINs as described in the
* table below. See the macros ISO7816_VERIFY_* for a list
* for each slot. The order is
*
* | idx | name |
* |-----+------------|
* | 0 | PW1.CH |
* | 1 | PW2.CH |
* | 2 | PW1.CH.SIG |
* | 3 | PW2.CH.SIG |
*
* See parse_pwidstr for details of the mapping.
*/
int tmp[4];
/* We use a helper array so that we can control that there is
* no superfluous application switches. */
if (app->appversion == 15)
{
tmp[0] = get_chv_status (app, 0, 0x03);
tmp[1] = get_chv_status (app, 0, 0x04);
}
else
{
tmp[0] = get_chv_status (app, 0, 0x00);
tmp[1] = get_chv_status (app, 0, 0x01);
}
tmp[2] = get_chv_status (app, app->app_local->qes_app_id, 0x81);
if (app->appversion == 15)
tmp[3] = get_chv_status (app, app->app_local->qes_app_id, 0x82);
else
tmp[3] = get_chv_status (app, app->app_local->qes_app_id, 0x83);
snprintf (buffer, sizeof buffer, "%d %d %d %d",
tmp[0], tmp[1], tmp[2], tmp[3]);
send_status_info (ctrl, table[idx].name,
buffer, strlen (buffer), NULL, 0);
}
break;
case 6: /* $DISPSERIALNO */
{
p = app_get_serialno (app);
p2 = get_dispserialno (app);
if (p && p2 && strcmp (p, p2))
send_status_info (ctrl, table[idx].name, p2, strlen (p2),
NULL, (size_t)0);
else /* No abbreviated S/N or identical to the full full S/N. */
err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */
xfree (p);
xfree (p2);
}
break;
default:
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
break;
}
return err;
}
/* Parse a keyref (NKS_*.*) and return the corresponding EF as an
* index into the filetable. With WANT_KEYPAIR set a keypair EF is
* requested; otherwise also cert EFs are returned. */
static gpg_error_t
parse_keyref (app_t app, const char *keyref, int want_keypair, int *r_fididx)
{
int nks_app_id, fid, idx;
if (!ascii_strncasecmp (keyref, "NKS-NKS3.", 9))
nks_app_id = NKS_APP_NKS;
else if (!ascii_strncasecmp (keyref, "NKS-ESIGN.", 10)
&& (!want_keypair || app->app_local->qes_app_id == NKS_APP_ESIGN))
nks_app_id = NKS_APP_ESIGN;
else if (!ascii_strncasecmp (keyref, "NKS-SIGG.", 9)
&& (!want_keypair || app->app_local->qes_app_id == NKS_APP_SIGG))
nks_app_id = NKS_APP_SIGG;
else if (!ascii_strncasecmp (keyref, "NKS-IDLM.", 9))
nks_app_id = NKS_APP_IDLM;
else if (!ascii_strncasecmp (keyref, "NKS-DF01.", 9))
nks_app_id = NKS_APP_NKS;
else
return gpg_error (GPG_ERR_INV_ID);
keyref += nks_app_id == NKS_APP_ESIGN? 10 : 9;
if (!hexdigitp (keyref) || !hexdigitp (keyref+1)
|| !hexdigitp (keyref+2) || !hexdigitp (keyref+3)
|| keyref[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (keyref);
for (idx=0; filelist[idx].fid; idx++)
if (filelist[idx].fid == fid
&& filelist[idx].nks_app_id == nks_app_id
&& ((want_keypair && filelist[idx].iskeypair)
|| (!want_keypair
&& (filelist[idx].certtype || filelist[idx].iskeypair > 0))))
break;
if (!filelist[idx].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_fididx = idx;
return 0;
}
const char *
get_nks_tag (app_t app, int nks_app_id)
{
const char *tag;
if (nks_app_id == NKS_APP_ESIGN)
tag = "ESIGN";
else if (nks_app_id == NKS_APP_SIGG)
tag = "SIGG";
else if (nks_app_id == NKS_APP_IDLM)
tag = "IDLM";
else if (app->appversion < 3)
tag = "DF01";
else
tag = "NKS3";
return tag;
}
static void
set_usage_string (char usagebuf[5], int i)
{
int usageidx = 0;
if (filelist[i].issignkey)
usagebuf[usageidx++] = 's';
if (filelist[i].isauthkey)
usagebuf[usageidx++] = 'a';
if (filelist[i].isencrkey)
usagebuf[usageidx++] = 'e';
if (!usageidx)
usagebuf[usageidx++] = '-';
usagebuf[usageidx] = 0;
}
static void
do_learn_status_core (app_t app, ctrl_t ctrl, unsigned int flags,
int nks_app_id)
{
gpg_error_t err;
char ct_buf[100], id_buf[100];
int i;
const char *tag = get_nks_tag (app, nks_app_id);
/* Output information about all useful objects in the NKS application. */
for (i=0; filelist[i].fid; i++)
{
if (filelist[i].nks_ver > app->appversion)
continue;
if (filelist[i].nks_app_id != nks_app_id)
continue;
if (filelist[i].certtype && !(flags & APP_LEARN_FLAG_KEYPAIRINFO))
{
size_t len;
len = app_help_read_length_of_cert (app_get_slot (app),
filelist[i].fid, NULL);
if (len)
{
/* FIXME: We should store the length in the application's
context so that a following readcert does only need to
read that many bytes. */
snprintf (ct_buf, sizeof ct_buf, "%d", filelist[i].certtype);
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
send_status_info (ctrl, "CERTINFO",
ct_buf, strlen (ct_buf),
id_buf, strlen (id_buf),
NULL, (size_t)0);
}
}
else if (filelist[i].iskeypair)
{
char gripstr[40+1];
char usagebuf[5];
char *algostr = NULL;
err = keygripstr_from_pk_file (app, filelist[i].fid,
filelist[i].iskeypair, gripstr,
NULL, &algostr);
if (err)
log_error ("can't get keygrip from FID 0x%04X: %s\n",
filelist[i].fid, gpg_strerror (err));
else
{
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
tag, filelist[i].fid);
set_usage_string (usagebuf, i);
send_status_info (ctrl, "KEYPAIRINFO",
gripstr, 40,
id_buf, strlen (id_buf),
usagebuf, strlen (usagebuf),
"-", (size_t)1,
algostr, strlen (algostr),
NULL, (size_t)0);
}
xfree (algostr);
}
}
}
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
do_getattr (app, ctrl, "CHV-STATUS");
err = switch_application (app, NKS_APP_NKS);
if (err)
return err;
do_learn_status_core (app, ctrl, flags, app->app_local->active_nks_app);
if (app->app_local->only_idlm)
return 0; /* ready. */
err = switch_application (app, app->app_local->qes_app_id);
if (err)
return 0; /* Silently ignore if we can't switch to SigG. */
do_learn_status_core (app, ctrl, flags, app->app_local->qes_app_id);
return 0;
}
/* Helper to read a certificate from the file FID. The function
* assumes that the application has already been selected. */
static gpg_error_t
readcert_from_ef (app_t app, int fid, unsigned char **cert, size_t *certlen)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca = 0;
*cert = NULL;
*certlen = 0;
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = iso7816_select_file (app_get_slot (app), fid, 0);
if (err)
{
log_error ("nks: error selecting FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
err = iso7816_read_binary (app_get_slot (app), 0, 0, &buffer, &buflen);
if (err)
{
log_error ("nks: error reading certificate from FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
if (!buflen || *buffer == 0xff || all_zero_p (buffer, buflen))
{
log_info ("nks: no certificate contained in FID 0x%04X\n", fid);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Now figure something out about the object. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed )
;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
log_assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (rootca)
;
else if (class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
const unsigned char *save_p;
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
return gpg_error (GPG_ERR_INV_OBJ);
totobjlen = objlen + hdrlen;
log_assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
*cert = buffer;
buffer = NULL;
*certlen = totobjlen;
leave:
xfree (buffer);
return err;
}
/*
* Iterate over FILELIST, supporting two use cases:
*
* (1) With WANT_KEYGRIPSTR=<GRIP>, finding matching entry.
* (2) With WANT_KEYGRIPSTR=NULL, listing entries
* by CAPABILITY (possibly == 0, for all entries).
*
* Caller supplies an array KEYGRIPSTR.
* Caller should start *IDX_P == -1, and keep the index value in IDX_P.
*
* Returns 0 on success, otherwise returns error value.
*
* When all entries are tried, returns GPG_ERR_NOT_FOUND for the use
* case of (1). Returns GPG_ERR_TRUE for the use case of (2).
*/
static gpg_error_t
iterate_over_filelist (app_t app, const char *want_keygripstr, int capability,
char keygripstr[2*KEYGRIP_LEN+1], int *idx_p)
{
gpg_error_t err;
int idx = *idx_p;
for (idx++; filelist[idx].fid; idx++)
{
if (filelist[idx].nks_ver > app->appversion)
continue; /* EF not support by this card version. */
if (!filelist[idx].iskeypair)
continue; /* Skip - We are only interested in keypairs. */
if (app->app_local->only_idlm)
{
/* IDLM cards have no other applications we want to switch
* to. We skip all EFs which are not known for IDLM. */
if (filelist[idx].nks_app_id != NKS_APP_IDLM)
continue;
}
else
{
/* Skip all EFs which are not for NKS or the card's
* implementation for a qualified electoric signature (QES)
* which is either the old SIGG or the newer ESIGN. */
if (filelist[idx].nks_app_id != NKS_APP_NKS
&& filelist[idx].nks_app_id != app->app_local->qes_app_id)
continue;
/* Switch if needed. Note that the filelist should be
* sorted to avoid unnecessary switches. */
err = switch_application (app, filelist[idx].nks_app_id);
if (err)
{
*idx_p = idx;
return err;
}
}
/* Get the keygrip from the EF. Note that this functions
* consults the cache to avoid computing the keygrip again. */
err = keygripstr_from_pk_file (app, filelist[idx].fid,
filelist[idx].iskeypair, keygripstr,
NULL, NULL);
if (err)
{
log_error ("can't get keygrip from FID 0x%04X: %s\n",
filelist[idx].fid, gpg_strerror (err));
continue;
}
if (want_keygripstr)
{
/* If the keygrip matches the requested one we are ready. */
if (!strcmp (keygripstr, want_keygripstr))
{
/* Found */
*idx_p = idx;
return 0;
}
}
else /* No keygrip requested - list all . */
{
/* If a capability has been requested return only keys with
* that capability. */
if (capability == GCRY_PK_USAGE_SIGN)
{
if (!filelist[idx].issignkey)
continue;
}
if (capability == GCRY_PK_USAGE_ENCR)
{
if (!filelist[idx].isencrkey)
continue;
}
if (capability == GCRY_PK_USAGE_AUTH)
{
if (!filelist[idx].isauthkey)
continue;
}
/* Found. Return but save the last idenx of the loop. */
*idx_p = idx;
return 0;
}
}
if (!want_keygripstr)
err = gpg_error (GPG_ERR_TRUE);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
return err;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
int idx, fid;
gpg_error_t err;
*cert = NULL;
*certlen = 0;
/* Handle the case with KEYGRIP. We got a keygrip if the string has
* a length of 40 and does not start with an N as in NKS-* */
if (certid[0] != 'N' && strlen (certid) == 40)
{
char keygripstr[2*KEYGRIP_LEN+1];
idx = -1;
err = iterate_over_filelist (app, certid, 0, keygripstr, &idx);
if (err)
return err;
/* Switching is not required here because iterate_over_filelist
* has already done that. */
}
else /* This is not a keygrip. */
{
err = parse_keyref (app, certid, 0, &idx);
if (err)
return err;
err = switch_application (app, filelist[idx].nks_app_id);
if (err)
return err;
}
/* If the requested objects is a plain public key, redirect it to
the corresponding certificate. The whole system is a bit messy
because we sometime use the key directly or let the caller
retrieve the key from the certificate. The rationale for
that is to support not-yet stored certificates. */
if (filelist[idx].iskeypair > 0)
fid = filelist[idx].iskeypair;
else
fid = filelist[idx].fid;
return readcert_from_ef (app, fid, cert, certlen);
}
/* Handle the READKEY command. On success a canonical encoded
S-expression with the public key will get stored at PK and its
length at PKLEN; the caller must release that buffer. On error PK
and PKLEN are not changed and an error code is returned. As of now
this function is only useful for the internal authentication key.
Other keys are automagically retrieved by means of the
certificate parsing code in commands.c:cmd_readkey. For internal
use PK and PKLEN may be NULL to just check for an existing key. */
static gpg_error_t
do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
unsigned char *dummy_pk = NULL;
size_t dummy_pklen = 0;
if (!pk)
pk = &dummy_pk;
if (!pklen)
pklen = &dummy_pklen;
/* We use a generic name to retrieve PK.AUT.IFD-SPK. */
if (!strcmp (keyid, "$IFDAUTHKEY") && app->appversion >= 3)
{
unsigned short path[1] = { 0x4500 };
unsigned char *buffer[2];
size_t buflen[2];
/* Access the KEYD file which is always in the master directory. */
err = iso7816_select_path (app_get_slot (app), path, DIM (path), 0);
if (err)
goto leave;
/* Due to the above select we need to re-select our application. */
app->app_local->need_app_select = 1;
/* Get the two records. */
err = iso7816_read_record (app_get_slot (app), 5, 1, 0,
&buffer[0], &buflen[0]);
if (err)
goto leave;
if (all_zero_p (buffer[0], buflen[0]))
{
xfree (buffer[0]);
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
err = iso7816_read_record (app_get_slot (app), 6, 1, 0,
&buffer[1], &buflen[1]);
if (err)
{
xfree (buffer[0]);
goto leave;
}
if ((flags & APP_READKEY_FLAG_INFO))
{
/* FIXME */
}
if (pk && pklen && pk != &dummy_pk)
{
*pk = make_canon_sexp_from_rsa_pk (buffer[0], buflen[0],
buffer[1], buflen[1],
pklen);
if (!*pk)
err = gpg_error_from_syserror ();
}
xfree (buffer[0]);
xfree (buffer[1]);
}
else if (keyid[0] != 'N' && strlen (keyid) == 40)
{
char keygripstr[2*KEYGRIP_LEN+1];
int i = -1;
err = iterate_over_filelist (app, keyid, 0, keygripstr, &i);
if (err)
goto leave;
err = pubkey_from_pk_file (app, filelist[i].fid, filelist[i].iskeypair,
pk, pklen);
if (!err && (flags & APP_READKEY_FLAG_INFO))
{
char *algostr;
char usagebuf[5];
char id_buf[100];
if (app_help_get_keygrip_string_pk (*pk, *pklen, NULL, NULL, NULL,
&algostr))
algostr = NULL; /* Ooops. */
snprintf (id_buf, sizeof id_buf, "NKS-%s.%04X",
get_nks_tag (app, filelist[i].nks_app_id),
filelist[i].fid);
set_usage_string (usagebuf, i);
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
id_buf, strlen (id_buf),
usagebuf, strlen (usagebuf),
"-", (size_t)1,
algostr, strlen (algostr?algostr:""),
NULL, (size_t)0);
xfree (algostr);
}
}
else if (!strncmp (keyid, "NKS-IDLM.", 9))
{
keyid += 9;
if (!hexdigitp (keyid) || !hexdigitp (keyid+1)
|| !hexdigitp (keyid+2) || !hexdigitp (keyid+3)
|| keyid[4])
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = pubkey_from_pk_file (app, xtoi_4 (keyid), -1, pk, pklen);
/* FIXME: Implement KEYPAIRINFO. */
}
else /* Return the error code expected by cmd_readkey. */
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
leave:
xfree (dummy_pk);
return err;
}
/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR.
* CERTREFSTR is of the form "NKS_<yyy>.<four_hexdigit_keyref>". */
static gpg_error_t
do_writecert (app_t app, ctrl_t ctrl,
const char *certid,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *cert, size_t certlen)
{
gpg_error_t err;
int i, fid, pwid;
int nks_app_id, tmp_app_id;
const char *desc;
(void)ctrl;
if (!strncmp (certid, "NKS-NKS3.", 9))
nks_app_id = NKS_APP_NKS;
else if (!strncmp (certid, "NKS-ESIGN.", 10))
nks_app_id = NKS_APP_ESIGN;
else if (!strncmp (certid, "NKS-SIGG.", 9))
nks_app_id = NKS_APP_SIGG;
else if (!strncmp (certid, "NKS-DF01.", 9))
nks_app_id = NKS_APP_NKS;
else if (!strncmp (certid, "NKS-IDLM.", 9))
nks_app_id = NKS_APP_IDLM;
else
return gpg_error (GPG_ERR_INV_ID);
certid += nks_app_id == NKS_APP_ESIGN? 10 : 9;
err = switch_application (app, nks_app_id);
if (err)
return err;
if (!hexdigitp (certid) || !hexdigitp (certid+1)
|| !hexdigitp (certid+2) || !hexdigitp (certid+3)
|| certid[4])
return gpg_error (GPG_ERR_INV_ID);
fid = xtoi_4 (certid);
for (i=0; filelist[i].fid; i++)
if ((filelist[i].certtype || filelist[i].iskeypair > 0)
&& filelist[i].nks_app_id == nks_app_id
&& filelist[i].fid == fid)
break;
if (!filelist[i].fid)
return gpg_error (GPG_ERR_NOT_FOUND);
/* If the requested objects is a plain public key, redirect it to
* the corresponding certificate. This makes it easier for the user
* to figure out which CERTID to use. For example gpg-card shows
* the id of the key and not of the certificate. */
if (filelist[i].iskeypair > 0)
fid = filelist[i].iskeypair;
/* We have no selective flush mechanism and given the rare use of
* writecert it won't harm to flush the entire cache. */
flush_fid_cache (app);
/* The certificates we support all require PW1.CH. Note that we
* check that the nks_app_id matches which sorts out CERTID values
* which are subkeys to a different nks_app_id. */
desc = parse_pwidstr (app, "PW1.CH", 0, &tmp_app_id, &pwid);
if (!desc || tmp_app_id != nks_app_id)
return gpg_error (GPG_ERR_INV_ID);
err = verify_pin (app, pwid, desc, pincb, pincb_arg);
if (err)
return err;
/* Select the file and write the certificate. */
err = iso7816_select_file (app_get_slot (app), fid, 0);
if (err)
{
log_error ("nks: error selecting FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
err = iso7816_update_binary (app_get_slot (app), 1, 0, cert, certlen);
if (err)
{
log_error ("nks: error updating certificate at FID 0x%04X: %s\n",
fid, gpg_strerror (err));
return err;
}
return 0;
}
/* Handle the WRITEKEY command for NKS. This function expects a
canonical encoded S-expression with the public key in KEYDATA and
its length in KEYDATALEN. The only supported KEYID is
"$IFDAUTHKEY" to store the terminal key on the card. Bit 0 of
FLAGS indicates whether an existing key shall get overwritten.
PINCB and PINCB_ARG are the usual arguments for the pinentry
callback. */
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = (flags & 1);
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
size_t rsa_n_len, rsa_e_len;
unsigned int nbits;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
if (!strcmp (keyid, "$IFDAUTHKEY") && app->appversion >= 3)
;
else
return gpg_error (GPG_ERR_INV_ID);
if (!force && !do_readkey (app, ctrl, keyid, 0, NULL, NULL))
return gpg_error (GPG_ERR_EEXIST);
/* Parse the S-expression. */
err = get_rsa_pk_from_canon_sexp (keydata, keydatalen,
&rsa_n, &rsa_n_len, &rsa_e, &rsa_e_len);
if (err)
goto leave;
/* Check that the parameters match the requirements. */
nbits = app_help_count_bits (rsa_n, rsa_n_len);
if (nbits != 1024)
{
log_error (_("RSA modulus missing or not of size %d bits\n"), 1024);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
nbits = app_help_count_bits (rsa_e, rsa_e_len);
if (nbits < 2 || nbits > 32)
{
log_error (_("RSA public exponent missing or larger than %d bits\n"),
32);
err = gpg_error (GPG_ERR_BAD_PUBKEY);
goto leave;
}
/* /\* Store them. *\/ */
/* err = verify_pin (app, 0, NULL, pincb, pincb_arg); */
/* if (err) */
/* goto leave; */
/* Send the MSE:Store_Public_Key. */
/* We will need to clear the cache here. */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
/* mse = xtrymalloc (1000); */
/* mse[0] = 0x80; /\* Algorithm reference. *\/ */
/* mse[1] = 1; */
/* mse[2] = 0x17; */
/* mse[3] = 0x84; /\* Private key reference. *\/ */
/* mse[4] = 1; */
/* mse[5] = 0x77; */
/* mse[6] = 0x7F; /\* Public key parameter. *\/ */
/* mse[7] = 0x49; */
/* mse[8] = 0x81; */
/* mse[9] = 3 + 0x80 + 2 + rsa_e_len; */
/* mse[10] = 0x81; /\* RSA modulus of 128 byte. *\/ */
/* mse[11] = 0x81; */
/* mse[12] = rsa_n_len; */
/* memcpy (mse+12, rsa_n, rsa_n_len); */
/* mse[10] = 0x82; /\* RSA public exponent of up to 4 bytes. *\/ */
/* mse[12] = rsa_e_len; */
/* memcpy (mse+12, rsa_e, rsa_e_len); */
/* err = iso7816_manage_security_env (app_get_slot (app), 0x81, 0xB6, */
/* mse, sizeof mse); */
leave:
return err;
}
/* Return an allocated string to be used as prompt. Returns NULL on
* malloc error. */
static char *
make_prompt (app_t app, int remaining, const char *firstline,
const char *extraline)
{
char *serial, *tmpbuf, *result;
serial = get_dispserialno (app);
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
"",
"");
xfree (serial);
if (!result)
return NULL; /* Out of core. */
/* Append a "remaining attempts" info if needed. */
if (remaining != -1 && remaining < 3)
{
char *rembuf;
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (rembuf)
{
tmpbuf = strconcat (firstline, "%0A%0A", result,
"%0A%0A", rembuf, NULL);
xfree (rembuf);
}
else
tmpbuf = NULL;
xfree (result);
result = tmpbuf;
}
else
{
tmpbuf = strconcat (firstline, "%0A%0A", result,
extraline? "%0A%0A":"", extraline,
NULL);
xfree (result);
result = tmpbuf;
}
return result;
}
static gpg_error_t
basic_pin_checks (const char *pinvalue, int minlen, int maxlen)
{
if (strlen (pinvalue) < minlen)
{
log_error ("PIN is too short; minimum length is %d\n", minlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (strlen (pinvalue) > maxlen)
{
log_error ("PIN is too large; maximum length is %d\n", maxlen);
return gpg_error (GPG_ERR_BAD_PIN);
}
return 0;
}
/* Verify the PIN if required. */
static gpg_error_t
verify_pin (app_t app, int pwid, const char *desc,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc;
pininfo_t pininfo;
char *prompt;
const char *extrapromptline = NULL;
int remaining, nullpin;
if (!desc)
desc = "||PIN";
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
/* FIXME: TCOS allows one to read the min. and max. values - do this. */
if (app->appversion == 15)
{
if (app->app_local->active_nks_app == NKS_APP_NKS && pwid == 0x03)
pininfo.minlen = 6;
else if (app->app_local->active_nks_app == NKS_APP_ESIGN && pwid == 0x81)
pininfo.minlen = 6;
else
pininfo.minlen = 8;
pininfo.maxlen = 24;
}
else if (app->app_local->active_nks_app == NKS_APP_IDLM)
{
if (pwid == 0x00)
pininfo.minlen = 6;
else
pininfo.minlen = 8;
pininfo.maxlen = 24;
}
else
{
/* For NKS3 we used these fixed values; let's keep this. */
pininfo.minlen = 6;
pininfo.maxlen = 16;
}
remaining = iso7816_verify_status (app_get_slot (app), pwid);
nullpin = (remaining == ISO7816_VERIFY_NULLPIN);
if (remaining < 0)
remaining = -1; /* We don't care about the concrete error. */
if (remaining < 3)
{
if (remaining >= 0)
log_info ("nks: PIN has %d attempts left\n", remaining);
}
if (nullpin)
{
log_info ("nks: The NullPIN for PIN 0x%02x has not yet been changed\n",
pwid);
extrapromptline = _("Note: PIN has not yet been enabled.");
}
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo) )
{
prompt = make_prompt (app, remaining, desc, extrapromptline);
rc = pincb (pincb_arg, prompt, NULL);
xfree (prompt);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app_get_slot (app), pwid, &pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
char *pinvalue;
prompt = make_prompt (app, remaining, desc, extrapromptline);
rc = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
if (rc)
{
log_info ("PIN callback returned error: %s\n", gpg_strerror (rc));
return rc;
}
rc = basic_pin_checks (pinvalue, pininfo.minlen, pininfo.maxlen);
if (rc)
{
xfree (pinvalue);
return rc;
}
rc = iso7816_verify (app_get_slot (app), pwid,
pinvalue, strlen (pinvalue));
xfree (pinvalue);
}
if (rc)
{
if ( gpg_err_code (rc) == GPG_ERR_USE_CONDITIONS )
log_error (_("the NullPIN has not yet been changed\n"));
else
log_error ("verify PIN failed\n");
return rc;
}
return 0;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that in the 3rd argument. */
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
static unsigned char sha1_prefix[15] = /* Object ID is 1.3.14.3.2.26 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */
{ 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04,
0x1C };
static unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
static unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */
{ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30 };
static unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */
{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40 };
gpg_error_t err;
int idx;
int pwid;
unsigned char kid;
unsigned char data[83]; /* Must be large enough for a SHA-1 digest
+ the largest OID prefix. */
size_t datalen;
int algo;
unsigned int digestlen; /* Length of the hash. */
unsigned char oidbuf[64];
size_t oidbuflen;
(void)ctrl;
switch (indatalen)
{
case 20: /* plain SHA-1 or RMD160 digest */
case 28: /* plain SHA-224 digest */
case 32: /* plain SHA-256 digest */
case 48: /* plain SHA-384 digest */
case 64: /* plain SHA-512 digest */
case 35: /* ASN.1 encoded SHA-1 or RMD160 digest */
case 47: /* ASN.1 encoded SHA-224 digest */
case 51: /* ASN.1 encoded SHA-256 digest */
case 67: /* ASN.1 encoded SHA-384 digest */
case 83: /* ASN.1 encoded SHA-512 digest */
break;
default:
log_info ("nks: invalid length of input data: %zu\n", indatalen);
return gpg_error (GPG_ERR_INV_VALUE);
}
err = find_fid_by_keyref (app, keyidstr, &idx, &algo);
if (err)
return err;
if (app->app_local->active_nks_app == NKS_APP_SIGG
&& app->app_local->sigg_is_msig)
{
log_info ("mass signature cards are not allowed\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!filelist[idx].issignkey)
{
log_debug ("key %s is not a signing key\n", keyidstr);
return gpg_error (GPG_ERR_INV_ID);
}
kid = filelist[idx].kid;
digestlen = gcry_md_get_algo_dlen (hashalgo);
/* Prepare the input object from INDATA. */
if (algo == GCRY_PK_ECC)
{
if (digestlen != 32 && digestlen != 48 && digestlen != 64)
{
log_error ("nks: ECC signing not possible: dlen=%u\n", digestlen);
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
if (indatalen == digestlen)
{
/* Already prepared. */
datalen = indatalen;
log_assert (datalen <= sizeof data);
memcpy (data, indata, datalen);
}
else if (indatalen > digestlen)
{
/* Assume a PKCS#1 prefix and remove it. */
oidbuflen = sizeof oidbuf;
err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen);
if (err)
{
log_error ("nks: no OID for hash algo %d\n", hashalgo);
return gpg_error (GPG_ERR_INTERNAL);
}
if (indatalen != oidbuflen + digestlen
|| memcmp (indata, oidbuf, oidbuflen))
{
log_error ("nks: input data too long for ECC: len=%zu\n",
indatalen);
return gpg_error (GPG_ERR_INV_VALUE);
}
datalen = indatalen - oidbuflen;
log_assert (datalen <= sizeof data);
memcpy (data, (const char*)indata + oidbuflen, datalen);
}
else
{
log_error ("nks: input data too short for ECC: len=%zu\n",
indatalen);
return gpg_error (GPG_ERR_INV_VALUE);
}
}
else if (app->appversion > 2 && (indatalen == 35
|| indatalen == 47
|| indatalen == 51
|| indatalen == 67
|| indatalen == 83))
{
/* Verify that the caller has sent a proper ASN.1 encoded hash
for RMD160 or SHA-{1,224,256,384,512}. */
#define X(algo,prefix,plaindigestlen) \
if (hashalgo == (algo) \
&& indatalen == sizeof prefix + (plaindigestlen) \
&& !memcmp (indata, prefix, sizeof prefix)) \
;
X(GCRY_MD_RMD160, rmd160_prefix, 20)
else X(GCRY_MD_SHA1, sha1_prefix, 20)
else X(GCRY_MD_SHA224, sha224_prefix, 28)
else X(GCRY_MD_SHA256, sha256_prefix, 32)
else X(GCRY_MD_SHA384, sha384_prefix, 48)
else X(GCRY_MD_SHA512, sha512_prefix, 64)
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
#undef X
log_assert (indatalen <= sizeof data);
memcpy (data, indata, indatalen);
datalen = indatalen;
}
else if (indatalen == 35)
{
/* Alright, the caller was so kind to send us an already
prepared DER object. This is for TCOS 2. */
if (hashalgo == GCRY_MD_SHA1 && !memcmp (indata, sha1_prefix, 15))
;
else if (hashalgo == GCRY_MD_RMD160 && !memcmp (indata,rmd160_prefix,15))
;
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
memcpy (data, indata, indatalen);
datalen = 35;
}
/* Concatenate prefix and digest.
* Note that the X macro creates an "else if". Ugly - I know. */
#define X(algo,prefix,plaindigestlen) \
if ((hashalgo == (algo)) && (indatalen == (plaindigestlen))) \
{ \
datalen = sizeof prefix + indatalen; \
log_assert (datalen <= sizeof data); \
memcpy (data, prefix, sizeof prefix); \
memcpy (data + sizeof prefix, indata, indatalen); \
}
else X(GCRY_MD_RMD160, rmd160_prefix, 20)
else X(GCRY_MD_SHA1, sha1_prefix, 20)
else X(GCRY_MD_SHA224, sha224_prefix, 28)
else X(GCRY_MD_SHA256, sha256_prefix, 32)
else X(GCRY_MD_SHA384, sha384_prefix, 48)
else X(GCRY_MD_SHA512, sha512_prefix, 64)
else
return gpg_error (GPG_ERR_INV_VALUE);
#undef X
/* Send an MSE for PSO:Compute_Signature. */
if (app->appversion > 2 && app->app_local->active_nks_app != NKS_APP_ESIGN)
{
unsigned char mse[6];
unsigned int mselen;
if (algo == GCRY_PK_ECC)
{
mse[0] = 0x84; /* Private key reference. */
mse[1] = 1;
mse[2] = kid;
mselen = 3;
}
else /* RSA */
{
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 2; /* Card does pkcs#1 v1.5 padding, no ASN.1 check. */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
mselen = 6;
}
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB6,
mse, mselen);
}
if (app->app_local->active_nks_app == NKS_APP_ESIGN)
pwid = 0x81;
else if (app->appversion == 15)
pwid = 0x03;
else
pwid = 0x00;
if (!err)
err = verify_pin (app, pwid, NULL, pincb, pincb_arg);
/* Compute the signature. */
if (!err)
err = iso7816_compute_ds (app_get_slot (app), 0, data, datalen, 0,
outdata, outdatalen);
return err;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
static gpg_error_t
do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
int idx;
int kid;
int algo;
int pwid;
int padind;
int extended_mode;
(void)ctrl;
(void)r_info;
if (!indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
err = find_fid_by_keyref (app, keyidstr, &idx, &algo);
if (err)
return err;
if (!filelist[idx].isencrkey)
return gpg_error (GPG_ERR_INV_ID);
kid = filelist[idx].kid;
if (app->appversion <= 2)
{
static const unsigned char mse[] =
{
0x80, 1, 0x10, /* Select algorithm RSA. */
0x84, 1, 0x81 /* Select local secret key 1 for decryption. */
};
err = iso7816_manage_security_env (app_get_slot (app), 0xC1, 0xB8,
mse, sizeof mse);
extended_mode = 0;
padind = 0x81;
}
else if (algo == GCRY_PK_ECC)
{
unsigned char mse[3];
mse[0] = 0x84; /* Private key reference. */
mse[1] = 1;
mse[2] = kid;
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8,
mse, sizeof mse);
extended_mode = 0;
padind = 0x00;
}
else
{
unsigned char mse[6];
mse[0] = 0x80; /* Algorithm reference. */
mse[1] = 1;
mse[2] = 0x0a; /* RSA no padding. (0x1A is pkcs#1.5 padding.) */
mse[3] = 0x84; /* Private key reference. */
mse[4] = 1;
mse[5] = kid;
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8,
mse, sizeof mse);
extended_mode = 1;
padind = 0x81;
}
if (err)
{
log_error ("nks: MSE failed: %s\n", gpg_strerror (err));
goto leave;
}
/* We use the Global PIN 1 */
if (app->appversion == 15)
pwid = 0x03;
else
pwid = 0x00;
err = verify_pin (app, pwid, NULL, pincb, pincb_arg);
if (err)
goto leave;
err = iso7816_decipher (app_get_slot (app), extended_mode,
indata, indatalen, 0, padind,
outdata, outdatalen);
leave:
return err;
}
/* Parse a password ID string. Returns NULL on error or a string
* suitable as passphrase prompt on success. On success stores the
* reference value for the password at R_PWID and a flag indicating
* which app is to be used at R_NKS_APP_ID. If NEW_MODE is true, the
* returned description is suitable for a new password. Here is a
* take mapping the PWIDSTR to the used PWIDs:
*
* | pwidstr | | NKS3 | NKS15 | IDKEY1 |
* |------------+--------------+------+-------+--------|
* | PW1.CH | Global PIN 1 | 0x00 | 0x03 | 0x00 |
* | PW2.CH | Global PIN 2 | 0x01 | 0x04 | 0x01 |
* | PW1.CH.SIG | SigG PIN 1 | 0x81 | 0x81 | - |
* | PW2.CH.SIG | SigG PIN 2 | 0x83 | 0x82 | - |
*
* The names for PWIDSTR are taken from the NKS3 specs; the specs of
* other cards use different names but we keep using the. PIN1 can be
* used to unlock PIN2 and vice versa; for consistence with other
* cards we name PIN2 a "PUK". The IDKEY card also features a Card
* Reset Key (CR Key 0x01) which can also be used to reset PIN1.
*
* For testing it is possible to specify the PWID directly; the
* prompts are then not very descriptive:
*
* NKS.0xnn - Switch to NKS and select id 0xnn
* SIGG.0xnn - Switch to SigG and select id 0xnn
* ESIGN.0xnn - Switch to ESIGN and select id 0xnn
*/
static const char *
parse_pwidstr (app_t app, const char *pwidstr, int new_mode,
int *r_nks_app_id, int *r_pwid)
{
const char *desc;
int nks15 = app->appversion == 15;
if (!pwidstr)
desc = NULL;
else if (!strcmp (pwidstr, "PW1.CH"))
{
*r_nks_app_id = NKS_APP_NKS;
*r_pwid = nks15? 0x03 : 0x00;
/* TRANSLATORS: Do not translate the "|*|" prefixes but keep
them verbatim at the start of the string. */
desc = (new_mode
? _("|N|Please enter a new PIN for the standard keys.")
: _("||Please enter the PIN for the standard keys."));
}
else if (!strcmp (pwidstr, "PW2.CH"))
{
*r_nks_app_id = NKS_APP_NKS;
*r_pwid = nks15? 0x04 : 0x01;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the standard keys.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the standard keys."));
}
else if (!strcmp (pwidstr, "PW1.CH.SIG") && !app->app_local->only_idlm)
{
*r_nks_app_id = app->app_local->qes_app_id;
*r_pwid = 0x81;
desc = (new_mode
? _("|N|Please enter a new PIN for the key to create "
"qualified signatures.")
: _("||Please enter the PIN for the key to create "
"qualified signatures."));
}
else if (!strcmp (pwidstr, "PW2.CH.SIG") && !app->app_local->only_idlm)
{
*r_nks_app_id = app->app_local->qes_app_id;
*r_pwid = nks15? 0x82 : 0x83;
desc = (new_mode
? _("|NP|Please enter a new PIN Unblocking Code (PUK) "
"for the key to create qualified signatures.")
: _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the key to create qualified signatures."));
}
else if (!strncmp (pwidstr, "NKS.0x", 6)
&& hexdigitp (pwidstr+6) && hexdigitp (pwidstr+7) && !pwidstr[8])
{
/* Hack to help debugging. */
*r_nks_app_id = NKS_APP_NKS;
*r_pwid = xtoi_2 (pwidstr+6);
desc = (new_mode
? "|N|Please enter a new PIN for the given NKS pwid"
: "||Please enter the PIN for the given NKS pwid" );
}
else if (!strncmp (pwidstr, "SIGG.0x", 7)
&& hexdigitp (pwidstr+7) && hexdigitp (pwidstr+8) && !pwidstr[9])
{
/* Hack to help debugging. */
*r_nks_app_id = NKS_APP_SIGG;
*r_pwid = xtoi_2 (pwidstr+7);
desc = (new_mode
? "|N|Please enter a new PIN for the given SIGG pwid"
: "||Please enter the PIN for the given SIGG pwid" );
}
else if (!strncmp (pwidstr, "ESIGN.0x", 8)
&& hexdigitp (pwidstr+8) && hexdigitp (pwidstr+9) && !pwidstr[10])
{
/* Hack to help debugging. */
*r_nks_app_id = NKS_APP_ESIGN;
*r_pwid = xtoi_2 (pwidstr+8);
desc = (new_mode
? "|N|Please enter a new PIN for the given ESIGN pwid"
: "||Please enter the PIN for the given ESIGN pwid" );
}
else if (!strncmp (pwidstr, "IDLM.0x", 7)
&& hexdigitp (pwidstr+7) && hexdigitp (pwidstr+8) && !pwidstr[9])
{
/* Hack to help debugging. */
*r_nks_app_id = NKS_APP_IDLM;
*r_pwid = xtoi_2 (pwidstr+7);
desc = (new_mode
? "|N|Please enter a new PIN for the given IDLM pwid"
: "||Please enter the PIN for the given IDLM pwid" );
}
else
{
*r_pwid = 0; /* Only to avoid gcc warning in calling function. */
desc = NULL; /* Error. */
}
return desc;
}
/* Handle the PASSWD command. See parse_pwidstr() for allowed values
for CHVNOSTR. */
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char *newpin = NULL;
char *oldpin = NULL;
size_t newpinlen;
size_t oldpinlen;
int nks_app_id;
const char *newdesc;
int pwid;
pininfo_t pininfo;
int remaining;
char *prompt;
(void)ctrl;
/* The minimum length is enforced by TCOS, the maximum length is
just a reasonable value. */
memset (&pininfo, 0, sizeof pininfo);
pininfo.minlen = 6;
pininfo.maxlen = 16;
newdesc = parse_pwidstr (app, pwidstr, 1, &nks_app_id, &pwid);
if (!newdesc)
return gpg_error (GPG_ERR_INV_ID);
if ((flags & APP_CHANGE_FLAG_CLEAR))
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
err = switch_application (app, nks_app_id);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_NULLPIN))
{
/* With the nullpin flag, we do not verify the PIN - it would
fail if the Nullpin is still set. */
oldpin = xtrycalloc (1, 6);
if (!oldpin)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (app->appversion == 15)
{
memset (oldpin, '0', 5);
oldpinlen = 5; /* 5 ascii zeroes. */
}
else
{
oldpinlen = 6; /* 6 binary Nuls. */
}
}
else
{
const char *desc;
int dummy1, dummy2;
if ((flags & APP_CHANGE_FLAG_RESET))
{
/* Reset mode: Ask for the alternate PIN. */
const char *altpwidstr;
if (!strcmp (pwidstr, "PW1.CH"))
altpwidstr = "PW2.CH";
else if (!strcmp (pwidstr, "PW2.CH"))
altpwidstr = "PW1.CH";
else if (!strcmp (pwidstr, "PW1.CH.SIG"))
altpwidstr = "PW2.CH.SIG";
else if (!strcmp (pwidstr, "PW2.CH.SIG"))
altpwidstr = "PW1.CH.SIG";
else
{
err = gpg_error (GPG_ERR_BUG);
goto leave;
}
desc = parse_pwidstr (app, altpwidstr, 0, &dummy1, &dummy2);
remaining = iso7816_verify_status (app_get_slot (app), dummy2);
}
else
{
/* Regular change mode: Ask for the old PIN. */
desc = parse_pwidstr (app, pwidstr, 0, &dummy1, &dummy2);
remaining = iso7816_verify_status (app_get_slot (app), pwid);
}
if (remaining < 0)
remaining = -1; /* We don't care about the concrete error. */
if (remaining < 3)
{
if (remaining >= 0)
log_info ("nks: PIN has %d attempts left\n", remaining);
}
prompt = make_prompt (app, remaining, desc, NULL);
err = pincb (pincb_arg, prompt, &oldpin);
xfree (prompt);
if (err)
{
log_error ("error getting old PIN: %s\n", gpg_strerror (err));
goto leave;
}
oldpinlen = strlen (oldpin);
err = basic_pin_checks (oldpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
}
prompt = make_prompt (app, -1, newdesc, NULL);
err = pincb (pincb_arg, prompt, &newpin);
xfree (prompt);
if (err)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (err));
goto leave;
}
newpinlen = strlen (newpin);
err = basic_pin_checks (newpin, pininfo.minlen, pininfo.maxlen);
if (err)
goto leave;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *data;
size_t datalen = oldpinlen + newpinlen;
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (data, oldpin, oldpinlen);
memcpy (data+oldpinlen, newpin, newpinlen);
err = iso7816_reset_retry_counter_with_rc (app_get_slot (app), pwid,
data, datalen);
wipememory (data, datalen);
xfree (data);
}
else
err = iso7816_change_reference_data (app_get_slot (app), pwid,
oldpin, oldpinlen,
newpin, newpinlen);
leave:
xfree (oldpin);
xfree (newpin);
return err;
}
/* Perform a simple verify operation. KEYIDSTR should be NULL or empty. */
static gpg_error_t
do_check_pin (app_t app, ctrl_t ctrl, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int pwid;
int nks_app_id;
const char *desc;
(void)ctrl;
desc = parse_pwidstr (app, pwidstr, 0, &nks_app_id, &pwid);
if (!desc)
return gpg_error (GPG_ERR_INV_ID);
err = switch_application (app, nks_app_id);
if (err)
return err;
return verify_pin (app, pwid, desc, pincb, pincb_arg);
}
/* Process the various keygrip based info requests. */
static gpg_error_t
do_with_keygrip (app_t app, ctrl_t ctrl, int action,
const char *want_keygripstr, int capability)
{
gpg_error_t err;
char keygripstr[2*KEYGRIP_LEN+1];
char *serialno = NULL;
int data = 0;
int idx = -1;
/* First a quick check for valid parameters. */
switch (action)
{
case KEYGRIP_ACTION_LOOKUP:
if (!want_keygripstr)
{
return gpg_error (GPG_ERR_NOT_FOUND);
}
break;
case KEYGRIP_ACTION_SEND_DATA:
data = 1;
break;
case KEYGRIP_ACTION_WRITE_STATUS:
break;
default:
return gpg_error (GPG_ERR_INV_ARG);
}
/* Allocate the S/N string if needed. */
if (action != KEYGRIP_ACTION_LOOKUP)
{
serialno = app_get_serialno (app);
if (!serialno)
return gpg_error_from_syserror ();
}
while (1)
{
err = iterate_over_filelist (app, want_keygripstr, capability,
keygripstr, &idx);
if (err)
break;
if (want_keygripstr)
{
if (!err)
break;
}
else
{
char idbuf[20];
char usagebuf[5];
snprintf (idbuf, sizeof idbuf, "NKS-%s.%04X",
get_nks_tag (app, app->app_local->active_nks_app),
filelist[idx].fid);
set_usage_string (usagebuf, idx);
send_keyinfo (ctrl, data, keygripstr, serialno, idbuf, usagebuf);
}
}
xfree (serialno);
return err;
}
/* Return the version of the NKS application. */
static int
get_nks_version (int slot)
{
unsigned char *result = NULL;
size_t resultlen;
int type;
if (iso7816_apdu_direct (slot, "\x80\xaa\x06\x00\x00", 5, 0,
NULL, &result, &resultlen))
return 2; /* NKS 2 does not support this command. */
/* Example values: 04 11 19 22 21 6A 20 80 03 03 01 01 01 00 00 00
* 05 a0 22 3e c8 0c 04 20 0f 01 b6 01 01 00 00 02
* vv tt ccccccccccccccccc aa bb cc vv ff rr rr xx
* vendor -----------+ | | | | | | | | | |
* chip type -----------+ | | | | | | | | |
* chip id ----------------+ | | | | | | | |
* card type --------------------------------+ | | | | | | |
* OS version of card type ---------------------+ | | | | | |
* OS release of card type ------------------------+ | | | | |
* Completion code version number --------------------+ | | | |
* File system version ----------------------------------+ | | |
* RFU (00) ------------------------------------------------+ | |
* RFU (00) ---------------------------------------------------+ |
* Authentication key identifier ---------------------------------+
*
* vendor 4 := Philips
* 5 := Infinion
* card type 3 := TCOS 3
* 15 := TCOS Signature Card (bb,cc is the ROM mask version)
* Completion code version number Bit 7..5 := pre-completion code version
* Bit 4..0 := completion code version
* (pre-completion by chip vendor)
* (completion by OS developer)
*/
if (resultlen < 16)
type = 0; /* Invalid data returned. */
else
type = result[8];
xfree (result);
return type;
}
/* Switch to the NKS app identified by NKS_APP_ID if not yet done.
* Returns 0 on success. */
static gpg_error_t
switch_application (app_t app, int nks_app_id)
{
gpg_error_t err;
if (app->app_local->only_idlm)
return 0; /* No switching at all */
if (app->app_local->active_nks_app == nks_app_id
&& !app->app_local->need_app_select)
return 0; /* Already switched. */
log_info ("nks: switching to %s\n",
nks_app_id == NKS_APP_ESIGN? "eSign" :
nks_app_id == NKS_APP_SIGG? "SigG" : "NKS");
if (nks_app_id == NKS_APP_ESIGN)
err = iso7816_select_application (app_get_slot (app),
aid_esign, sizeof aid_esign, 0);
else if (nks_app_id == NKS_APP_SIGG)
err = iso7816_select_application (app_get_slot (app),
aid_sigg, sizeof aid_sigg, 0);
else
err = iso7816_select_application (app_get_slot (app),
aid_nks, sizeof aid_nks, 0);
if (!err && nks_app_id == NKS_APP_SIGG
&& app->appversion >= 3
&& !app->app_local->sigg_msig_checked)
{
/* Check whether this card is a mass signature card. */
unsigned char *buffer;
size_t buflen;
const unsigned char *tmpl;
size_t tmpllen;
app->app_local->sigg_msig_checked = 1;
app->app_local->sigg_is_msig = 1;
err = iso7816_select_file (app_get_slot (app), 0x5349, 0);
if (!err)
err = iso7816_read_record (app_get_slot (app), 1, 1, 0,
&buffer, &buflen);
if (!err)
{
tmpl = find_tlv (buffer, buflen, 0x7a, &tmpllen);
if (tmpl && tmpllen == 12
&& !memcmp (tmpl,
"\x93\x02\x00\x01\xA4\x06\x83\x01\x81\x83\x01\x83",
12))
app->app_local->sigg_is_msig = 0;
xfree (buffer);
}
if (app->app_local->sigg_is_msig)
log_info ("nks: This is a mass signature card\n");
}
if (!err)
{
app->app_local->need_app_select = 0;
app->app_local->active_nks_app = nks_app_id;
}
else
log_error ("nks: error switching to %s: %s\n",
nks_app_id == NKS_APP_ESIGN? "eSign" :
nks_app_id == NKS_APP_SIGG? "SigG" : "NKS",
gpg_strerror (err));
return err;
}
/* Select the NKS application. */
gpg_error_t
app_select_nks (app_t app)
{
int slot = app_get_slot (app);
int rc;
int is_idlm = 0;
rc = iso7816_select_application (slot, aid_nks, sizeof aid_nks, 0);
if (rc)
{
is_idlm = 1;
rc = iso7816_select_application (slot, aid_idlm, sizeof aid_idlm, 0);
}
if (!rc)
{
app->apptype = APPTYPE_NKS;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error (gpg_err_code_from_errno (errno));
goto leave;
}
app->appversion = get_nks_version (slot);
app->app_local->only_idlm = is_idlm;
if (is_idlm) /* Set it once, there won't be any switching. */
app->app_local->active_nks_app = NKS_APP_IDLM;
if (opt.verbose)
{
log_info ("Detected NKS version: %d\n", app->appversion);
if (is_idlm)
log_info ("Using only the IDLM application\n");
}
if (app->appversion == 15)
app->app_local->qes_app_id = NKS_APP_ESIGN;
else
app->app_local->qes_app_id = NKS_APP_SIGG;
app->fnc.deinit = do_deinit;
app->fnc.prep_reselect = NULL;
app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.writecert = do_writecert;
app->fnc.writekey = do_writekey;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = NULL;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
app->fnc.with_keygrip = do_with_keygrip;
}
leave:
if (rc)
do_deinit (app);
return rc;
}
diff --git a/scd/app-openpgp.c b/scd/app-openpgp.c
index 5882153eb..4af4f880e 100644
--- a/scd/app-openpgp.c
+++ b/scd/app-openpgp.c
@@ -1,6738 +1,6738 @@
/* app-openpgp.c - The OpenPGP card application.
* Copyright (C) 2003-2005, 2007-2009,
* 2013-2015 Free Software Foundation, Inc.
* Copyright (C) 2003-2005, 2007-2009, 2013-2015, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Some notes:
CHV means Card Holder Verification and is nothing else than a PIN
or password. That term seems to have been used originally with GSM
cards. Version v2 of the specs changes the term to the clearer
term PW for password. We use the terms here interchangeable
because we do not want to change existing strings i18n wise.
Version 2 of the specs also drops the separate PW2 which was
required in v1 due to ISO requirements. It is now possible to have
one physical PW but two reference to it so that they can be
individually be verified (e.g. to implement a forced verification
for one key). Thus you will noticed the use of PW2 with the verify
command but not with change_reference_data because the latter
operates directly on the physical PW.
The Reset Code (RC) as implemented by v2 cards uses the same error
counter as the PW2 of v1 cards. By default no RC is set and thus
that error counter is set to 0. After setting the RC the error
counter will be initialized to 3.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/util.h"
#include "../common/i18n.h"
#include "iso7816.h"
#include "../common/tlv.h"
#include "../common/host2net.h"
#include "../common/openpgpdefs.h"
#define KDF_DATA_LENGTH_MIN 90
#define KDF_DATA_LENGTH_MAX 110
/* The AID of this application. */
static char const openpgp_aid[] = { 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01 };
/* A table describing the DOs of the card. */
static struct {
int tag;
int constructed;
int get_from; /* Constructed DO with this DO or 0 for direct access. */
unsigned int binary:1;
unsigned int dont_cache:1;
unsigned int flush_on_error:1;
unsigned int get_immediate_in_v11:1; /* Enable a hack to bypass the cache of
this data object if it is used in 1.1
and later versions of the card. This
does not work with composite DO and
is currently only useful for the CHV
status bytes. */
unsigned int try_extlen:2; /* Large object; try to use an extended
length APDU when !=0. The size is
determined by extcap.max_certlen
when == 1, and by extcap.max_special_do
when == 2. */
char *desc;
} data_objects[] = {
{ 0x005E, 0, 0, 1, 0, 0, 0, 2, "Login Data" },
{ 0x5F50, 0, 0, 0, 0, 0, 0, 2, "URL" },
{ 0x5F52, 0, 0, 1, 0, 0, 0, 0, "Historical Bytes" },
{ 0x0065, 1, 0, 1, 0, 0, 0, 0, "Cardholder Related Data"},
{ 0x005B, 0, 0x65, 0, 0, 0, 0, 0, "Name" },
{ 0x5F2D, 0, 0x65, 0, 0, 0, 0, 0, "Language preferences" },
{ 0x5F35, 0, 0x65, 0, 0, 0, 0, 0, "Salutation" },
{ 0x006E, 1, 0, 1, 0, 0, 0, 0, "Application Related Data" },
{ 0x004F, 0, 0x6E, 1, 0, 0, 0, 0, "AID" },
{ 0x0073, 1, 0, 1, 0, 0, 0, 0, "Discretionary Data Objects" },
{ 0x0047, 0, 0x6E, 1, 1, 0, 0, 0, "Card Capabilities" },
{ 0x00C0, 0, 0x6E, 1, 1, 0, 0, 0, "Extended Card Capabilities" },
{ 0x00C1, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Signature" },
{ 0x00C2, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Decryption" },
{ 0x00C3, 0, 0x6E, 1, 1, 0, 0, 0, "Algorithm Attributes Authentication" },
{ 0x00C4, 0, 0x6E, 1, 0, 1, 1, 0, "CHV Status Bytes" },
{ 0x00C5, 0, 0x6E, 1, 0, 0, 0, 0, "Fingerprints" },
{ 0x00C6, 0, 0x6E, 1, 0, 0, 0, 0, "CA Fingerprints" },
{ 0x00CD, 0, 0x6E, 1, 0, 0, 0, 0, "Generation time" },
{ 0x007A, 1, 0, 1, 0, 0, 0, 0, "Security Support Template" },
{ 0x0093, 0, 0x7A, 1, 1, 0, 0, 0, "Digital Signature Counter" },
{ 0x0101, 0, 0, 0, 0, 0, 0, 2, "Private DO 1"},
{ 0x0102, 0, 0, 0, 0, 0, 0, 2, "Private DO 2"},
{ 0x0103, 0, 0, 0, 0, 0, 0, 2, "Private DO 3"},
{ 0x0104, 0, 0, 0, 0, 0, 0, 2, "Private DO 4"},
{ 0x7F21, 1, 0, 1, 0, 0, 0, 1, "Cardholder certificate"},
/* V3.0 */
{ 0x7F74, 0, 0x6E, 1, 0, 0, 0, 0, "General Feature Management"},
{ 0x00D5, 0, 0, 1, 0, 0, 0, 0, "AES key data"},
{ 0x00D6, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Signature"},
{ 0x00D7, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Decryption"},
{ 0x00D8, 0, 0x6E, 1, 0, 0, 0, 0, "UIF for Authentication"},
{ 0x00F9, 1, 0, 1, 0, 0, 0, 0, "KDF data object"},
{ 0x00FA, 1, 0, 1, 0, 0, 0, 2, "Algorithm Information"},
{ 0 }
};
/* Type of keys. */
typedef enum
{
KEY_TYPE_ECC,
KEY_TYPE_RSA,
}
key_type_t;
/* The format of RSA private keys. */
typedef enum
{
RSA_UNKNOWN_FMT,
RSA_STD,
RSA_STD_N,
RSA_CRT,
RSA_CRT_N
}
rsa_key_format_t;
/* One cache item for DOs. */
struct cache_s {
struct cache_s *next;
int tag;
size_t length;
unsigned char data[1];
};
/* Object with application (i.e. OpenPGP card) specific data. */
struct app_local_s {
/* A linked list with cached DOs. */
struct cache_s *cache;
/* Keep track of the public keys. */
struct
{
int read_done; /* True if we have at least tried to read them. */
unsigned char *key; /* This is a malloced buffer with a canonical
encoded S-expression encoding a public
key. Might be NULL if key is not
available. */
size_t keylen; /* The length of the above S-expression. This
is usually only required for cross checks
because the length of an S-expression is
implicitly available. */
unsigned char keygrip_str[41]; /* The keygrip, null terminated */
} pk[3];
unsigned char status_indicator; /* The card status indicator. */
unsigned int manufacturer:16; /* Manufacturer ID from the s/n. */
/* Keep track of the ISO card capabilities. */
struct
{
unsigned int cmd_chaining:1; /* Command chaining is supported. */
unsigned int ext_lc_le:1; /* Extended Lc and Le are supported. */
} cardcap;
/* Keep track of extended card capabilities. */
struct
{
unsigned int is_v2:1; /* Compatible to v2 or later. */
- unsigned int is_v3:1; /* Comatible to v3 or later. */
+ unsigned int is_v3:1; /* Compatible to v3 or later. */
unsigned int has_button:1; /* Has confirmation button or not. */
unsigned int sm_supported:1; /* Secure Messaging is supported. */
unsigned int get_challenge:1;
unsigned int key_import:1;
unsigned int change_force_chv:1;
unsigned int private_dos:1;
unsigned int algo_attr_change:1; /* Algorithm attributes changeable. */
unsigned int has_decrypt:1; /* Support symmetric decryption. */
unsigned int kdf_do:1; /* Support KDF DO. */
unsigned int sm_algo:2; /* Symmetric crypto algo for SM. */
unsigned int pin_blk2:1; /* PIN block 2 format supported. */
unsigned int mse:1; /* MSE command supported. */
unsigned int max_certlen:16; /* Maximum size of DO 7F21. */
unsigned int max_get_challenge:16; /* Maximum size for get_challenge. */
unsigned int max_special_do:16; /* Maximum size for special DOs. */
} extcap;
/* Flags used to control the application. */
struct
{
unsigned int no_sync:1; /* Do not sync CHV1 and CHV2 */
unsigned int def_chv2:1; /* Use 123456 for CHV2. */
} flags;
/* Flags used to override certain behavior. */
struct
{
unsigned int cache_6e:1;
} override;
/* Keep track on whether we cache a certain PIN so that we get it
* from the cache only if we know we cached it. This inhibits the
* use of the same cache entry for a card plugged in and out without
* gpg-agent having noticed that due to a bug. */
struct
{
unsigned int maybe_chv1:1;
unsigned int maybe_chv2:1;
unsigned int maybe_chv3:1;
} pincache;
/* Pinpad request specified on card. */
struct
{
unsigned int disabled:1; /* No pinpad use because of KDF DO. */
unsigned int specified:1;
int fixedlen_user;
int fixedlen_admin;
} pinpad;
struct
{
key_type_t key_type;
const char *keyalgo; /* Algorithm in standard string format. */
union {
struct {
unsigned int n_bits; /* Size of the modulus in bits. The rest
of this strucuire is only valid if
this is not 0. */
unsigned int e_bits; /* Size of the public exponent in bits. */
rsa_key_format_t format;
} rsa;
struct {
const char *curve; /* Canonical name defined in openpgp-oid.c */
int algo;
unsigned int flags;
} ecc;
};
} keyattr[3];
};
#define ECC_FLAG_DJB_TWEAK (1 << 0)
#define ECC_FLAG_PUBKEY (1 << 1)
/***** Local prototypes *****/
static unsigned long convert_sig_counter_value (const unsigned char *value,
size_t valuelen);
static unsigned long get_sig_counter (app_t app);
static gpg_error_t do_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen);
static const char *get_algorithm_attribute_string (const unsigned char *buffer,
size_t buflen);
static gpg_error_t parse_algorithm_attribute (app_t app, int keyno);
static gpg_error_t change_keyattr_from_string
(app_t app, ctrl_t ctrl,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const char *keyref, const char *keyalgo,
const void *value, size_t valuelen);
/* Return the OpenPGP card manufacturer name. */
static const char *
get_manufacturer (unsigned int no)
{
/* Note: Make sure that there is no colon or linefeed in the string. */
switch (no)
{
case 0x0001: return "PPC Card Systems";
case 0x0002: return "Prism";
case 0x0003: return "OpenFortress";
case 0x0004: return "Wewid";
case 0x0005: return "ZeitControl";
case 0x0006: return "Yubico";
case 0x0007: return "OpenKMS";
case 0x0008: return "LogoEmail";
case 0x0009: return "Fidesmo";
case 0x000A: return "VivoKey";
case 0x000B: return "Feitian Technologies";
case 0x000D: return "Dangerous Things";
case 0x000E: return "Excelsecu";
case 0x000F: return "Nitrokey";
case 0x0010: return "NeoPGP";
case 0x0011: return "Token2";
case 0x002A: return "Magrathea";
case 0x0042: return "GnuPG e.V.";
case 0x1337: return "Warsaw Hackerspace";
case 0x2342: return "warpzone"; /* hackerspace Muenster. */
case 0x4354: return "Confidential Technologies"; /* cotech.de */
case 0x5343: return "SSE Carte à puce";
case 0x5443: return "TIF-IT e.V.";
case 0x63AF: return "Trustica";
case 0xBA53: return "c-base e.V.";
case 0xBD0E: return "Paranoidlabs";
case 0xCA05: return "Atos CardOS";
case 0xF1D0: return "CanoKeys";
case 0xF517: return "FSIJ";
case 0xF5EC: return "F-Secure";
/* 0x0000 and 0xFFFF are defined as test cards per spec,
* 0xFF00 to 0xFFFE are assigned for use with randomly created
* serial numbers. */
case 0x0000:
case 0xffff: return "test card";
default: return (no & 0xff00) == 0xff00? "unmanaged S/N range":"unknown";
}
}
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
int i;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
for (i=0; i < DIM (app->app_local->pk); i++)
{
xfree (app->app_local->pk[i].key);
app->app_local->pk[i].read_done = 0;
}
xfree (app->app_local);
app->app_local = NULL;
}
}
/* This is a helper to do a wipememory followed by a free. In general
* we do not need this if the buffer has been allocated in secure
* memory. However at some places we can't make that sure and thus we
* better to an extra wipe here. */
static void
wipe_and_free (void *p, size_t len)
{
if (p)
{
if (len)
wipememory (p, len);
xfree (p);
}
}
/* Similar to wipe_and_free but assumes P is eitehr NULL or a proper
* string. */
static void
wipe_and_free_string (char *p)
{
if (p)
{
wipememory (p, strlen (p));
xfree (p);
}
}
/* Wrapper around iso7816_get_data which first tries to get the data
from the cache. With GET_IMMEDIATE passed as true, the cache is
bypassed. With TRY_EXTLEN extended lengths APDUs are use if
supported by the card. */
static gpg_error_t
get_cached_data (app_t app, int tag,
unsigned char **result, size_t *resultlen,
int get_immediate, int try_extlen)
{
gpg_error_t err;
int i;
unsigned char *p;
size_t len;
struct cache_s *c;
int exmode;
int do_constructed = 0;
if ((tag < 0x0100 && (tag & 0x20)) || (tag >= 0x0100 && (tag & 0x2000)))
do_constructed = 1;
*result = NULL;
*resultlen = 0;
if (tag == 0x6E && app->app_local->override.cache_6e)
get_immediate = 0;
if (!get_immediate)
{
for (c=app->app_local->cache; c; c = c->next)
if (c->tag == tag)
{
if(c->length)
{
p = xtrymalloc (c->length);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, c->data, c->length);
*result = p;
}
*resultlen = c->length;
return 0;
}
}
if (try_extlen && app->app_local->cardcap.ext_lc_le)
{
if (try_extlen == 1)
exmode = app->app_local->extcap.max_certlen;
else if (try_extlen == 2 && app->app_local->extcap.is_v3)
exmode = app->app_local->extcap.max_special_do;
else
exmode = 0;
}
else
exmode = 0;
err = iso7816_get_data (app_get_slot (app), exmode, tag, &p, &len);
if (err)
return err;
/* When Data Object is constructed, when it comes with its tag and
length, remove them before storing it into the cache, because
it's redundant and takes space. This handling also works well
with the card implementation which doesn't include its tag and
length for the DO value returned by GET DATA. */
if (do_constructed)
{
if (len && tag < 0x0100 && p[0] == tag)
{
if (len >= 2 && p[1] < 0x80 && p[1] == len - 2)
{
len -= 2;
memmove (p, p+2, len);
}
else if (len >= 3 && p[1] == 0x81 && p[2] == len - 3)
{
len -= 3;
memmove (p, p+3, len);
}
else if (len >= 4 && p[1] == 0x82 && ((p[2] << 8) | p[3]) == len - 4)
{
len -= 4;
memmove (p, p+4, len);
}
}
else if (len >= 2 && tag >= 0x0100 && ((p[0] << 8) | p[1]) == tag)
{
if (len >= 3 && p[2] < 0x80 && p[2] == len - 2)
{
len -= 3;
memmove (p, p+3, len);
}
else if (len >= 4 && p[2] == 0x81 && p[3] == len - 3)
{
len -= 4;
memmove (p, p+4, len);
}
else if (len >= 5 && p[2] == 0x82 && ((p[3] << 8) | p[4]) == len - 4)
{
len -= 5;
memmove (p, p+5, len);
}
}
}
if (len)
*result = p;
*resultlen = len;
/* Check whether we should cache this object. */
if (get_immediate)
return 0;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag)
{
if (data_objects[i].dont_cache)
return 0;
break;
}
/* Okay, cache it. */
for (c=app->app_local->cache; c; c = c->next)
log_assert (c->tag != tag);
c = xtrymalloc (sizeof *c + len);
if (c)
{
if (len)
memcpy (c->data, p, len);
else
xfree (p);
c->length = len;
c->tag = tag;
c->next = app->app_local->cache;
app->app_local->cache = c;
}
return 0;
}
/* Remove DO at TAG from the cache. */
static void
flush_cache_item (app_t app, int tag)
{
struct cache_s *c, *cprev;
int i;
if (!app->app_local)
return;
for (c=app->app_local->cache, cprev=NULL; c ; cprev=c, c = c->next)
if (c->tag == tag)
{
if (cprev)
cprev->next = c->next;
else
app->app_local->cache = c->next;
xfree (c);
for (c=app->app_local->cache; c ; c = c->next)
{
log_assert (c->tag != tag); /* Oops: duplicated entry. */
}
return;
}
/* Try again if we have an outer tag. */
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag && data_objects[i].get_from
&& data_objects[i].get_from != tag)
flush_cache_item (app, data_objects[i].get_from);
}
/* Flush all entries from the cache which might be out of sync after
an error. */
static void
flush_cache_after_error (app_t app)
{
int i;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].flush_on_error)
flush_cache_item (app, data_objects[i].tag);
}
/* Flush the entire cache. */
static void
flush_cache (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
app->app_local->cache = NULL;
}
}
/* Get the DO identified by TAG from the card in SLOT and return a
buffer with its content in RESULT and NBYTES. The return value is
NULL if not found or a pointer which must be used to release the
buffer holding value. */
static void *
get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
int *r_rc)
{
int rc, i;
unsigned char *buffer;
size_t buflen;
unsigned char *value;
size_t valuelen;
int dummyrc;
int exmode;
if (!r_rc)
r_rc = &dummyrc;
*result = NULL;
*nbytes = 0;
*r_rc = 0;
for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
;
if (app->appversion > 0x0100 && data_objects[i].get_immediate_in_v11)
{
exmode = 0;
rc = iso7816_get_data (app_get_slot (app), exmode, tag, &buffer, &buflen);
if (rc)
{
*r_rc = rc;
return NULL;
}
*result = buffer;
*nbytes = buflen;
return buffer;
}
value = NULL;
rc = -1;
if (data_objects[i].tag && data_objects[i].get_from)
{
rc = get_cached_data (app, data_objects[i].get_from,
&buffer, &buflen,
(data_objects[i].dont_cache
|| data_objects[i].get_immediate_in_v11),
data_objects[i].try_extlen);
if (!rc)
{
const unsigned char *s;
s = find_tlv_unchecked (buffer, buflen, tag, &valuelen);
if (!s)
value = NULL; /* not found */
else if (valuelen > buflen - (s - buffer))
{
log_error ("warning: constructed DO too short\n");
value = NULL;
xfree (buffer); buffer = NULL;
}
else
value = buffer + (s - buffer);
}
}
if (!value) /* Not in a constructed DO, try simple. */
{
rc = get_cached_data (app, tag, &buffer, &buflen,
(data_objects[i].dont_cache
|| data_objects[i].get_immediate_in_v11),
data_objects[i].try_extlen);
if (!rc)
{
value = buffer;
valuelen = buflen;
}
}
if (!rc)
{
*nbytes = valuelen;
*result = value;
return buffer;
}
*r_rc = rc;
return NULL;
}
static void
dump_all_do (int slot)
{
int rc, i, j;
unsigned char *buffer;
size_t buflen;
for (i=0; data_objects[i].tag; i++)
{
if (data_objects[i].get_from)
continue;
/* We don't try extended length APDU because such large DO would
be pretty useless in a log file. */
rc = iso7816_get_data (slot, 0, data_objects[i].tag, &buffer, &buflen);
if (gpg_err_code (rc) == GPG_ERR_NO_OBJ)
;
else if (rc)
log_info ("DO '%s' not available: %s\n",
data_objects[i].desc, gpg_strerror (rc));
else
{
if (data_objects[i].binary)
{
log_info ("DO '%s': ", data_objects[i].desc);
log_printhex (buffer, buflen, "");
}
else
log_info ("DO '%s': '%.*s'\n",
data_objects[i].desc,
(int)buflen, buffer); /* FIXME: sanitize */
if (data_objects[i].constructed)
{
for (j=0; data_objects[j].tag; j++)
{
const unsigned char *value;
size_t valuelen;
if (j==i || data_objects[i].tag != data_objects[j].get_from)
continue;
value = find_tlv_unchecked (buffer, buflen,
data_objects[j].tag, &valuelen);
if (!value)
; /* not found */
else if (valuelen > buflen - (value - buffer))
log_error ("warning: constructed DO too short\n");
else
{
if (data_objects[j].binary)
{
log_info ("DO '%s': ", data_objects[j].desc);
if (valuelen > 200)
log_info ("[%u]\n", (unsigned int)valuelen);
else
log_printhex (value, valuelen, "");
}
else
log_info ("DO '%s': '%.*s'\n",
data_objects[j].desc,
(int)valuelen, value); /* FIXME: sanitize */
}
}
}
}
xfree (buffer); buffer = NULL;
}
}
/* Count the number of bits, assuming the A represents an unsigned big
integer of length LEN bytes. */
static unsigned int
count_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
for (; len && !*a; len--, a++, n -=8)
;
if (len)
{
for (i=7; i && !(*a & (1<<i)); i--)
n--;
}
return n;
}
static unsigned int
count_sos_bits (const unsigned char *a, size_t len)
{
unsigned int n = len * 8;
int i;
if (len == 0 || *a == 0)
return n;
for (i=7; i && !(*a & (1<<i)); i--)
n--;
return n;
}
/* GnuPG makes special use of the login-data DO, this function parses
the login data to store the flags for later use. It may be called
at any time and should be called after changing the login-data DO.
Everything up to a LF is considered a mailbox or account name. If
the first LF is followed by DC4 (0x14) control sequence are
expected up to the next LF. Control sequences are separated by FS
(0x18) and consist of key=value pairs. There are two keys defined:
F=<flags>
Where FLAGS is a plain hexadecimal number representing flag values.
The lsb is here the rightmost bit. Defined flags bits are:
Bit 0 = CHV1 and CHV2 are not synchronized
Bit 1 = CHV2 has been set to the default PIN of "123456"
(this implies that bit 0 is also set).
P=<pinpad-request>
Where PINPAD_REQUEST is in the format of: <n> or <n>,<m>.
N for user PIN, M for admin PIN. If M is missing it means M=N.
0 means to force not to use pinpad.
*/
static void
parse_login_data (app_t app)
{
unsigned char *buffer, *p;
size_t buflen, len;
void *relptr;
/* Set defaults. */
app->app_local->flags.no_sync = 0;
app->app_local->flags.def_chv2 = 0;
app->app_local->pinpad.specified = 0;
app->app_local->pinpad.fixedlen_user = -1;
app->app_local->pinpad.fixedlen_admin = -1;
/* Read the DO. */
relptr = get_one_do (app, 0x005E, &buffer, &buflen, NULL);
if (!relptr)
return; /* Ooops. */
for (; buflen; buflen--, buffer++)
if (*buffer == '\n')
break;
if (buflen < 2 || buffer[1] != '\x14')
{
xfree (relptr);
return; /* No control sequences. */
}
buflen--;
buffer++;
do
{
buflen--;
buffer++;
if (buflen > 1 && *buffer == 'F' && buffer[1] == '=')
{
/* Flags control sequence found. */
int lastdig = 0;
/* For now we are only interested in the last digit, so skip
any leading digits but bail out on invalid characters. */
for (p=buffer+2, len = buflen-2; len && hexdigitp (p); p++, len--)
lastdig = xtoi_1 (p);
buffer = p;
buflen = len;
if (len && !(*p == '\n' || *p == '\x18'))
goto next; /* Invalid characters in field. */
app->app_local->flags.no_sync = !!(lastdig & 1);
app->app_local->flags.def_chv2 = (lastdig & 3) == 3;
}
else if (buflen > 1 && *buffer == 'P' && buffer[1] == '=')
{
/* Pinpad request control sequence found. */
buffer += 2;
buflen -= 2;
if (buflen)
{
if (digitp (buffer))
{
char *q;
int n, m;
n = strtol (buffer, &q, 10);
if (q >= (char *)buffer + buflen
|| *q == '\x18' || *q == '\n')
m = n;
else
{
if (*q++ != ',' || !digitp (q))
goto next;
m = strtol (q, &q, 10);
}
if (buflen < ((unsigned char *)q - buffer))
break;
buflen -= ((unsigned char *)q - buffer);
buffer = q;
if (buflen && !(*buffer == '\n' || *buffer == '\x18'))
goto next;
app->app_local->pinpad.specified = 1;
app->app_local->pinpad.fixedlen_user = n;
app->app_local->pinpad.fixedlen_admin = m;
}
}
}
next:
/* Skip to FS (0x18) or LF (\n). */
for (; buflen && *buffer != '\x18' && *buffer != '\n'; buflen--)
buffer++;
}
while (buflen && *buffer != '\n');
xfree (relptr);
}
#define MAX_ARGS_STORE_FPR 3
/* Note, that FPR must be at least 20 bytes. If UPDATE is not set,
* the fingerprint and the creation date is not actually stored but
* the fingerprint is only returned in FPR. */
static gpg_error_t
store_fpr (app_t app, int update, int keynumber, u32 timestamp,
unsigned char *fpr, int algo, ...)
{
unsigned int n, nbits;
unsigned char *buffer, *p;
int tag, tag2;
int rc;
const unsigned char *m[MAX_ARGS_STORE_FPR];
size_t mlen[MAX_ARGS_STORE_FPR];
va_list ap;
int argc;
int i;
n = 6; /* key packet version, 4-byte timestamps, and algorithm */
if (algo == PUBKEY_ALGO_ECDH)
argc = 3;
else
argc = 2;
va_start (ap, algo);
for (i = 0; i < argc; i++)
{
m[i] = va_arg (ap, const unsigned char *);
mlen[i] = va_arg (ap, size_t);
if (algo == PUBKEY_ALGO_RSA || i == 1)
n += 2;
n += mlen[i];
}
va_end (ap);
p = buffer = xtrymalloc (3 + n);
if (!buffer)
return gpg_error_from_syserror ();
*p++ = 0x99; /* ctb */
*p++ = n >> 8; /* 2 byte length header */
*p++ = n;
*p++ = 4; /* key packet version */
*p++ = timestamp >> 24;
*p++ = timestamp >> 16;
*p++ = timestamp >> 8;
*p++ = timestamp;
*p++ = algo;
for (i = 0; i < argc; i++)
{
if (algo == PUBKEY_ALGO_RSA)
{
nbits = count_bits (m[i], mlen[i]);
*p++ = nbits >> 8;
*p++ = nbits;
}
else if (i == 1)
{
nbits = count_sos_bits (m[i], mlen[i]);
*p++ = nbits >> 8;
*p++ = nbits;
}
memcpy (p, m[i], mlen[i]);
p += mlen[i];
}
gcry_md_hash_buffer (GCRY_MD_SHA1, fpr, buffer, n+3);
xfree (buffer);
if (!update)
return 0;
tag = (app->appversion > 0x0007? 0xC7 : 0xC6) + keynumber;
flush_cache_item (app, 0xC5);
tag2 = 0xCE + keynumber;
flush_cache_item (app, 0xCD);
rc = iso7816_put_data (app_get_slot (app), 0, tag, fpr, 20);
if (rc)
log_error (_("failed to store the fingerprint: %s\n"),gpg_strerror (rc));
if (!rc && app->appversion > 0x0100)
{
unsigned char buf[4];
buf[0] = timestamp >> 24;
buf[1] = timestamp >> 16;
buf[2] = timestamp >> 8;
buf[3] = timestamp;
rc = iso7816_put_data (app_get_slot (app), 0, tag2, buf, 4);
if (rc)
log_error (_("failed to store the creation date: %s\n"),
gpg_strerror (rc));
}
return rc;
}
static void
send_fpr_if_not_null (ctrl_t ctrl, const char *keyword,
int number, const unsigned char *fpr)
{
int i;
char buf[41];
char numbuf[25];
for (i=0; i < 20 && !fpr[i]; i++)
;
if (i==20)
return; /* All zero. */
bin2hex (fpr, 20, buf);
if (number == -1)
*numbuf = 0; /* Don't print the key number */
else
sprintf (numbuf, "%d", number);
send_status_info (ctrl, keyword,
numbuf, (size_t)strlen(numbuf),
buf, (size_t)strlen (buf), NULL, 0);
}
static void
send_fprtime_if_not_null (ctrl_t ctrl, const char *keyword,
int number, const unsigned char *stamp)
{
char numbuf1[50], numbuf2[50];
unsigned long value;
value = buf32_to_ulong (stamp);
if (!value)
return;
sprintf (numbuf1, "%d", number);
sprintf (numbuf2, "%lu", value);
send_status_info (ctrl, keyword,
numbuf1, (size_t)strlen(numbuf1),
numbuf2, (size_t)strlen(numbuf2), NULL, 0);
}
static void
send_key_data (ctrl_t ctrl, const char *name,
const unsigned char *a, size_t alen)
{
char *buffer, *buf;
size_t buflen;
buffer = buf = bin2hex (a, alen, NULL);
if (!buffer)
{
log_error ("memory allocation error in send_key_data\n");
return;
}
buflen = strlen (buffer);
/* 768 is the hexified size for the modulus of an 3072 bit key. We
use extra chunks to transmit larger data (i.e for 4096 bit). */
for ( ;buflen > 768; buflen -= 768, buf += 768)
send_status_info (ctrl, "KEY-DATA",
"-", 1,
buf, 768,
NULL, 0);
send_status_info (ctrl, "KEY-DATA",
name, (size_t)strlen(name),
buf, buflen,
NULL, 0);
xfree (buffer);
}
static void
send_key_attr (ctrl_t ctrl, app_t app, const char *keyword, int keyno)
{
char buffer[200];
log_assert (keyno >=0 && keyno < DIM(app->app_local->keyattr));
/* Note that the code in gpg-card supports prefixing the key number
* with "OPENPGP." but older code does not yet support this. There
* is also a discrepancy with the algorithm numbers: We should use
* the gcrypt numbers but the current code assumes OpenPGP numbers. */
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
snprintf (buffer, sizeof buffer, "%d 1 rsa%u %u %d",
keyno+1,
app->app_local->keyattr[keyno].rsa.n_bits,
app->app_local->keyattr[keyno].rsa.e_bits,
app->app_local->keyattr[keyno].rsa.format);
else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
{
snprintf (buffer, sizeof buffer, "%d %d %s",
keyno+1,
app->app_local->keyattr[keyno].ecc.algo,
app->app_local->keyattr[keyno].ecc.curve);
}
else
snprintf (buffer, sizeof buffer, "%d 0 0 UNKNOWN", keyno+1);
send_status_direct (ctrl, keyword, buffer);
}
#define RSA_SMALL_SIZE_KEY 1952
#define RSA_SMALL_SIZE_OP 2048
static int
determine_rsa_response (app_t app, int keyno)
{
int size;
size = 2 + 3 /* header */
+ 4 /* tag+len */ + (app->app_local->keyattr[keyno].rsa.n_bits+7)/8
+ 2 /* tag+len */ + (app->app_local->keyattr[keyno].rsa.e_bits+7)/8;
return size;
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int tag;
int special;
} table[] = {
{ "DISP-NAME", 0x005B },
{ "LOGIN-DATA", 0x005E },
{ "DISP-LANG", 0x5F2D },
{ "DISP-SEX", 0x5F35 },
{ "PUBKEY-URL", 0x5F50 },
{ "KEY-FPR", 0x00C5, 3 },
{ "KEY-TIME", 0x00CD, 4 },
{ "KEY-ATTR", 0x0000, -5 },
{ "CA-FPR", 0x00C6, 3 },
{ "CHV-STATUS", 0x00C4, 1 },
{ "SIG-COUNTER", 0x0093, 2 },
{ "SERIALNO", 0x004F, -1 },
{ "AID", 0x004F },
{ "EXTCAP", 0x0000, -2 },
{ "PRIVATE-DO-1", 0x0101 },
{ "PRIVATE-DO-2", 0x0102 },
{ "PRIVATE-DO-3", 0x0103 },
{ "PRIVATE-DO-4", 0x0104 },
{ "$AUTHKEYID", 0x0000, -3 },
{ "$ENCRKEYID", 0x0000, -6 },
{ "$SIGNKEYID", 0x0000, -7 },
{ "$DISPSERIALNO",0x0000, -4 },
{ "UIF-1", 0x00D6, 0 },
{ "UIF-2", 0x00D7, 0 },
{ "UIF-3", 0x00D8, 0 },
{ "KDF", 0x00F9, 5 },
{ "MANUFACTURER", 0x0000, -8 },
{ "UIF", 0x0000, -9 }, /* Shortcut for all UIF */
{ "KEY-STATUS", 0x00DE, 6 },
{ "KEY-ATTR-INFO", 0x00FA, 7 },
{ NULL, 0 }
};
int idx, i, rc;
void *relptr;
unsigned char *value;
size_t valuelen;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
if (table[idx].special == -1)
{
/* The serial number is very special. We can't use the AID
DO (0x4f) because this is the serialno per specs with the
correct appversion. We might however use a serialno with the
version set to 0.0 and that is what we need to return. */
char *serial = app_get_serialno (app);
if (serial)
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
return 0;
}
if (table[idx].special == -2)
{
char tmp[110];
/* Noet that with v3 cards mcl3 is used for all certificates. */
snprintf (tmp, sizeof tmp,
"gc=%d ki=%d fc=%d pd=%d mcl3=%u aac=%d "
"sm=%d si=%u dec=%d bt=%d kdf=%d",
app->app_local->extcap.get_challenge,
app->app_local->extcap.key_import,
app->app_local->extcap.change_force_chv,
app->app_local->extcap.private_dos,
app->app_local->extcap.max_certlen,
app->app_local->extcap.algo_attr_change,
(app->app_local->extcap.sm_supported
? (app->app_local->extcap.sm_algo == 0? CIPHER_ALGO_3DES :
(app->app_local->extcap.sm_algo == 1?
CIPHER_ALGO_AES : CIPHER_ALGO_AES256))
: 0),
app->app_local->status_indicator,
app->app_local->extcap.has_decrypt,
app->app_local->extcap.has_button,
app->app_local->extcap.kdf_do);
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -3)
{
char const tmp[] = "OPENPGP.3";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -4)
{
char *serial;
if ((serial = app_get_dispserialno (app, 0)))
{
send_status_info (ctrl, table[idx].name,
serial, strlen (serial), NULL, 0);
xfree (serial);
return 0;
}
return gpg_error (GPG_ERR_INV_NAME);
}
if (table[idx].special == -5)
{
for (i=0; i < 3; i++)
send_key_attr (ctrl, app, table[idx].name, i);
return 0;
}
if (table[idx].special == -6)
{
char const tmp[] = "OPENPGP.2";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -7)
{
char const tmp[] = "OPENPGP.1";
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
return 0;
}
if (table[idx].special == -8)
{
return send_status_printf
(ctrl, table[idx].name, "%u %s",
app->app_local->manufacturer,
get_manufacturer (app->app_local->manufacturer));
}
if (table[idx].special == -9)
{
rc = do_getattr (app, ctrl, "UIF-1");
if (!rc)
rc = do_getattr (app, ctrl, "UIF-2");
if (!rc)
rc = do_getattr (app, ctrl, "UIF-3");
return rc;
}
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &rc);
if (relptr)
{
if (table[idx].special == 1)
{
char numbuf[7*23];
for (i=0,*numbuf=0; i < valuelen && i < 7; i++)
sprintf (numbuf+strlen (numbuf), " %d", value[i]);
send_status_info (ctrl, table[idx].name,
numbuf, strlen (numbuf), NULL, 0);
}
else if (table[idx].special == 2)
{
char numbuf[50];
sprintf (numbuf, "%lu", convert_sig_counter_value (value, valuelen));
send_status_info (ctrl, table[idx].name,
numbuf, strlen (numbuf), NULL, 0);
}
else if (table[idx].special == 3)
{
if (valuelen >= 60)
for (i=0; i < 3; i++)
send_fpr_if_not_null (ctrl, table[idx].name, i+1, value+i*20);
}
else if (table[idx].special == 4)
{
if (valuelen >= 12)
for (i=0; i < 3; i++)
send_fprtime_if_not_null (ctrl, table[idx].name, i+1, value+i*4);
}
else if (table[idx].special == 5)
{
if ((valuelen == KDF_DATA_LENGTH_MIN
|| valuelen == KDF_DATA_LENGTH_MAX)
&& (value[2] == 0x03))
app->app_local->pinpad.disabled = 1;
else
app->app_local->pinpad.disabled = 0;
send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
}
else if (table[idx].special == 6)
{
for (i=0,rc=0; !rc && i+1 < valuelen; i += 2)
rc = send_status_printf (ctrl, table[idx].name, "OPENPGP.%u %u",
value[i], value[i+1]);
if (gpg_err_code (rc) == GPG_ERR_NO_OBJ)
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else if (table[idx].special == 7)
{
const unsigned char *p = value;
int tag;
size_t len;
if (valuelen < 2)
return gpg_error (GPG_ERR_INV_OBJ);
tag = p[0];
len = p[1];
/* Does it comes tag+len at the head? */
if (tag == 0x00FA)
{
p += 2;
if (len == 0x81)
{
if (valuelen < 3)
return gpg_error (GPG_ERR_INV_OBJ);
len = *p++;
}
else if (len == 0x82)
{
if (valuelen < 4)
return gpg_error (GPG_ERR_INV_OBJ);
len = *p++;
len = (len << 8) | *p++;
}
valuelen -= (p - value);
value = (unsigned char *)p;
if (valuelen != len)
{
if (opt.verbose)
log_info ("Yubikey bug: length %zu != %zu", valuelen, len);
if (APP_CARD(app)->cardtype != CARDTYPE_YUBIKEY)
return gpg_error (GPG_ERR_INV_OBJ);
}
}
for (; p < value + valuelen; p += len)
{
const char *key_algo_str;
int keyrefno;
if (p + 2 > value + valuelen)
break;
tag = *p++;
len = *p++;
if (tag < 0xc1)
continue;
if (tag == 0xda)
keyrefno = 0x81;
else
keyrefno = tag - 0xc1 + 1;
if (p + len > value + valuelen)
break;
key_algo_str = get_algorithm_attribute_string (p, len);
send_status_printf (ctrl, table[idx].name, "OPENPGP.%u %s",
keyrefno, key_algo_str);
}
}
else
send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
xfree (relptr);
}
else
{
if (table[idx].special == 5)
app->app_local->pinpad.disabled = 0;
}
return rc;
}
/* Return the DISP-NAME without any padding characters. Caller must
* free the result. If not found or empty NULL is returned. */
static char *
get_disp_name (app_t app)
{
int rc;
void *relptr;
unsigned char *value;
size_t valuelen;
char *string;
char *p, *given;
char *result;
relptr = get_one_do (app, 0x005B, &value, &valuelen, &rc);
if (!relptr)
return NULL;
string = xtrymalloc (valuelen + 1);
if (!string)
{
xfree (relptr);
return NULL;
}
memcpy (string, value, valuelen);
string[valuelen] = 0;
xfree (relptr);
/* Swap surname and given name. */
given = strstr (string, "<<");
for (p = string; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
result = strconcat (given, " ", string, NULL);
}
else
{
result = string;
string = NULL;
}
xfree (string);
return result;
}
/* Return the number of remaining tries for the standard or the admin
* pw. Returns -1 on card error. */
static int
get_remaining_tries (app_t app, int adminpw)
{
void *relptr;
unsigned char *value;
size_t valuelen;
int remaining;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
return -1;
}
remaining = value[adminpw? 6 : 4];
xfree (relptr);
return remaining;
}
/* Retrieve the fingerprint from the card inserted in SLOT and write
the according hex representation to FPR. Caller must have provide
a buffer at FPR of least 41 bytes. Returns 0 on success or an
error code. */
static gpg_error_t
retrieve_fpr_from_card (app_t app, int keyno, char *fpr)
{
gpg_error_t err = 0;
void *relptr;
unsigned char *value;
size_t valuelen;
log_assert (keyno >=0 && keyno <= 2);
relptr = get_one_do (app, 0x00C5, &value, &valuelen, NULL);
if (relptr && valuelen >= 60)
bin2hex (value+keyno*20, 20, fpr);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
xfree (relptr);
return err;
}
/* Retrieve the creation time of the fingerprint for key KEYNO from
* the card inserted in the slot of APP and store it at R_FPRTIME.
* Returns 0 on success or an error code. */
static gpg_error_t
retrieve_fprtime_from_card (app_t app, int keyno, u32 *r_fprtime)
{
gpg_error_t err = 0;
void *relptr;
unsigned char *value;
size_t valuelen;
u32 fprtime;
log_assert (keyno >=0 && keyno <= 2);
relptr = get_one_do (app, 0x00CD, &value, &valuelen, NULL);
if (relptr && valuelen >= 4*(keyno+1))
{
fprtime = buf32_to_u32 (value + 4*keyno);
if (!fprtime)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
*r_fprtime = fprtime;
}
else
err = gpg_error (GPG_ERR_NOT_FOUND);
xfree (relptr);
return err;
}
/* Retrieve the public key material for the RSA key, whose fingerprint
is FPR, from gpg output, which can be read through the stream FP.
The RSA modulus will be stored at the address of M and MLEN, the
public exponent at E and ELEN. Returns zero on success, an error
code on failure. Caller must release the allocated buffers at M
and E if the function returns success. */
static gpg_error_t
retrieve_key_material (FILE *fp, const char *hexkeyid,
const unsigned char **m, size_t *mlen,
const unsigned char **e, size_t *elen)
{
gcry_error_t err = 0;
char *line = NULL; /* read_line() buffer. */
size_t line_size = 0; /* Helper for for read_line. */
int found_key = 0; /* Helper to find a matching key. */
unsigned char *m_new = NULL;
unsigned char *e_new = NULL;
size_t m_new_n = 0;
size_t e_new_n = 0;
/* Loop over all records until we have found the subkey
corresponding to the fingerprint. Inm general the first record
should be the pub record, but we don't rely on that. Given that
we only need to look at one key, it is sufficient to compare the
keyid so that we don't need to look at "fpr" records. */
for (;;)
{
char *p;
char *fields[6] = { NULL, NULL, NULL, NULL, NULL, NULL };
int nfields;
size_t max_length;
gcry_mpi_t mpi;
int i;
max_length = 4096;
i = read_line (fp, &line, &line_size, &max_length);
if (!i)
break; /* EOF. */
if (i < 0)
{
err = gpg_error_from_syserror ();
goto leave; /* Error. */
}
if (!max_length)
{
err = gpg_error (GPG_ERR_TRUNCATED);
goto leave; /* Line truncated - we better stop processing. */
}
/* Parse the line into fields. */
for (nfields=0, p=line; p && nfields < DIM (fields); nfields++)
{
fields[nfields] = p;
p = strchr (p, ':');
if (p)
*(p++) = 0;
}
if (!nfields)
continue; /* No fields at all - skip line. */
if (!found_key)
{
if ( (!strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
&& nfields > 4 && !strcmp (fields[4], hexkeyid))
found_key = 1;
continue;
}
if ( !strcmp (fields[0], "sub") || !strcmp (fields[0], "pub") )
break; /* Next key - stop. */
if ( strcmp (fields[0], "pkd") )
continue; /* Not a key data record. */
if ( nfields < 4 || (i = atoi (fields[1])) < 0 || i > 1
|| (!i && m_new) || (i && e_new))
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave; /* Error: Invalid key data record or not an RSA key. */
}
err = gcry_mpi_scan (&mpi, GCRYMPI_FMT_HEX, fields[3], 0, NULL);
if (err)
mpi = NULL;
else if (!i)
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &m_new, &m_new_n, mpi);
else
err = gcry_mpi_aprint (GCRYMPI_FMT_STD, &e_new, &e_new_n, mpi);
gcry_mpi_release (mpi);
if (err)
goto leave;
}
if (m_new && e_new)
{
*m = m_new;
*mlen = m_new_n;
m_new = NULL;
*e = e_new;
*elen = e_new_n;
e_new = NULL;
}
else
err = gpg_error (GPG_ERR_GENERAL);
leave:
xfree (m_new);
xfree (e_new);
xfree (line);
return err;
}
static gpg_error_t
rsa_read_pubkey (app_t app, ctrl_t ctrl, int meta_update,
u32 created_at, int keyno,
const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *m, *e;
size_t mlen, elen;
unsigned char *mbuf = NULL, *ebuf = NULL;
m = find_tlv (data, datalen, 0x0081, &mlen);
if (!m)
{
log_error (_("response does not contain the RSA modulus\n"));
return gpg_error (GPG_ERR_CARD);
}
e = find_tlv (data, datalen, 0x0082, &elen);
if (!e)
{
log_error (_("response does not contain the RSA public exponent\n"));
return gpg_error (GPG_ERR_CARD);
}
if (ctrl)
{
send_key_data (ctrl, "n", m, mlen);
send_key_data (ctrl, "e", e, elen);
}
for (; mlen && !*m; mlen--, m++) /* strip leading zeroes */
;
for (; elen && !*e; elen--, e++) /* strip leading zeroes */
;
if (ctrl)
{
unsigned char fprbuf[20];
/* If META_UPDATE is not set we only compute but not store the
* fingerprint. This might return a wrong fingerprint if
* CREATED_AT is not set. */
err = store_fpr (app, meta_update, keyno,
created_at, fprbuf, PUBKEY_ALGO_RSA,
m, mlen, e, elen);
if (err)
return err;
send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
}
mbuf = xtrymalloc (mlen + 1);
if (!mbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (mlen && (*m & 0x80))
{
*mbuf = 0;
memcpy (mbuf+1, m, mlen);
mlen++;
}
else
memcpy (mbuf, m, mlen);
ebuf = xtrymalloc (elen + 1);
if (!ebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (elen && (*e & 0x80))
{
*ebuf = 0;
memcpy (ebuf+1, e, elen);
elen++;
}
else
memcpy (ebuf, e, elen);
err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, mbuf, (int)elen, ebuf);
leave:
xfree (mbuf);
xfree (ebuf);
return err;
}
/* Determine KDF hash algorithm and KEK encryption algorithm by CURVE. */
static const unsigned char*
ecdh_params (const char *curve)
{
unsigned int nbits;
openpgp_curve_to_oid (curve, &nbits, NULL);
/* See RFC-6637 for those constants.
0x03: Number of bytes
0x01: Version for this parameter format
KEK digest algorithm
KEK cipher algorithm
*/
if (nbits <= 256)
return (const unsigned char*)"\x03\x01\x08\x07";
else if (nbits <= 384)
return (const unsigned char*)"\x03\x01\x09\x09";
else
return (const unsigned char*)"\x03\x01\x0a\x09";
}
static gpg_error_t
ecc_read_pubkey (app_t app, ctrl_t ctrl, int meta_update,
u32 created_at, int keyno,
const unsigned char *data, size_t datalen, gcry_sexp_t *r_sexp)
{
gpg_error_t err;
unsigned char *qbuf = NULL;
const unsigned char *ecc_q;
size_t ecc_q_len;
gcry_mpi_t oid = NULL;
int n;
const char *curve;
const char *oidstr;
const unsigned char *oidbuf;
size_t oid_len;
int algo;
const char *format;
ecc_q = find_tlv (data, datalen, 0x0086, &ecc_q_len);
if (!ecc_q)
{
log_error (_("response does not contain the EC public key\n"));
return gpg_error (GPG_ERR_CARD);
}
curve = app->app_local->keyattr[keyno].ecc.curve;
oidstr = openpgp_curve_to_oid (curve, NULL, NULL);
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
return err;
oidbuf = gcry_mpi_get_opaque (oid, &n);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
oid_len = (n+7)/8;
qbuf = xtrymalloc (ecc_q_len + 1);
if (!qbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
{ /* Prepend 0x40 prefix. */
*qbuf = 0x40;
memcpy (qbuf+1, ecc_q, ecc_q_len);
ecc_q_len++;
}
else
memcpy (qbuf, ecc_q, ecc_q_len);
if (ctrl)
{
send_key_data (ctrl, "q", qbuf, ecc_q_len);
send_key_data (ctrl, "curve", oidbuf, oid_len);
}
algo = app->app_local->keyattr[keyno].ecc.algo;
if (keyno == 1)
{
if (ctrl)
send_key_data (ctrl, "kdf/kek", ecdh_params (curve), (size_t)4);
}
if (ctrl)
{
unsigned char fprbuf[20];
/* If META_UPDATE is not set we only compute but not store the
* fingerprint. This might return a wrong fingerprint if
* CREATED_AT is not set or the ECDH params do not match the
* current defaults. */
err = store_fpr (app, meta_update, keyno,
created_at, fprbuf, algo, oidbuf, oid_len,
qbuf, ecc_q_len, ecdh_params (curve), (size_t)4);
if (err)
goto leave;
send_fpr_if_not_null (ctrl, "KEY-FPR", -1, fprbuf);
}
if (!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK))
format = "(public-key(ecc(curve%s)(q%b)))";
else if (keyno == 1)
format = "(public-key(ecc(curve%s)(flags djb-tweak)(q%b)))";
else
format = "(public-key(ecc(curve%s)(flags eddsa)(q%b)))";
err = gcry_sexp_build (r_sexp, NULL, format,
app->app_local->keyattr[keyno].ecc.curve,
(int)ecc_q_len, qbuf);
leave:
gcry_mpi_release (oid);
xfree (qbuf);
return err;
}
static gpg_error_t
store_keygrip (app_t app, int keyno)
{
gpg_error_t err;
unsigned char grip[20];
err = keygrip_from_canon_sexp (app->app_local->pk[keyno].key,
app->app_local->pk[keyno].keylen,
grip);
if (err)
return err;
bin2hex (grip, 20, app->app_local->pk[keyno].keygrip_str);
return 0;
}
/* Parse tag-length-value data for public key in BUFFER of BUFLEN
* length. Key of KEYNO in APP is updated with an S-expression of
* public key. If CTRL is not NULL, the fingerprint is computed with
* CREATED_AT and key data and fingerprint are send back to the client
* side. If also META_UPDATE is true the fingerprint and the creation
* date are also written to the card.
*/
static gpg_error_t
read_public_key (app_t app, ctrl_t ctrl, int meta_update,
u32 created_at, int keyno,
const unsigned char *buffer, size_t buflen)
{
gpg_error_t err;
const unsigned char *data;
size_t datalen;
gcry_sexp_t s_pkey = NULL;
data = find_tlv (buffer, buflen, 0x7F49, &datalen);
if (!data)
{
log_error (_("response does not contain the public key data\n"));
return gpg_error (GPG_ERR_CARD);
}
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
err = rsa_read_pubkey (app, ctrl, meta_update, created_at, keyno,
data, datalen, &s_pkey);
else if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_ECC)
err = ecc_read_pubkey (app, ctrl, meta_update, created_at, keyno,
data, datalen, &s_pkey);
else
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
if (!err)
{
unsigned char *keybuf;
size_t len;
len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
keybuf = xtrymalloc (len);
if (!keybuf)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_pkey);
return err;
}
gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
gcry_sexp_release (s_pkey);
app->app_local->pk[keyno].key = keybuf;
/* Decrement for trailing '\0' */
app->app_local->pk[keyno].keylen = len - 1;
err = store_keygrip (app, keyno);
}
return err;
}
/* Get the public key for KEYNO and store it as an S-expression with
the APP handle. On error that field gets cleared. If we already
know about the public key we will just return. Note that this does
not mean a key is available; this is solely indicated by the
presence of the app->app_local->pk[KEYNO].key field.
Note that GnuPG 1.x does not need this and it would be too time
consuming to send it just for the fun of it. However, given that we
use the same code in gpg 1.4, we can't use the gcry S-expression
here but need to open encode it. */
static gpg_error_t
get_public_key (app_t app, int keyno)
{
gpg_error_t err = 0;
unsigned char *buffer;
const unsigned char *m, *e;
size_t buflen;
size_t mlen = 0;
size_t elen = 0;
char *keybuf = NULL;
gcry_sexp_t s_pkey;
size_t len;
if (keyno < 0 || keyno > 2)
return gpg_error (GPG_ERR_INV_ID);
/* Already cached? */
if (app->app_local->pk[keyno].read_done)
return 0;
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
m = e = NULL; /* (avoid cc warning) */
if (app->appversion > 0x0100)
{
int exmode, le_value;
/* We may simply read the public key out of these cards. */
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[keyno].rsa.n_bits > RSA_SMALL_SIZE_KEY)
{
exmode = 1; /* Use extended length. */
le_value = determine_rsa_response (app, keyno);
}
else
{
exmode = 0;
le_value = 256; /* Use legacy value. */
}
err = iso7816_read_public_key (app_get_slot (app), exmode,
(keyno == 0? "\xB6" :
keyno == 1? "\xB8" : "\xA4"),
2, le_value, &buffer, &buflen);
if (err)
{
/* Yubikey returns wrong code. Fix it up. */
if (APP_CARD(app)->cardtype == CARDTYPE_YUBIKEY)
err = gpg_error (GPG_ERR_NO_OBJ);
/* Yubikey NEO (!CARDTYPE_YUBIKEY) also returns wrong code.
* Fix it up. */
else if (gpg_err_code (err) == GPG_ERR_CARD)
err = gpg_error (GPG_ERR_NO_OBJ);
log_error (_("reading public key failed: %s\n"), gpg_strerror (err));
goto leave;
}
/* Note that we use 0 for the creation date and thus the - via
* status lines - returned fingerprint will only be valid if the
* key has also been created with that date. A similar problem
* occurs with the ECDH params which are fixed in the code. */
err = read_public_key (app, NULL, 0, 0U, keyno, buffer, buflen);
}
else
{
/* Due to a design problem in v1.0 cards we can't get the public
key out of these cards without doing a verify on CHV3.
Clearly that is not an option and thus we try to locate the
key using an external helper.
The helper we use here is gpg itself, which should know about
the key in any case. */
char fpr[41];
char *hexkeyid;
char *command = NULL;
FILE *fp;
int ret;
buffer = NULL; /* We don't need buffer. */
err = retrieve_fpr_from_card (app, keyno, fpr);
if (err)
{
log_error ("error while retrieving fpr from card: %s\n",
gpg_strerror (err));
goto leave;
}
hexkeyid = fpr + 24;
ret = gpgrt_asprintf
(&command, "%s --list-keys --with-colons --with-key-data '%s'",
gnupg_module_name (GNUPG_MODULE_NAME_GPG), fpr);
if (ret < 0)
{
err = gpg_error_from_syserror ();
goto leave;
}
fp = popen (command, "r");
xfree (command);
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("running gpg failed: %s\n", gpg_strerror (err));
goto leave;
}
err = retrieve_key_material (fp, hexkeyid, &m, &mlen, &e, &elen);
pclose (fp);
if (err)
{
log_error ("error while retrieving key material through pipe: %s\n",
gpg_strerror (err));
goto leave;
}
err = gcry_sexp_build (&s_pkey, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, m, (int)elen, e);
if (err)
goto leave;
len = gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, NULL, 0);
keybuf = xtrymalloc (len);
if (!keybuf)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_pkey);
goto leave;
}
gcry_sexp_sprint (s_pkey, GCRYSEXP_FMT_CANON, keybuf, len);
gcry_sexp_release (s_pkey);
app->app_local->pk[keyno].key = (unsigned char*)keybuf;
/* Decrement for trailing '\0' */
app->app_local->pk[keyno].keylen = len - 1;
err = store_keygrip (app, keyno);
}
leave:
/* Set a flag to indicate that we tried to read the key. */
if (!err)
app->app_local->pk[keyno].read_done = 1;
xfree (buffer);
return err;
}
static const char *
get_usage_string (int keyno)
{
const char *usage;
switch (keyno)
{
case 0: usage = "sc"; break;
case 1: usage = "e"; break;
case 2: usage = "sa"; break;
default: usage = "-"; break;
}
return usage;
}
/* Send the KEYPAIRINFO back. KEY needs to be in the range [1,3].
This is used by the LEARN command. */
static gpg_error_t
send_keypair_info (app_t app, ctrl_t ctrl, int key)
{
int keyno = key - 1;
gpg_error_t err = 0;
const char *usage;
u32 fprtime;
char *algostr = NULL;
err = get_public_key (app, keyno);
if (err)
goto leave;
log_assert (keyno >= 0 && keyno <= 2);
if (!app->app_local->pk[keyno].key)
goto leave; /* No such key - ignore. */
usage = get_usage_string (keyno);
if (retrieve_fprtime_from_card (app, keyno, &fprtime))
fprtime = 0;
{
gcry_sexp_t s_pkey;
if (gcry_sexp_new (&s_pkey, app->app_local->pk[keyno].key,
app->app_local->pk[keyno].keylen, 0))
algostr = xtrystrdup ("?");
else
{
algostr = pubkey_algo_string (s_pkey, NULL);
gcry_sexp_release (s_pkey);
}
}
if (!algostr)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = send_status_printf (ctrl, "KEYPAIRINFO", "%s OPENPGP.%d %s %lu %s",
app->app_local->pk[keyno].keygrip_str,
keyno+1, usage, (unsigned long)fprtime, algostr);
leave:
xfree (algostr);
return err;
}
/* Handle the LEARN command for OpenPGP. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err = 0;
(void)flags;
err = do_getattr (app, ctrl, "EXTCAP");
if (!err)
err = do_getattr (app, ctrl, "MANUFACTURER");
if (!err)
err = do_getattr (app, ctrl, "DISP-NAME");
if (!err)
err = do_getattr (app, ctrl, "DISP-LANG");
if (!err)
err = do_getattr (app, ctrl, "DISP-SEX");
if (!err)
err = do_getattr (app, ctrl, "PUBKEY-URL");
if (!err)
err = do_getattr (app, ctrl, "LOGIN-DATA");
if (!err)
err = do_getattr (app, ctrl, "KEY-FPR");
if (!err && app->appversion > 0x0100)
err = do_getattr (app, ctrl, "KEY-TIME");
if (!err)
err = do_getattr (app, ctrl, "CA-FPR");
if (!err)
err = do_getattr (app, ctrl, "CHV-STATUS");
if (!err)
err = do_getattr (app, ctrl, "SIG-COUNTER");
if (!err && app->app_local->extcap.kdf_do)
{
err = do_getattr (app, ctrl, "KDF");
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
}
if (!err && app->app_local->extcap.has_button)
err = do_getattr (app, ctrl, "UIF");
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
if (!err && app->app_local->extcap.private_dos)
{
if (!err)
err = do_getattr (app, ctrl, "PRIVATE-DO-1");
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
if (!err)
err = do_getattr (app, ctrl, "PRIVATE-DO-2");
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
if (!err && app->did_chv2)
err = do_getattr (app, ctrl, "PRIVATE-DO-3");
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
if (!err && app->did_chv3)
err = do_getattr (app, ctrl, "PRIVATE-DO-4");
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
}
if (!err)
err = send_keypair_info (app, ctrl, 1);
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
if (!err)
err = send_keypair_info (app, ctrl, 2);
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
if (!err)
err = send_keypair_info (app, ctrl, 3);
if (gpg_err_code (err) == GPG_ERR_NO_OBJ)
err = 0;
/* Note: We do not send the Cardholder Certificate, because that is
relatively long and for OpenPGP applications not really needed. */
return err;
}
/* Handle the READKEY command for OpenPGP. On success a canonical
encoded S-expression with the public key will get stored at PK and
its length (for assertions) at PKLEN; the caller must release that
buffer. On error PK and PKLEN are not changed and an error code is
returned. */
static gpg_error_t
do_readkey (app_t app, ctrl_t ctrl, const char *keyid, unsigned int flags,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
int keyno;
unsigned char *buf;
if (strlen (keyid) == 40)
{
const unsigned char *keygrip_str;
for (keyno = 0; keyno < 3; keyno++)
{
keygrip_str = app->app_local->pk[keyno].keygrip_str;
if (!strncmp (keygrip_str, keyid, 40))
break;
}
if (keyno >= 3)
return gpg_error (GPG_ERR_INV_ID);
}
else if (!ascii_strcasecmp (keyid, "OPENPGP.1"))
keyno = 0;
else if (!ascii_strcasecmp (keyid, "OPENPGP.2"))
keyno = 1;
else if (!ascii_strcasecmp (keyid, "OPENPGP.3"))
keyno = 2;
else
return gpg_error (GPG_ERR_INV_ID);
err = get_public_key (app, keyno);
if (err)
return err;
buf = app->app_local->pk[keyno].key;
if (!buf)
return gpg_error (GPG_ERR_NO_PUBKEY);
if ((flags & APP_READKEY_FLAG_INFO))
{
err = send_keypair_info (app, ctrl, keyno+1);
if (err)
return err;
}
if (pk && pklen)
{
*pklen = app->app_local->pk[keyno].keylen;
*pk = xtrymalloc (*pklen);
if (!*pk)
{
err = gpg_error_from_syserror ();
*pklen = 0;
return err;
}
memcpy (*pk, buf, *pklen);
}
return 0;
}
/* Read the standard certificate of an OpenPGP v2 card. It is
returned in a freshly allocated buffer with that address stored at
CERT and the length of the certificate stored at CERTLEN. */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **cert, size_t *certlen)
{
gpg_error_t err;
int occurrence = 0;
*cert = NULL;
*certlen = 0;
if (strlen (certid) == 40)
{
int keyno;
const unsigned char *keygrip_str;
for (keyno = 0; keyno < 3; keyno++)
{
keygrip_str = app->app_local->pk[keyno].keygrip_str;
if (!strncmp (keygrip_str, certid, 40))
break;
}
if (keyno == 2)
;
else if (keyno == 1)
occurrence = 1;
else if (keyno == 0)
occurrence = 2;
else
return gpg_error (GPG_ERR_INV_ID);
}
else if (!ascii_strcasecmp (certid, "OPENPGP.3"))
;
else if (!ascii_strcasecmp (certid, "OPENPGP.2"))
occurrence = 1;
else if (!ascii_strcasecmp (certid, "OPENPGP.1"))
occurrence = 2;
else
return gpg_error (GPG_ERR_INV_ID);
if (!app->app_local->extcap.is_v3 && occurrence)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (!app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_FOUND);
if (occurrence)
{
int exmode;
err = iso7816_select_data (app_get_slot (app), occurrence, 0x7F21);
if (!err)
{
if (app->app_local->cardcap.ext_lc_le)
exmode = app->app_local->extcap.max_certlen;
else
exmode = 0;
err = iso7816_get_data (app_get_slot (app), exmode, 0x7F21,
cert, certlen);
/* We reset the curDO even for an error. */
iso7816_select_data (app_get_slot (app), 0, 0x7F21);
}
if (err)
err = gpg_error (GPG_ERR_NOT_FOUND);
}
else
{
unsigned char *buffer;
size_t buflen;
void *relptr;
relptr = get_one_do (app, 0x7F21, &buffer, &buflen, NULL);
if (!relptr)
return gpg_error (GPG_ERR_NOT_FOUND);
if (!buflen)
err = gpg_error (GPG_ERR_NOT_FOUND);
else if (!(*cert = xtrymalloc (buflen)))
err = gpg_error_from_syserror ();
else
{
memcpy (*cert, buffer, buflen);
*certlen = buflen;
err = 0;
}
xfree (relptr);
}
return err;
}
/* Decide if we use the pinpad of the reader for PIN input according
to the user preference on the card, and the capability of the
reader. This routine is only called when the reader has pinpad.
Returns 0 if we use pinpad, 1 otherwise. */
static int
check_pinpad_request (app_t app, pininfo_t *pininfo, int admin_pin)
{
if (app->app_local->pinpad.disabled)
return 1;
if (app->app_local->pinpad.specified == 0) /* No preference on card. */
{
if (pininfo->fixedlen == 0) /* Reader has varlen capability. */
return 0; /* Then, use pinpad. */
else
/*
* Reader has limited capability, and it may not match PIN of
* the card.
*/
return 1;
}
if (admin_pin)
pininfo->fixedlen = app->app_local->pinpad.fixedlen_admin;
else
pininfo->fixedlen = app->app_local->pinpad.fixedlen_user;
if (pininfo->fixedlen == 0 /* User requests disable pinpad. */
|| pininfo->fixedlen < pininfo->minlen
|| pininfo->fixedlen > pininfo->maxlen
/* Reader doesn't have the capability to input a PIN which
* length is FIXEDLEN. */)
return 1;
return 0;
}
/* Return a string with information about the card for use in a
* prompt. Returns NULL on memory failure. */
static char *
get_prompt_info (app_t app, int chvno, unsigned long sigcount, int remaining)
{
char *serial, *disp_name, *rembuf, *tmpbuf, *result;
serial = app_get_dispserialno (app, 0);
if (!serial)
return NULL;
disp_name = get_disp_name (app);
if (chvno == 1)
{
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s%%0A"
"Counter\x1f: %lu"
"%s"),
"\x1e",
serial,
disp_name? disp_name:"",
sigcount,
"");
}
else
{
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
disp_name? disp_name:"",
"");
}
xfree (disp_name);
xfree (serial);
if (remaining != -1)
{
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (!rembuf)
{
xfree (result);
return NULL;
}
tmpbuf = strconcat (result, "%0A%0A", rembuf, NULL);
xfree (rembuf);
if (!tmpbuf)
{
xfree (result);
return NULL;
}
xfree (result);
result = tmpbuf;
}
return result;
}
/* Compute hash if KDF-DO is available. CHVNO must be 0 for reset
* code, 1 or 2 for user pin and 3 for admin pin. PIN is the original
* PIN as entered by the user. R_PINVALUE and r_PINLEN will receive a
* newly allocated buffer with a possible modified pin. */
static gpg_error_t
pin2hash_if_kdf (app_t app, int chvno, const char *pin,
char **r_pinvalue, size_t *r_pinlen)
{
gpg_error_t err = 0;
void *relptr = NULL;
unsigned char *buffer;
size_t pinlen, buflen;
char *dek = NULL;
size_t deklen = 32;
*r_pinvalue = NULL;
*r_pinlen = 0;
pinlen = strlen (pin);
if (app->app_local->extcap.kdf_do
&& (relptr = get_one_do (app, 0x00F9, &buffer, &buflen, NULL))
&& buflen >= KDF_DATA_LENGTH_MIN && (buffer[2] == 0x03))
{
const char *salt;
unsigned long s2k_count;
int salt_index;
dek = xtrymalloc (deklen);
if (!dek)
{
err = gpg_error_from_syserror ();
goto leave;
}
s2k_count = (((unsigned int)buffer[8] << 24)
| (buffer[9] << 16) | (buffer[10] << 8) | buffer[11]);
if (buflen == KDF_DATA_LENGTH_MIN)
salt_index =14;
else if (buflen == KDF_DATA_LENGTH_MAX)
salt_index = (chvno==3 ? 34 : (chvno==0 ? 24 : 14));
else
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
salt = &buffer[salt_index];
err = gcry_kdf_derive (pin, pinlen,
GCRY_KDF_ITERSALTED_S2K,
DIGEST_ALGO_SHA256, salt, 8,
s2k_count, deklen, dek);
if (!err)
{
*r_pinlen = deklen;
*r_pinvalue = dek;
dek = NULL;
}
}
else
{
/* Just copy the PIN to a malloced buffer. */
*r_pinvalue = xtrymalloc_secure (pinlen + 1);
if (!*r_pinvalue)
{
err = gpg_error_from_syserror ();
goto leave;
}
strcpy (*r_pinvalue, pin);
*r_pinlen = pinlen;
}
leave:
xfree (dek);
xfree (relptr);
return err;
}
static const char *
chvno_to_keyref (int chvno)
{
const char *keyref;
switch (chvno)
{
case 1: keyref = "1"; break;
case 2: keyref = "2"; break;
case 3: keyref = "3"; break;
default: keyref = NULL; break;
}
return keyref;
}
/* Helper to cache a PIN. If PIN is NULL the cache is cleared. */
static void
cache_pin (app_t app, ctrl_t ctrl, int chvno, const char *pin)
{
const char *keyref;
if (opt.pcsc_shared)
return;
keyref = chvno_to_keyref (chvno);
if (!keyref)
return;
switch (APP_CARD(app)->cardtype)
{
case CARDTYPE_YUBIKEY: break;
default: return;
}
pincache_put (ctrl, app_get_slot (app), "openpgp", keyref,
pin, pin? strlen (pin):0);
switch (chvno)
{
case 1: app->app_local->pincache.maybe_chv1 = !!pin; break;
case 2: app->app_local->pincache.maybe_chv2 = !!pin; break;
case 3: app->app_local->pincache.maybe_chv3 = !!pin; break;
}
}
/* If the PIN cache is expected and really has a valid PIN return that
* pin at R_PIN. Returns true if that is the case; otherwise
* false. */
static int
pin_from_cache (app_t app, ctrl_t ctrl, int chvno, char **r_pin)
{
const char *keyref = chvno_to_keyref (chvno);
int maybe_cached;
*r_pin = NULL;
if (!keyref)
return 0;
switch (APP_CARD(app)->cardtype)
{
case CARDTYPE_YUBIKEY: break;
default: return 0;
}
switch (chvno)
{
case 1: maybe_cached = app->app_local->pincache.maybe_chv1; break;
case 2: maybe_cached = app->app_local->pincache.maybe_chv2; break;
case 3: maybe_cached = app->app_local->pincache.maybe_chv3; break;
default: maybe_cached = 0; break;
}
if (!maybe_cached)
return 0;
if (pincache_get (ctrl, app_get_slot (app), "openpgp", keyref, r_pin))
return 0;
return 1;
}
/* Verify a CHV either using the pinentry or if possible by
using a pinpad. PINCB and PINCB_ARG describe the usual callback
for the pinentry. CHVNO must be either 1 or 2. SIGCOUNT is only
used with CHV1. PINVALUE is the address of a pointer which will
receive a newly allocated block with the actual PIN (this is useful
in case that PIN shall be used for another verify operation). The
caller needs to free this value. If the function returns with
success and NULL is stored at PINVALUE, the caller should take this
as an indication that the pinpad has been used.
*/
static gpg_error_t
verify_a_chv (app_t app, ctrl_t ctrl,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int chvno, unsigned long sigcount,
char **r_pinvalue, size_t *r_pinlen)
{
int rc = 0;
char *prompt_buffer = NULL;
const char *prompt;
pininfo_t pininfo;
int minlen = 6;
int remaining;
char *pin = NULL;
log_assert (chvno == 1 || chvno == 2);
*r_pinvalue = NULL;
*r_pinlen = 0;
remaining = get_remaining_tries (app, 0);
if (remaining == -1)
return gpg_error (GPG_ERR_CARD);
if (chvno == 2 && app->app_local->flags.def_chv2)
{
/* Special case for def_chv2 mechanism. */
if (opt.verbose)
log_info (_("using default PIN as %s\n"), "CHV2");
rc = iso7816_verify (app_get_slot (app), 0x82, "123456", 6);
if (rc)
{
/* Verification of CHV2 with the default PIN failed,
although the card pretends to have the default PIN set as
CHV2. We better disable the def_chv2 flag now. */
log_info (_("failed to use default PIN as %s: %s"
" - disabling further default use\n"),
"CHV2", gpg_strerror (rc));
app->app_local->flags.def_chv2 = 0;
}
return rc;
}
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
{
const char *firstline = _("||Please unlock the card");
char *infoblock = get_prompt_info (app, chvno, sigcount,
remaining < 3? remaining : -1);
prompt_buffer = strconcat (firstline, "%0A%0A", infoblock, NULL);
if (prompt_buffer)
prompt = prompt_buffer;
else
prompt = firstline; /* ENOMEM fallback. */
xfree (infoblock);
}
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app_get_slot (app), ISO7816_VERIFY, &pininfo)
&& !check_pinpad_request (app, &pininfo, 0))
{
/* The reader supports the verify command through the pinpad.
* In this case we do not utilize the PIN cache because by using
* a pinpad the PIN can't have been cached.
* Note that the pincb appends a text to the prompt telling the
* user to use the pinpad. */
rc = pincb (pincb_arg, prompt, NULL);
prompt = NULL;
xfree (prompt_buffer);
prompt_buffer = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app_get_slot (app), 0x80+chvno, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
}
else
{
/* The reader has no pinpad or we don't want to use it. If we
* have at least the standard 3 remaining tries we first try to
* get the PIN from the cache. With less remaining tries it is
* better to let the user know about failed attempts (which
* might be due to a bug in the PIN cache handling). */
if (remaining >= 3 && pin_from_cache (app, ctrl, chvno, &pin))
rc = 0;
else
rc = pincb (pincb_arg, prompt, &pin);
prompt = NULL;
xfree (prompt_buffer);
prompt_buffer = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
if (strlen (pin) < minlen)
{
log_error (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), chvno, minlen);
wipe_and_free_string (pin);
return gpg_error (GPG_ERR_BAD_PIN);
}
rc = pin2hash_if_kdf (app, chvno, pin, r_pinvalue, r_pinlen);
if (!rc)
rc = iso7816_verify (app_get_slot (app),
0x80 + chvno, *r_pinvalue, *r_pinlen);
if (!rc)
cache_pin (app, ctrl, chvno, pin);
}
wipe_and_free_string (pin);
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), chvno, gpg_strerror (rc));
xfree (*r_pinvalue);
*r_pinvalue = NULL;
*r_pinlen = 0;
flush_cache_after_error (app);
}
return rc;
}
/* Verify CHV2 if required. Depending on the configuration of the
card CHV1 will also be verified. */
static gpg_error_t
verify_chv2 (app_t app, ctrl_t ctrl,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc;
char *pinvalue;
size_t pinlen;
if (app->did_chv2)
return 0; /* We already verified CHV2. */
if (app->app_local->pk[1].key || app->app_local->pk[2].key)
{
rc = verify_a_chv (app, ctrl, pincb, pincb_arg, 2, 0, &pinvalue, &pinlen);
if (rc)
return rc;
app->did_chv2 = 1;
if (!app->did_chv1 && !app->force_chv1 && pinvalue && !opt.pcsc_shared)
{
/* For convenience we verify CHV1 here too. We do this only if
the card is not configured to require a verification before
each CHV1 controlled operation (force_chv1) and if we are not
using the pinpad (PINVALUE == NULL). */
rc = iso7816_verify (app_get_slot (app), 0x81, pinvalue, pinlen);
if (gpg_err_code (rc) == GPG_ERR_BAD_PIN)
rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED);
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 1, gpg_strerror (rc));
flush_cache_after_error (app);
}
else
{
app->did_chv1 = 1;
/* Note that we are not able to cache the CHV 1 here because
* it is possible that due to the use of a KDF-DO PINVALUE
* has the hashed binary PIN of length PINLEN. */
}
}
}
else
{
rc = verify_a_chv (app, ctrl, pincb, pincb_arg, 1, 0, &pinvalue, &pinlen);
if (rc)
return rc;
}
wipe_and_free (pinvalue, pinlen);
return rc;
}
/* Build the prompt to enter the Admin PIN. The prompt depends on the
* current state of the card. If R_REMAINING is not NULL the
* remaining tries are stored there. */
static gpg_error_t
build_enter_admin_pin_prompt (app_t app, char **r_prompt, int *r_remaining)
{
int remaining;
char *prompt;
char *infoblock;
*r_prompt = NULL;
if (r_remaining)
*r_remaining = 0;
remaining = get_remaining_tries (app, 1);
if (remaining == -1)
return gpg_error (GPG_ERR_CARD);
if (!remaining)
{
log_info (_("card is permanently locked!\n"));
return gpg_error (GPG_ERR_PIN_BLOCKED);
}
log_info (ngettext("%d Admin PIN attempt remaining before card"
" is permanently locked\n",
"%d Admin PIN attempts remaining before card"
" is permanently locked\n",
remaining), remaining);
infoblock = get_prompt_info (app, 3, 0, remaining < 3? remaining : -1);
/* TRANSLATORS: Do not translate the "|A|" prefix but keep it at
the start of the string. Use %0A (single percent) for a linefeed. */
prompt = strconcat (_("|A|Please enter the Admin PIN"),
"%0A%0A", infoblock, NULL);
xfree (infoblock);
if (!prompt)
return gpg_error_from_syserror ();
*r_prompt = prompt;
if (r_remaining)
*r_remaining = remaining;
return 0;
}
/* Verify CHV3 if required. */
static gpg_error_t
verify_chv3 (app_t app, ctrl_t ctrl,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc = 0;
if (!opt.allow_admin)
{
log_info (_("access to admin commands is not configured\n"));
return gpg_error (GPG_ERR_EACCES);
}
if (!app->did_chv3)
{
pininfo_t pininfo;
int minlen = 8;
char *prompt;
int remaining;
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
rc = build_enter_admin_pin_prompt (app, &prompt, &remaining);
if (rc)
return rc;
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app_get_slot (app),
ISO7816_VERIFY, &pininfo)
&& !check_pinpad_request (app, &pininfo, 1))
{
/* The reader supports the verify command through the pinpad. */
rc = pincb (pincb_arg, prompt, NULL);
xfree (prompt);
prompt = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
rc = iso7816_verify_kp (app_get_slot (app), 0x83, &pininfo);
/* Dismiss the prompt. */
pincb (pincb_arg, NULL, NULL);
}
else
{
char *pin;
char *pinvalue;
size_t pinlen;
if (remaining >= 3 && pin_from_cache (app, ctrl, 3, &pin))
rc = 0;
else
rc = pincb (pincb_arg, prompt, &pin);
xfree (prompt);
prompt = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
if (strlen (pin) < minlen)
{
log_error (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), 3, minlen);
wipe_and_free_string (pin);
return gpg_error (GPG_ERR_BAD_PIN);
}
rc = pin2hash_if_kdf (app, 3, pin, &pinvalue, &pinlen);
if (!rc)
rc = iso7816_verify (app_get_slot (app), 0x83, pinvalue, pinlen);
if (!rc)
cache_pin (app, ctrl, 3, pin);
wipe_and_free_string (pin);
wipe_and_free (pinvalue, pinlen);
}
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 3, gpg_strerror (rc));
flush_cache_after_error (app);
return rc;
}
app->did_chv3 = 1;
}
return rc;
}
/* Handle the SETATTR operation. All arguments are already basically
checked. */
static gpg_error_t
do_setattr (app_t app, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t rc;
int idx;
static struct {
const char *name;
int tag;
int flush_tag; /* The tag which needs to be flushed or 0. */
int need_chv;
int special;
unsigned int need_v2:1;
unsigned int need_v3:1;
} table[] = {
{ "DISP-NAME", 0x005B, 0, 3 },
{ "LOGIN-DATA", 0x005E, 0, 3, 2 },
{ "DISP-LANG", 0x5F2D, 0, 3 },
{ "DISP-SEX", 0x5F35, 0, 3 },
{ "PUBKEY-URL", 0x5F50, 0, 3 },
{ "CHV-STATUS-1", 0x00C4, 0, 3, 1 },
{ "CA-FPR-1", 0x00CA, 0x00C6, 3 },
{ "CA-FPR-2", 0x00CB, 0x00C6, 3 },
{ "CA-FPR-3", 0x00CC, 0x00C6, 3 },
{ "PRIVATE-DO-1", 0x0101, 0, 2 },
{ "PRIVATE-DO-2", 0x0102, 0, 3 },
{ "PRIVATE-DO-3", 0x0103, 0, 2 },
{ "PRIVATE-DO-4", 0x0104, 0, 3 },
{ "CERT-1", 0x7F21, 0, 3,11, 1, 1 },
{ "CERT-2", 0x7F21, 0, 3,12, 1, 1 },
{ "CERT-3", 0x7F21, 0, 3, 0, 1 },
{ "SM-KEY-ENC", 0x00D1, 0, 3, 0, 1 },
{ "SM-KEY-MAC", 0x00D2, 0, 3, 0, 1 },
{ "KEY-ATTR", 0, 0, 0, 3, 1 },
{ "AESKEY", 0x00D5, 0, 3, 0, 1 },
{ "UIF-1", 0x00D6, 0, 3, 5, 1 },
{ "UIF-2", 0x00D7, 0, 3, 5, 1 },
{ "UIF-3", 0x00D8, 0, 3, 5, 1 },
{ "KDF", 0x00F9, 0, 0, 4, 1 },
{ NULL, 0 }
};
int exmode;
for (idx=0; table[idx].name && strcmp (table[idx].name, name); idx++)
;
if (!table[idx].name)
return gpg_error (GPG_ERR_INV_NAME);
if (table[idx].need_v2 && !app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (table[idx].need_v3 && !app->app_local->extcap.is_v3)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
if (table[idx].special == 5 && app->app_local->extcap.has_button == 0)
return gpg_error (GPG_ERR_INV_OBJ);
if (table[idx].special == 3)
return change_keyattr_from_string (app, ctrl, pincb, pincb_arg,
NULL, NULL,
value, valuelen);
switch (table[idx].need_chv)
{
case 2:
rc = verify_chv2 (app, ctrl, pincb, pincb_arg);
break;
case 3:
rc = verify_chv3 (app, ctrl, pincb, pincb_arg);
break;
default:
rc = 0;
}
if (rc)
return rc;
/* Flush the cache before writing it, so that the next get operation
will reread the data from the card and thus get synced in case of
errors (e.g. data truncated by the card). */
flush_cache_item (app, table[idx].flush_tag? table[idx].flush_tag
/* */ : table[idx].tag);
if (app->app_local->cardcap.ext_lc_le && valuelen > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && valuelen > 254)
exmode = -254; /* Command chaining with max. 254 bytes. */
else
exmode = 0;
if (table[idx].special == 4)
{
if (APP_CARD(app)->cardtype == CARDTYPE_YUBIKEY
|| APP_CARD(app)->cardtype == CARDTYPE_GNUK)
{
rc = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (rc)
return rc;
if (valuelen == 3
&& APP_CARD(app)->cardtype == CARDTYPE_GNUK)
{
value = NULL;
valuelen = 0;
}
cache_pin (app, ctrl, 1, NULL);
cache_pin (app, ctrl, 2, NULL);
cache_pin (app, ctrl, 3, NULL);
}
else
{
char *oldpinvalue = NULL;
char *buffer1 = NULL;
size_t bufferlen1;
const char *u, *a;
size_t ulen, alen;
if (valuelen == 3)
{
u = "123456";
a = "12345678";
ulen = 6;
alen = 8;
}
else if (valuelen == KDF_DATA_LENGTH_MAX)
{
u = (const char *)value + 44;
a = u + 34;
ulen = alen = 32;
}
else
return gpg_error (GPG_ERR_INV_OBJ);
if (!pin_from_cache (app, ctrl, 3, &oldpinvalue))
{
char *prompt;
rc = build_enter_admin_pin_prompt (app, &prompt, NULL);
if (rc)
return rc;
rc = pincb (pincb_arg, prompt, &oldpinvalue);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
return rc;
}
}
rc = pin2hash_if_kdf (app, 3, oldpinvalue, &buffer1, &bufferlen1);
if (!rc)
rc = iso7816_change_reference_data (app_get_slot (app),
0x83,
buffer1, bufferlen1,
a, alen);
if (!rc)
rc = iso7816_verify (app_get_slot (app), 0x83, a, alen);
if (!rc)
cache_pin (app, ctrl, 3, "12345678");
if (!rc)
rc = iso7816_reset_retry_counter (app_get_slot (app), 0x81, u, ulen);
if (!rc)
cache_pin (app, ctrl, 1, "123456");
if (!rc)
rc = iso7816_put_data (app_get_slot (app), 0, 0xD3, NULL, 0);
wipe_and_free (buffer1, bufferlen1);
wipe_and_free_string (oldpinvalue);
}
/* Flush the cache again, because pin2hash_if_kdf uses the DO. */
flush_cache_item (app, 0x00F9);
}
if (table[idx].special == 11 || table[idx].special == 12) /* CERT-1 or -2 */
{
rc = iso7816_select_data (app_get_slot (app),
table[idx].special == 11? 2 : 1,
table[idx].tag);
if (!rc)
{
rc = iso7816_put_data (app_get_slot (app),
exmode, table[idx].tag, value, valuelen);
/* We better reset the curDO. */
iso7816_select_data (app_get_slot (app), 0, table[idx].tag);
}
}
else /* Standard. */
rc = iso7816_put_data (app_get_slot (app),
exmode, table[idx].tag, value, valuelen);
if (rc)
log_error ("failed to set '%s': %s\n", table[idx].name, gpg_strerror (rc));
if (table[idx].special == 1)
app->force_chv1 = (valuelen && *value == 0);
else if (table[idx].special == 2)
parse_login_data (app);
else if (table[idx].special == 4)
{
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
if ((valuelen == KDF_DATA_LENGTH_MIN || valuelen == KDF_DATA_LENGTH_MAX)
&& (value[2] == 0x03))
app->app_local->pinpad.disabled = 1;
else
app->app_local->pinpad.disabled = 0;
}
return rc;
}
/* Handle the WRITECERT command for OpenPGP. This writes the standard
* certificate to the card; CERTID needs to be set to "OPENPGP.3".
* PINCB and PINCB_ARG are the usual arguments for the pinentry
* callback. */
static gpg_error_t
do_writecert (app_t app, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *certdata, size_t certdatalen)
{
const char *name;
if (!ascii_strcasecmp (certidstr, "OPENPGP.3"))
name = "CERT-3";
else if (!ascii_strcasecmp (certidstr, "OPENPGP.2"))
name = "CERT-2";
else if (!ascii_strcasecmp (certidstr, "OPENPGP.1"))
name = "CERT-1";
else
return gpg_error (GPG_ERR_INV_ID);
if (!certdata || !certdatalen)
return gpg_error (GPG_ERR_INV_ARG);
if (!app->app_local->extcap.is_v2)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* do_setattr checks that CERT-2 and CERT-1 requires a v3 card. */
if (certdatalen > app->app_local->extcap.max_certlen)
return gpg_error (GPG_ERR_TOO_LARGE);
return do_setattr (app, ctrl, name, pincb, pincb_arg,
certdata, certdatalen);
}
static gpg_error_t
clear_chv_status (app_t app, ctrl_t ctrl, int chvno)
{
unsigned char apdu[4];
gpg_error_t err;
cache_pin (app, ctrl, chvno, NULL);
if (!app->app_local->extcap.is_v2)
return GPG_ERR_UNSUPPORTED_OPERATION;
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0xff;
apdu[3] = 0x80+chvno;
err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, NULL, NULL, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_INV_VALUE)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
return err;
}
if (chvno == 1)
{
apdu[3]++;
err = iso7816_apdu_direct (app_get_slot (app),
apdu, 4, 0, NULL, NULL, NULL);
app->did_chv1 = app->did_chv2 = 0;
}
else if (chvno == 2)
app->did_chv2 = 0;
else if (chvno == 3)
app->did_chv3 = 0;
return err;
}
/* Handle the PASSWD command. The following combinations are
possible:
Flags CHVNO Vers. Description
RESET 1 1 Verify CHV3 and set a new CHV1 and CHV2
RESET 1 2 Verify PW3 and set a new PW1.
RESET 2 1 Verify CHV3 and set a new CHV1 and CHV2.
RESET 2 2 Verify PW3 and set a new Reset Code.
RESET 3 any Returns GPG_ERR_INV_ID.
- 1 1 Verify CHV2 and set a new CHV1 and CHV2.
- 1 2 Verify PW1 and set a new PW1.
- 2 1 Verify CHV2 and set a new CHV1 and CHV2.
- 2 2 Verify Reset Code and set a new PW1.
- 3 any Verify CHV3/PW3 and set a new CHV3/PW3.
The CHVNO can be prefixed with "OPENPGP.".
*/
static gpg_error_t
do_change_pin (app_t app, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int rc = 0;
int chvno;
char *resetcode = NULL;
char *oldpinvalue = NULL;
char *pinvalue = NULL;
int reset_mode = !!(flags & APP_CHANGE_FLAG_RESET);
int set_resetcode = 0;
int use_resetcode = 0;
pininfo_t pininfo;
int use_pinpad = 0;
int minlen = 6;
if (digitp (chvnostr))
chvno = atoi (chvnostr);
else if (!ascii_strcasecmp (chvnostr, "OPENPGP.1"))
chvno = 1;
else if (!ascii_strcasecmp (chvnostr, "OPENPGP.2"))
chvno = 2;
else if (!ascii_strcasecmp (chvnostr, "OPENPGP.3"))
chvno = 3;
else
return gpg_error (GPG_ERR_INV_ID);
memset (&pininfo, 0, sizeof pininfo);
pininfo.fixedlen = -1;
pininfo.minlen = minlen;
/* Better clear all the PIN caches first. */
cache_pin (app, ctrl, 1, NULL);
cache_pin (app, ctrl, 2, NULL);
cache_pin (app, ctrl, 3, NULL);
if ((flags & APP_CHANGE_FLAG_CLEAR))
return clear_chv_status (app, ctrl, chvno);
if (reset_mode && chvno == 3)
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (!app->app_local->extcap.is_v2)
{
/* Version 1 cards. */
if (reset_mode || chvno == 3)
{
/* We always require that the PIN is entered. */
app->did_chv3 = 0;
rc = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (rc)
goto leave;
}
else if (chvno == 1 || chvno == 2)
{
/* On a v1.x card CHV1 and CVH2 should always have the same
value, thus we enforce it here. */
int save_force = app->force_chv1;
app->force_chv1 = 0;
app->did_chv1 = 0;
app->did_chv2 = 0;
rc = verify_chv2 (app, ctrl, pincb, pincb_arg);
app->force_chv1 = save_force;
if (rc)
goto leave;
}
else
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
}
else
{
/* Version 2 cards. */
if (!opt.disable_pinpad
&& !iso7816_check_pinpad (app_get_slot (app),
ISO7816_CHANGE_REFERENCE_DATA, &pininfo)
&& !check_pinpad_request (app, &pininfo, chvno == 3))
use_pinpad = 1;
if (reset_mode)
{
/* To reset a PIN the Admin PIN is required. */
use_pinpad = 0;
app->did_chv3 = 0;
rc = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (rc)
goto leave;
if (chvno == 2)
set_resetcode = 1;
}
else if (chvno == 1 || chvno == 3)
{
if (!use_pinpad)
{
char *promptbuf = NULL;
const char *prompt;
if (chvno == 3)
{
minlen = 8;
rc = build_enter_admin_pin_prompt (app, &promptbuf, NULL);
if (rc)
goto leave;
prompt = promptbuf;
}
else
prompt = _("||Please enter the PIN");
rc = pincb (pincb_arg, prompt, &oldpinvalue);
xfree (promptbuf);
promptbuf = NULL;
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
if (strlen (oldpinvalue) < minlen)
{
log_info (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), chvno, minlen);
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
}
}
else if (chvno == 2)
{
/* There is no PW2 for v2 cards. We use this condition to
allow a PW reset using the Reset Code. */
void *relptr;
unsigned char *value;
size_t valuelen;
int remaining;
use_pinpad = 0;
minlen = 8;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
rc = gpg_error (GPG_ERR_CARD);
goto leave;
}
remaining = value[5];
xfree (relptr);
if (!remaining)
{
log_error (_("Reset Code not or not anymore available\n"));
rc = gpg_error (GPG_ERR_NO_RESET_CODE);
goto leave;
}
rc = pincb (pincb_arg,
_("|R|Please enter the Reset Code for the card"),
&resetcode);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
if (strlen (resetcode) < minlen)
{
log_info (_("Reset Code is too short; minimum length is %d\n"),
minlen);
rc = gpg_error (GPG_ERR_BAD_RESET_CODE);
goto leave;
}
use_resetcode = 1;
}
else
{
rc = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
} /* End version 2 cards. */
if (chvno == 3)
app->did_chv3 = 0;
else
app->did_chv1 = app->did_chv2 = 0;
if (!use_pinpad)
{
/* TRANSLATORS: Do not translate the "|*|" prefixes but
keep it at the start of the string. We need this elsewhere
to get some infos on the string. */
rc = pincb (pincb_arg, set_resetcode? _("|RN|New Reset Code") :
chvno == 3? _("|AN|New Admin PIN") : _("|N|New PIN"),
&pinvalue);
if (rc || pinvalue == NULL)
{
log_error (_("error getting new PIN: %s\n"), gpg_strerror (rc));
goto leave;
}
if (set_resetcode)
{
size_t bufferlen = strlen (pinvalue);
if (bufferlen != 0 && bufferlen < 8)
{
log_error (_("Reset Code is too short; minimum length is %d\n"), 8);
rc = gpg_error (GPG_ERR_BAD_RESET_CODE);
goto leave;
}
}
else if (use_resetcode)
{
minlen = 6; /* Reset from the RC value to the PIN value. */
if (strlen (pinvalue) < minlen)
{
log_info (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), 1, minlen);
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
}
else
{
if (chvno == 3)
minlen = 8;
if (strlen (pinvalue) < minlen)
{
log_info (_("PIN for CHV%d is too short;"
" minimum length is %d\n"), chvno, minlen);
rc = gpg_error (GPG_ERR_BAD_PIN);
goto leave;
}
}
}
if (resetcode)
{
char *result1 = NULL;
char *result2 = NULL;
char *buffer = NULL;
size_t resultlen1, resultlen2=0, bufferlen=0;
rc = pin2hash_if_kdf (app, 0, resetcode, &result1, &resultlen1);
if (!rc)
rc = pin2hash_if_kdf (app, 1, pinvalue, &result2, &resultlen2);
if (!rc)
{
bufferlen = resultlen1 + resultlen2;
buffer = xtrymalloc (bufferlen);
if (!buffer)
rc = gpg_error_from_syserror ();
else
{
memcpy (buffer, result1, resultlen1);
memcpy (buffer+resultlen1, result2, resultlen2);
}
}
if (!rc)
rc = iso7816_reset_retry_counter_with_rc (app_get_slot (app), 0x81,
buffer, bufferlen);
wipe_and_free (result1, resultlen1);
wipe_and_free (result2, resultlen2);
wipe_and_free (buffer, bufferlen);
}
else if (set_resetcode)
{
size_t bufferlen;
char *buffer = NULL;
rc = pin2hash_if_kdf (app, 0, pinvalue, &buffer, &bufferlen);
if (!rc)
rc = iso7816_put_data (app_get_slot (app),
0, 0xD3, buffer, bufferlen);
wipe_and_free (buffer, bufferlen);
}
else if (reset_mode)
{
char *buffer = NULL;
size_t bufferlen;
rc = pin2hash_if_kdf (app, 1, pinvalue, &buffer, &bufferlen);
if (!rc)
rc = iso7816_reset_retry_counter (app_get_slot (app),
0x81, buffer, bufferlen);
if (!rc && !app->app_local->extcap.is_v2)
rc = iso7816_reset_retry_counter (app_get_slot (app),
0x82, buffer, bufferlen);
wipe_and_free (buffer, bufferlen);
}
else if (!app->app_local->extcap.is_v2)
{
/* Version 1 cards. */
if (chvno == 1 || chvno == 2)
{
rc = iso7816_change_reference_data (app_get_slot (app),
0x81, NULL, 0,
pinvalue, strlen (pinvalue));
if (!rc)
rc = iso7816_change_reference_data (app_get_slot (app),
0x82, NULL, 0,
pinvalue, strlen (pinvalue));
}
else /* CHVNO == 3 */
{
rc = iso7816_change_reference_data (app_get_slot (app),
0x80 + chvno, NULL, 0,
pinvalue, strlen (pinvalue));
}
}
else
{
/* Version 2 cards. */
log_assert (chvno == 1 || chvno == 3);
if (use_pinpad)
{
rc = pincb (pincb_arg,
chvno == 3 ?
_("||Please enter the Admin PIN and New Admin PIN") :
_("||Please enter the PIN and New PIN"), NULL);
if (rc)
{
log_info (_("PIN callback returned error: %s\n"),
gpg_strerror (rc));
goto leave;
}
rc = iso7816_change_reference_data_kp (app_get_slot (app),
0x80 + chvno, 0,
&pininfo);
pincb (pincb_arg, NULL, NULL); /* Dismiss the prompt. */
}
else
{
char *buffer1 = NULL;
char *buffer2 = NULL;
size_t bufferlen1, bufferlen2 = 0;
rc = pin2hash_if_kdf (app, chvno, oldpinvalue, &buffer1, &bufferlen1);
if (!rc)
rc = pin2hash_if_kdf (app, chvno, pinvalue, &buffer2, &bufferlen2);
if (!rc)
rc = iso7816_change_reference_data (app_get_slot (app),
0x80 + chvno,
buffer1, bufferlen1,
buffer2, bufferlen2);
wipe_and_free (buffer1, bufferlen1);
wipe_and_free (buffer2, bufferlen2);
}
}
wipe_and_free_string (pinvalue);
if (rc)
flush_cache_after_error (app);
leave:
wipe_and_free_string (resetcode);
wipe_and_free_string (oldpinvalue);
return rc;
}
/* Check whether a key already exists. KEYIDX is the index of the key
(0..2). If FORCE is TRUE a diagnositic will be printed but no
error returned if the key already exists. The flag GENERATING is
only used to print correct messages. */
static gpg_error_t
does_key_exist (app_t app, int keyidx, int generating, int force)
{
const unsigned char *fpr;
unsigned char *buffer;
size_t buflen, n;
int i;
log_assert (keyidx >=0 && keyidx <= 2);
if (iso7816_get_data (app_get_slot (app), 0, 0x006E, &buffer, &buflen))
{
log_error (_("error reading application data\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr = find_tlv (buffer, buflen, 0x00C5, &n);
if (!fpr || n < 60)
{
log_error (_("error reading fingerprint DO\n"));
xfree (buffer);
return gpg_error (GPG_ERR_GENERAL);
}
fpr += 20*keyidx;
for (i=0; i < 20 && !fpr[i]; i++)
;
xfree (buffer);
if (i!=20 && !force)
{
log_error (_("key already exists\n"));
return gpg_error (GPG_ERR_EEXIST);
}
else if (i!=20)
log_info (_("existing key will be replaced\n"));
else if (generating)
log_info (_("generating new key\n"));
else
log_info (_("writing new key\n"));
return 0;
}
/* Create a TLV tag and value and store it at BUFFER. Return the length
of tag and length. A LENGTH greater than 65535 is truncated. */
static size_t
add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
{
unsigned char *p = buffer;
log_assert (tag <= 0xffff);
if ( tag > 0xff )
*p++ = tag >> 8;
*p++ = tag;
if (length < 128)
*p++ = length;
else if (length < 256)
{
*p++ = 0x81;
*p++ = length;
}
else
{
if (length > 0xffff)
length = 0xffff;
*p++ = 0x82;
*p++ = length >> 8;
*p++ = length;
}
return p - buffer;
}
static gpg_error_t
build_privkey_template (app_t app, int keyno,
const unsigned char *rsa_n, size_t rsa_n_len,
const unsigned char *rsa_e, size_t rsa_e_len,
const unsigned char *rsa_p, size_t rsa_p_len,
const unsigned char *rsa_q, size_t rsa_q_len,
const unsigned char *rsa_u, size_t rsa_u_len,
const unsigned char *rsa_dp, size_t rsa_dp_len,
const unsigned char *rsa_dq, size_t rsa_dq_len,
unsigned char **result, size_t *resultlen)
{
size_t rsa_e_reqlen;
unsigned char privkey[7*(1+3+3)];
size_t privkey_len;
unsigned char exthdr[2+2+3];
size_t exthdr_len;
unsigned char suffix[2+3];
size_t suffix_len;
unsigned char *tp;
size_t datalen;
unsigned char *template;
size_t template_size;
*result = NULL;
*resultlen = 0;
switch (app->app_local->keyattr[keyno].rsa.format)
{
case RSA_STD:
case RSA_STD_N:
case RSA_CRT:
case RSA_CRT_N:
break;
default:
return gpg_error (GPG_ERR_INV_VALUE);
}
/* Get the required length for E. Rounded up to the nearest byte */
rsa_e_reqlen = (app->app_local->keyattr[keyno].rsa.e_bits + 7) / 8;
log_assert (rsa_e_len <= rsa_e_reqlen);
/* Build the 7f48 cardholder private key template. */
datalen = 0;
tp = privkey;
tp += add_tlv (tp, 0x91, rsa_e_reqlen);
datalen += rsa_e_reqlen;
tp += add_tlv (tp, 0x92, rsa_p_len);
datalen += rsa_p_len;
tp += add_tlv (tp, 0x93, rsa_q_len);
datalen += rsa_q_len;
if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
tp += add_tlv (tp, 0x94, rsa_u_len);
datalen += rsa_u_len;
tp += add_tlv (tp, 0x95, rsa_dp_len);
datalen += rsa_dp_len;
tp += add_tlv (tp, 0x96, rsa_dq_len);
datalen += rsa_dq_len;
}
if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
tp += add_tlv (tp, 0x97, rsa_n_len);
datalen += rsa_n_len;
}
privkey_len = tp - privkey;
/* Build the extended header list without the private key template. */
tp = exthdr;
*tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
*tp++ = 0;
tp += add_tlv (tp, 0x7f48, privkey_len);
exthdr_len = tp - exthdr;
/* Build the 5f48 suffix of the data. */
tp = suffix;
tp += add_tlv (tp, 0x5f48, datalen);
suffix_len = tp - suffix;
/* Now concatenate everything. */
template_size = (1 + 3 /* 0x4d and len. */
+ exthdr_len
+ privkey_len
+ suffix_len
+ datalen);
tp = template = xtrymalloc_secure (template_size);
if (!template)
return gpg_error_from_syserror ();
tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
memcpy (tp, exthdr, exthdr_len);
tp += exthdr_len;
memcpy (tp, privkey, privkey_len);
tp += privkey_len;
memcpy (tp, suffix, suffix_len);
tp += suffix_len;
memcpy (tp, rsa_e, rsa_e_len);
if (rsa_e_len < rsa_e_reqlen)
{
/* Right justify E. */
memmove (tp + rsa_e_reqlen - rsa_e_len, tp, rsa_e_len);
memset (tp, 0, rsa_e_reqlen - rsa_e_len);
}
tp += rsa_e_reqlen;
memcpy (tp, rsa_p, rsa_p_len);
tp += rsa_p_len;
memcpy (tp, rsa_q, rsa_q_len);
tp += rsa_q_len;
if (app->app_local->keyattr[keyno].rsa.format == RSA_CRT
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
memcpy (tp, rsa_u, rsa_u_len);
tp += rsa_u_len;
memcpy (tp, rsa_dp, rsa_dp_len);
tp += rsa_dp_len;
memcpy (tp, rsa_dq, rsa_dq_len);
tp += rsa_dq_len;
}
if (app->app_local->keyattr[keyno].rsa.format == RSA_STD_N
|| app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N)
{
memcpy (tp, rsa_n, rsa_n_len);
tp += rsa_n_len;
}
/* Sanity check. We don't know the exact length because we
allocated 3 bytes for the first length header. */
log_assert (tp - template <= template_size);
*result = template;
*resultlen = tp - template;
return 0;
}
static gpg_error_t
build_ecc_privkey_template (app_t app, int keyno,
const unsigned char *ecc_d, size_t ecc_d_len,
size_t ecc_d_fixed_len,
const unsigned char *ecc_q, size_t ecc_q_len,
unsigned char **result, size_t *resultlen)
{
unsigned char privkey[2*(1+3)];
size_t privkey_len;
unsigned char exthdr[2+2+3];
size_t exthdr_len;
unsigned char suffix[2+3];
size_t suffix_len;
unsigned char *tp;
size_t datalen;
unsigned char *template;
size_t template_size;
int pubkey_required;
/* This case doesn't occur in GnuPG 2.3 or later, because
agent/sexp-secret.c does the fixup. */
if (ecc_d_fixed_len < ecc_d_len)
{
if (ecc_d_fixed_len != ecc_d_len - 1 || *ecc_d)
return gpg_error (GPG_ERR_INV_OBJ);
/* Remove the additional zero. */
ecc_d_len--;
ecc_d++;
}
pubkey_required = !!(app->app_local->keyattr[keyno].ecc.flags
& ECC_FLAG_PUBKEY);
*result = NULL;
*resultlen = 0;
/* Build the 7f48 cardholder private key template. */
datalen = 0;
tp = privkey;
tp += add_tlv (tp, 0x92, ecc_d_fixed_len);
datalen += ecc_d_fixed_len;
if (pubkey_required)
{
tp += add_tlv (tp, 0x99, ecc_q_len);
datalen += ecc_q_len;
}
privkey_len = tp - privkey;
/* Build the extended header list without the private key template. */
tp = exthdr;
*tp++ = keyno ==0 ? 0xb6 : keyno == 1? 0xb8 : 0xa4;
*tp++ = 0;
tp += add_tlv (tp, 0x7f48, privkey_len);
exthdr_len = tp - exthdr;
/* Build the 5f48 suffix of the data. */
tp = suffix;
tp += add_tlv (tp, 0x5f48, datalen);
suffix_len = tp - suffix;
/* Now concatenate everything. */
template_size = (1 + 1 /* 0x4d and len. */
+ exthdr_len
+ privkey_len
+ suffix_len
+ datalen);
if (exthdr_len + privkey_len + suffix_len + datalen >= 128)
template_size++;
tp = template = xtrymalloc_secure (template_size);
if (!template)
return gpg_error_from_syserror ();
tp += add_tlv (tp, 0x4d, exthdr_len + privkey_len + suffix_len + datalen);
memcpy (tp, exthdr, exthdr_len);
tp += exthdr_len;
memcpy (tp, privkey, privkey_len);
tp += privkey_len;
memcpy (tp, suffix, suffix_len);
tp += suffix_len;
if (ecc_d_fixed_len > ecc_d_len)
{
memset (tp, 0, ecc_d_fixed_len - ecc_d_len);
memcpy (tp + ecc_d_fixed_len - ecc_d_len, ecc_d, ecc_d_len);
}
else
memcpy (tp, ecc_d, ecc_d_len);
tp += ecc_d_fixed_len;
if (pubkey_required)
{
memcpy (tp, ecc_q, ecc_q_len);
tp += ecc_q_len;
}
log_assert (tp - template == template_size);
*result = template;
*resultlen = tp - template;
return 0;
}
/* Helper for do_writekey to change the size of a key. Note that
this deletes the entire key without asking. */
static gpg_error_t
change_keyattr (app_t app, ctrl_t ctrl,
int keyno, const unsigned char *buf, size_t buflen,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
log_assert (keyno >=0 && keyno <= 2);
/* Prepare for storing the key. */
err = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (err)
return err;
/* Change the attribute. */
err = iso7816_put_data (app_get_slot (app), 0, 0xC1+keyno, buf, buflen);
if (err)
log_error ("error changing key attribute of OPENPGP.%d\n",
keyno+1);
else
log_info ("key attribute of OPENPGP.%d changed\n", keyno+1);
flush_cache (app);
err = parse_algorithm_attribute (app, keyno);
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
cache_pin (app, ctrl, 1, NULL);
cache_pin (app, ctrl, 2, NULL);
cache_pin (app, ctrl, 3, NULL);
return err;
}
static gpg_error_t
change_rsa_keyattr (app_t app, ctrl_t ctrl, int keyno, unsigned int nbits,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err = 0;
unsigned char *buf;
size_t buflen;
void *relptr;
/* Read the current attributes into a buffer. */
relptr = get_one_do (app, 0xC1+keyno, &buf, &buflen, NULL);
if (!relptr)
err = gpg_error (GPG_ERR_CARD);
else if (buflen < 6)
{
/* Attributes too short. */
xfree (relptr);
err = gpg_error (GPG_ERR_CARD);
}
else
{
/* If key attribute was RSA, we only change n_bits and don't
touch anything else. Before we do so, we round up NBITS to a
sensible way in the same way as gpg's key generation does it.
This may help to sort out problems with a few bits too short
keys. */
nbits = ((nbits + 31) / 32) * 32;
buf[1] = (nbits >> 8);
buf[2] = nbits;
/* If it was not RSA, we need to fill other parts. */
if (buf[0] != PUBKEY_ALGO_RSA)
{
buf[0] = PUBKEY_ALGO_RSA;
buf[3] = 0;
buf[4] = 32;
buf[5] = 0;
buflen = 6;
}
err = change_keyattr (app, ctrl, keyno, buf, buflen, pincb, pincb_arg);
xfree (relptr);
}
return err;
}
/* Helper to process an setattr command for name KEY-ATTR.
*
* If KEYREF and KEYALGO are NULL (VALUE,VALUELEN) are expected to
* contain one of the following strings:
* RSA: "--force <key> <algo> rsa<nbits>"
* ECC: "--force <key> <algo> <curvename>"
*
* If KEYREF and KEYALGO is given the key attribute for KEYREF are
* changed to what is described by KEYALGO (e.g. "rsa3072", "rsa2048",
* or "ed25519").
*/
static gpg_error_t
change_keyattr_from_string (app_t app, ctrl_t ctrl,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const char *keyref, const char *keyalgo,
const void *value, size_t valuelen)
{
gpg_error_t err = 0;
char *string = NULL;
int key, keyno, algo;
unsigned int nbits = 0;
const char *oidstr = NULL; /* OID of the curve. */
char *endp;
if (keyref && keyalgo && *keyref && *keyalgo)
{
if (!ascii_strcasecmp (keyref, "OPENPGP.1"))
keyno = 0;
else if (!ascii_strcasecmp (keyref, "OPENPGP.2"))
keyno = 1;
else if (!ascii_strcasecmp (keyref, "OPENPGP.3"))
keyno = 2;
else
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (!strncmp (keyalgo, "rsa", 3) && digitp (keyalgo+3))
{
errno = 0;
nbits = strtoul (keyalgo+3, &endp, 10);
if (errno || *endp)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
algo = PUBKEY_ALGO_RSA;
}
else if ((!strncmp (keyalgo, "dsa", 3) || !strncmp (keyalgo, "elg", 3))
&& digitp (keyalgo+3))
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
else
{
nbits = 0;
oidstr = openpgp_curve_to_oid (keyalgo, NULL, &algo);
if (!oidstr)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
if (!algo)
algo = keyno == 1? PUBKEY_ALGO_ECDH : PUBKEY_ALGO_ECDSA;
}
}
else if (!keyref && !keyalgo && value)
{
int n;
/* VALUE is expected to be a string but not guaranteed to be
* terminated. Thus copy it to an allocated buffer first. */
string = xtrymalloc (valuelen+1);
if (!string)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (string, value, valuelen);
string[valuelen] = 0;
/* Because this function deletes the key we require the string
* "--force" in the data to make clear that something serious
* might happen. */
n = 0;
sscanf (string, "--force %d %d %n", &key, &algo, &n);
if (n < 12)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
keyno = key - 1;
if (algo == PUBKEY_ALGO_RSA)
{
errno = 0;
nbits = strtoul (string+n+3, NULL, 10);
if (errno)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
{
oidstr = openpgp_curve_to_oid (string+n, NULL, NULL);
if (!oidstr)
{
err = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
}
else
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (keyno < 0 || keyno > 2)
err = gpg_error (GPG_ERR_INV_ID);
else if (algo == PUBKEY_ALGO_RSA)
{
if (nbits < 1024)
err = gpg_error (GPG_ERR_TOO_SHORT);
else if (nbits > 4096)
err = gpg_error (GPG_ERR_TOO_LARGE);
else
err = change_rsa_keyattr (app, ctrl, keyno, nbits, pincb, pincb_arg);
}
else if (algo == PUBKEY_ALGO_ECDH || algo == PUBKEY_ALGO_ECDSA
|| algo == PUBKEY_ALGO_EDDSA)
{
gcry_mpi_t oid;
const unsigned char *oidbuf;
size_t oid_len;
unsigned int n;
/* Check that the requested algo matches the properties of the
* key slot. */
if (keyno == 1 && algo != PUBKEY_ALGO_ECDH)
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
else if (keyno != 1 && algo == PUBKEY_ALGO_ECDH)
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
else
err = 0;
if (err)
goto leave;
/* Convert the OID string to an OpenPGP formatted OID. */
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
goto leave;
oidbuf = gcry_mpi_get_opaque (oid, &n);
oid_len = (n+7)/8;
/* Create the template. */
xfree (string);
string = xtrymalloc (1 + oid_len);
if (!string)
{
err = gpg_error_from_syserror ();
goto leave;
}
string[0] = algo;
memcpy (string+1, oidbuf+1, oid_len-1);
err = change_keyattr (app, ctrl,keyno, string, oid_len, pincb, pincb_arg);
gcry_mpi_release (oid);
}
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
leave:
xfree (string);
return err;
}
static gpg_error_t
rsa_writekey (app_t app, ctrl_t ctrl,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int keyno,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
const unsigned char *rsa_p = NULL;
const unsigned char *rsa_q = NULL;
size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len;
unsigned int nbits;
unsigned int maxbits;
unsigned char *template = NULL;
unsigned char *tp;
size_t template_len;
unsigned char fprbuf[20];
u32 created_at = 0;
if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_RSA)
{
log_error (_("unsupported algorithm: %s"), "RSA");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break;
case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len;break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Parse other attributes. */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
{
if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
goto leave;
if (tok)
{
for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
tok++, toklen--)
created_at = created_at*10 + (*tok - '0');
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters and that they match the card
description. */
if (!created_at)
{
log_error (_("creation timestamp missing\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
nbits = rsa_n? count_bits (rsa_n, rsa_n_len) : 0;
if (opt.verbose)
log_info ("RSA modulus size is %u bits\n", nbits);
if (nbits && nbits != maxbits
&& app->app_local->extcap.algo_attr_change)
{
/* Try to switch the key to a new length. */
err = change_rsa_keyattr (app, ctrl, keyno, nbits, pincb, pincb_arg);
if (!err)
maxbits = app->app_local->keyattr[keyno].rsa.n_bits;
}
if (nbits != maxbits)
{
log_error (_("RSA modulus missing or not of size %d bits\n"),
(int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.e_bits;
if (maxbits > 32 && !app->app_local->extcap.is_v2)
maxbits = 32; /* Our code for v1 does only support 32 bits. */
nbits = rsa_e? count_bits (rsa_e, rsa_e_len) : 0;
if (nbits < 2 || nbits > maxbits)
{
log_error (_("RSA public exponent missing or larger than %d bits\n"),
(int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
maxbits = app->app_local->keyattr[keyno].rsa.n_bits/2;
nbits = rsa_p? count_bits (rsa_p, rsa_p_len) : 0;
if (nbits != maxbits)
{
log_error (_("RSA prime %s missing or not of size %d bits\n"),
"P", (int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
nbits = rsa_q? count_bits (rsa_q, rsa_q_len) : 0;
if (nbits != maxbits)
{
log_error (_("RSA prime %s missing or not of size %d bits\n"),
"Q", (int)maxbits);
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* We need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
if (app->app_local->extcap.is_v2)
{
unsigned char *rsa_u, *rsa_dp, *rsa_dq;
size_t rsa_u_len, rsa_dp_len, rsa_dq_len;
gcry_mpi_t mpi_e, mpi_p, mpi_q;
gcry_mpi_t mpi_u = gcry_mpi_snew (0);
gcry_mpi_t mpi_dp = gcry_mpi_snew (0);
gcry_mpi_t mpi_dq = gcry_mpi_snew (0);
gcry_mpi_t mpi_tmp = gcry_mpi_snew (0);
int exmode;
/* Calculate the u, dp and dq components needed by RSA_CRT cards */
gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL);
gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL);
gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL);
gcry_mpi_invm (mpi_u, mpi_q, mpi_p);
gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1);
gcry_mpi_invm (mpi_dp, mpi_e, mpi_tmp);
gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1);
gcry_mpi_invm (mpi_dq, mpi_e, mpi_tmp);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_u, &rsa_u_len, mpi_u);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dp, &rsa_dp_len, mpi_dp);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dq, &rsa_dq_len, mpi_dq);
gcry_mpi_release (mpi_e);
gcry_mpi_release (mpi_p);
gcry_mpi_release (mpi_q);
gcry_mpi_release (mpi_u);
gcry_mpi_release (mpi_dp);
gcry_mpi_release (mpi_dq);
gcry_mpi_release (mpi_tmp);
/* Build the private key template as described in section 4.3.3.7 of
the OpenPGP card specs version 2.0. */
err = build_privkey_template (app, keyno,
rsa_n, rsa_n_len,
rsa_e, rsa_e_len,
rsa_p, rsa_p_len,
rsa_q, rsa_q_len,
rsa_u, rsa_u_len,
rsa_dp, rsa_dp_len,
rsa_dq, rsa_dq_len,
&template, &template_len);
xfree(rsa_u);
xfree(rsa_dp);
xfree(rsa_dq);
if (err)
goto leave;
/* Prepare for storing the key. */
err = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (err)
goto leave;
/* Store the key. */
if (app->app_local->cardcap.ext_lc_le && template_len > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
exmode = -254;
else
exmode = 0;
err = iso7816_put_data_odd (app_get_slot (app), exmode, 0x3fff,
template, template_len);
}
else
{
/* Build the private key template as described in section 4.3.3.6 of
the OpenPGP card specs version 1.1:
0xC0 <length> public exponent
0xC1 <length> prime p
0xC2 <length> prime q
*/
log_assert (rsa_e_len <= 4);
template_len = (1 + 1 + 4
+ 1 + 1 + rsa_p_len
+ 1 + 1 + rsa_q_len);
template = tp = xtrymalloc_secure (template_len);
if (!template)
{
err = gpg_error_from_syserror ();
goto leave;
}
*tp++ = 0xC0;
*tp++ = 4;
memcpy (tp, rsa_e, rsa_e_len);
if (rsa_e_len < 4)
{
/* Right justify E. */
memmove (tp+4-rsa_e_len, tp, rsa_e_len);
memset (tp, 0, 4-rsa_e_len);
}
tp += 4;
*tp++ = 0xC1;
*tp++ = rsa_p_len;
memcpy (tp, rsa_p, rsa_p_len);
tp += rsa_p_len;
*tp++ = 0xC2;
*tp++ = rsa_q_len;
memcpy (tp, rsa_q, rsa_q_len);
tp += rsa_q_len;
log_assert (tp - template == template_len);
/* Prepare for storing the key. */
err = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (err)
goto leave;
/* Store the key. */
err = iso7816_put_data (app_get_slot (app), 0,
(app->appversion > 0x0007? 0xE0:0xE9)+keyno,
template, template_len);
}
if (err)
{
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
goto leave;
}
err = store_fpr (app, 1, keyno, created_at, fprbuf, PUBKEY_ALGO_RSA,
rsa_n, rsa_n_len, rsa_e, rsa_e_len);
if (err)
goto leave;
leave:
xfree (template);
return err;
}
static gpg_error_t
ecc_writekey (app_t app, ctrl_t ctrl,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg, int keyno,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *ecc_q = NULL;
const unsigned char *ecc_d = NULL;
size_t ecc_q_len, ecc_d_len;
const unsigned char *ecdh_param = NULL;
size_t ecdh_param_len = 0;
const char *curve = NULL;
u32 created_at = 0;
const char *oidstr;
int flag_djb_tweak = 0;
int algo;
gcry_mpi_t oid = NULL;
const unsigned char *oidbuf;
unsigned int n;
size_t oid_len;
unsigned char fprbuf[20];
size_t ecc_d_fixed_len;
/* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)(ecdh-params%s)):
curve = "NIST P-256" */
/* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
curve = "secp256k1" */
/* (private-key(ecc(curve%s)(flags eddsa)(q%m)(d%m))(created-at%d)):
curve = "Ed25519" */
/* (private-key(ecc(curve%s)(q%m)(d%m))(created-at%d)):
curve = "Ed448" */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
{
char *curve_name;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
curve_name = xtrymalloc (toklen+1);
if (!curve_name)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (curve_name, tok, toklen);
curve_name[toklen] = 0;
curve = openpgp_is_curve_supported (curve_name, NULL, NULL);
xfree (curve_name);
}
else if (tok && toklen == 5 && !memcmp (tok, "flags", 5))
{
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok)
{
if ((toklen == 5 && !memcmp (tok, "eddsa", 5))
|| (toklen == 9 && !memcmp (tok, "djb-tweak", 9)))
flag_djb_tweak = 1;
}
}
else if (tok && toklen == 1)
{
const unsigned char **buf2;
size_t *buf2len;
switch (*tok)
{
case 'q': buf2 = &ecc_q; buf2len = &ecc_q_len; break;
case 'd': buf2 = &ecc_d; buf2len = &ecc_d_len; break;
default: buf2 = NULL; buf2len = NULL; break;
}
if (buf2 && *buf2)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && buf2)
{
*buf2 = tok;
*buf2len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Parse other attributes. */
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 10 && !memcmp ("created-at", tok, toklen))
{
if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
goto leave;
if (tok)
{
for (created_at=0; toklen && *tok && *tok >= '0' && *tok <= '9';
tok++, toklen--)
created_at = created_at*10 + (*tok - '0');
}
}
else if (tok && toklen == 11 && !memcmp ("ecdh-params", tok, toklen))
{
if ((err = parse_sexp (&buf,&buflen,&depth,&tok,&toklen)))
goto leave;
if (tok)
{
ecdh_param = tok;
ecdh_param_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters and that they match the card
description. */
if (!curve)
{
log_error (_("unsupported curve\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (!created_at)
{
log_error (_("creation timestamp missing\n"));
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
if (keyno == 1)
algo = PUBKEY_ALGO_ECDH;
else if (!strcmp (curve, "Ed25519") || !strcmp (curve, "Ed448"))
algo = PUBKEY_ALGO_EDDSA;
else
algo = PUBKEY_ALGO_ECDSA;
if (algo == PUBKEY_ALGO_ECDH && !ecdh_param)
{
/* In case this is used by older clients we fallback to our
* default ecc parameters. */
log_info ("opgp: using default ecdh parameters\n");
ecdh_param = ecdh_params (curve);
ecdh_param_len = 4;
}
oidstr = openpgp_curve_to_oid (curve, &n, NULL);
ecc_d_fixed_len = (n+7)/8;
err = openpgp_oid_from_str (oidstr, &oid);
if (err)
goto leave;
oidbuf = gcry_mpi_get_opaque (oid, &n);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
oid_len = (n+7)/8;
if (app->app_local->keyattr[keyno].key_type != KEY_TYPE_ECC
|| app->app_local->keyattr[keyno].ecc.curve != curve
|| (flag_djb_tweak !=
(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)))
{
if (app->app_local->extcap.algo_attr_change)
{
unsigned char *keyattr;
if (!oid_len)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
keyattr = xtrymalloc (oid_len);
if (!keyattr)
{
err = gpg_error_from_syserror ();
goto leave;
}
keyattr[0] = algo;
memcpy (keyattr+1, oidbuf+1, oid_len-1);
err = change_keyattr (app, ctrl, keyno,
keyattr, oid_len, pincb, pincb_arg);
xfree (keyattr);
if (err)
goto leave;
}
else
{
log_error ("key attribute on card doesn't match\n");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
}
if (opt.verbose)
log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
/* We need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
if (app->app_local->extcap.is_v2)
{
/* Build the private key template as described in section 4.3.3.7 of
the OpenPGP card specs version 2.0. */
unsigned char *template;
size_t template_len;
int exmode;
err = build_ecc_privkey_template (app, keyno,
ecc_d, ecc_d_len, ecc_d_fixed_len,
ecc_q, ecc_q_len,
&template, &template_len);
if (err)
goto leave;
/* Prepare for storing the key. */
err = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (err)
{
xfree (template);
goto leave;
}
/* Store the key. */
if (app->app_local->cardcap.ext_lc_le && template_len > 254)
exmode = 1; /* Use extended length w/o a limit. */
else if (app->app_local->cardcap.cmd_chaining && template_len > 254)
exmode = -254;
else
exmode = 0;
err = iso7816_put_data_odd (app_get_slot (app), exmode, 0x3fff,
template, template_len);
xfree (template);
}
else
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
if (err)
{
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
goto leave;
}
err = store_fpr (app, 1, keyno, created_at, fprbuf, algo, oidbuf, oid_len,
ecc_q, ecc_q_len, ecdh_param, ecdh_param_len);
leave:
gcry_mpi_release (oid);
return err;
}
/* Handle the WRITEKEY command for OpenPGP. This function expects a
canonical encoded S-expression with the secret key in KEYDATA and
its length (for assertions) in KEYDATALEN. KEYID needs to be the
usual keyid which for OpenPGP is the string "OPENPGP.n" with
n=1,2,3. Bit 0 of FLAGS indicates whether an existing key shall
get overwritten. PINCB and PINCB_ARG are the usual arguments for
the pinentry callback. */
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyid, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = (flags & 1);
int keyno;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth;
char *algostr = NULL;
(void)ctrl;
if (!ascii_strcasecmp (keyid, "OPENPGP.1"))
keyno = 0;
else if (!ascii_strcasecmp (keyid, "OPENPGP.2"))
keyno = 1;
else if (!ascii_strcasecmp (keyid, "OPENPGP.3"))
keyno = 2;
else
return gpg_error (GPG_ERR_INV_ID);
err = does_key_exist (app, keyno, 0, force);
if (err)
return err;
/*
Parse the S-expression
*/
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
{
if (!tok)
;
else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
log_info ("protected-private-key passed to writekey\n");
else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
log_info ("shadowed-private-key passed to writekey\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 3 && (!memcmp ("rsa", tok, toklen)
|| !memcmp ("ecc", tok, toklen)))
{
gcry_sexp_t stmp;
if (!gcry_sexp_new (&stmp, keydata, keydatalen, 0))
algostr = pubkey_algo_string (stmp, NULL);
else
algostr = NULL;
gcry_sexp_release (stmp);
if (app->app_local->keyattr[keyno].keyalgo && algostr
&& strcmp (app->app_local->keyattr[keyno].keyalgo, algostr))
{
log_info ("openpgp: changing key attribute from %s to %s\n",
app->app_local->keyattr[keyno].keyalgo, algostr);
err = change_keyattr_from_string (app, ctrl, pincb, pincb_arg,
keyid, algostr, NULL, 0);
if (err)
return err;
}
if (*tok == 'r')
err = rsa_writekey (app, ctrl, pincb, pincb_arg, keyno,
buf,buflen,depth);
else
err = ecc_writekey (app, ctrl, pincb, pincb_arg, keyno,
buf, buflen, depth);
}
else
{
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
}
leave:
xfree (algostr);
return err;
}
/* Handle the GENKEY command. */
static gpg_error_t
do_genkey (app_t app, ctrl_t ctrl, const char *keyref, const char *keyalgo,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
char numbuf[30];
const char *keynostr;
unsigned char *buffer = NULL;
const unsigned char *keydata;
size_t buflen, keydatalen;
u32 created_at;
int keyno;
int force = !!(flags & APP_GENKEY_FLAG_FORCE);
time_t start_at;
int exmode = 0;
int le_value = 256; /* Use legacy value. */
/* Strip the OpenPGP prefix which is for historical reasons optional. */
keynostr = keyref;
if (!ascii_strncasecmp (keynostr, "OPENPGP.", 8))
keynostr += 8;
keyno = atoi (keynostr) - 1;
if (!digitp (keynostr) || keyno < 0 || keyno > 2)
return gpg_error (GPG_ERR_INV_ID);
/* We flush the cache to increase the traffic before a key
generation. This _might_ help a card to gather more entropy. */
flush_cache (app);
/* Obviously we need to remove the cached public key. */
xfree (app->app_local->pk[keyno].key);
app->app_local->pk[keyno].key = NULL;
app->app_local->pk[keyno].keylen = 0;
app->app_local->pk[keyno].read_done = 0;
/* Check whether a key already exists. */
err = does_key_exist (app, keyno, 1, force);
if (err)
return err;
if (keyalgo && app->app_local->keyattr[keyno].keyalgo
&& strcmp (keyalgo, app->app_local->keyattr[keyno].keyalgo))
{
/* Specific algorithm requested which is not the currently
* configured algorithm. Change it. */
log_info ("openpgp: changing key attribute from %s to %s\n",
app->app_local->keyattr[keyno].keyalgo, keyalgo);
err = change_keyattr_from_string (app, ctrl, pincb, pincb_arg,
keyref, keyalgo, NULL, 0);
if (err)
return err;
}
if (app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
{
unsigned int keybits = app->app_local->keyattr[keyno].rsa.n_bits;
/* Because we send the key parameter back via status lines we need
to put a limit on the max. allowed keysize. 2048 bit will
already lead to a 527 byte long status line and thus a 4096 bit
key would exceed the Assuan line length limit. */
if (keybits > 4096)
return gpg_error (GPG_ERR_TOO_LARGE);
if (app->app_local->cardcap.ext_lc_le && keybits > RSA_SMALL_SIZE_KEY
&& app->app_local->keyattr[keyno].key_type == KEY_TYPE_RSA)
{
exmode = 1; /* Use extended length w/o a limit. */
le_value = determine_rsa_response (app, keyno);
/* No need to check le_value because it comes from a 16 bit
value and thus can't create an overflow on a 32 bit
system. */
}
}
/* Prepare for key generation by verifying the Admin PIN. */
err = verify_chv3 (app, ctrl, pincb, pincb_arg);
if (err)
return err;
log_info (_("please wait while key is being generated ...\n"));
start_at = time (NULL);
err = iso7816_generate_keypair (app_get_slot (app), exmode, 0x80, 0,
(keyno == 0? "\xB6" :
keyno == 1? "\xB8" : "\xA4"),
2, le_value, &buffer, &buflen);
if (err)
{
log_error (_("generating key failed\n"));
return gpg_error (GPG_ERR_CARD);
}
{
int nsecs = (int)(time (NULL) - start_at);
log_info (ngettext("key generation completed (%d second)\n",
"key generation completed (%d seconds)\n",
nsecs), nsecs);
}
keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
if (!keydata)
{
err = gpg_error (GPG_ERR_CARD);
log_error (_("response does not contain the public key data\n"));
goto leave;
}
created_at = (u32)(createtime? createtime : gnupg_get_time ());
sprintf (numbuf, "%u", created_at);
send_status_info (ctrl, "KEY-CREATED-AT",
numbuf, (size_t)strlen(numbuf), NULL, 0);
err = read_public_key (app, ctrl, 1, created_at, keyno, buffer, buflen);
leave:
xfree (buffer);
return err;
}
static unsigned long
convert_sig_counter_value (const unsigned char *value, size_t valuelen)
{
unsigned long ul;
if (valuelen == 3 )
ul = (value[0] << 16) | (value[1] << 8) | value[2];
else
{
log_error (_("invalid structure of OpenPGP card (DO 0x93)\n"));
ul = 0;
}
return ul;
}
static unsigned long
get_sig_counter (app_t app)
{
void *relptr;
unsigned char *value;
size_t valuelen;
unsigned long ul;
relptr = get_one_do (app, 0x0093, &value, &valuelen, NULL);
if (!relptr)
return 0;
ul = convert_sig_counter_value (value, valuelen);
xfree (relptr);
return ul;
}
static gpg_error_t
compare_fingerprint (app_t app, int keyno, unsigned char *sha1fpr)
{
const unsigned char *fpr;
unsigned char *buffer;
size_t buflen, n;
int rc, i;
log_assert (keyno >= 0 && keyno <= 2);
rc = get_cached_data (app, 0x006E, &buffer, &buflen, 0, 0);
if (rc)
{
log_error (_("error reading application data\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr = find_tlv (buffer, buflen, 0x00C5, &n);
if (!fpr || n < 60)
{
xfree (buffer);
log_error (_("error reading fingerprint DO\n"));
return gpg_error (GPG_ERR_GENERAL);
}
fpr += keyno*20;
for (i=0; i < 20; i++)
if (sha1fpr[i] != fpr[i])
{
xfree (buffer);
log_info (_("fingerprint on card does not match requested one\n"));
return gpg_error (GPG_ERR_WRONG_SECKEY);
}
xfree (buffer);
return 0;
}
/* If a fingerprint has been specified check it against the one on the
card. This allows for a meaningful error message in case the key
on the card has been replaced but the shadow information known to
gpg has not been updated. If there is no fingerprint we assume
that this is okay. */
static gpg_error_t
check_against_given_fingerprint (app_t app, const char *fpr, int key)
{
unsigned char tmp[20];
const char *s;
int n;
for (s=fpr, n=0; hexdigitp (s); s++, n++)
;
if (n != 40)
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* okay */
else
return gpg_error (GPG_ERR_INV_ID);
for (s=fpr, n=0; n < 20; s += 2, n++)
tmp[n] = xtoi_2 (s);
return compare_fingerprint (app, key-1, tmp);
}
/* Check KEYIDSTR, if it's valid.
When KEYNO is 0, it means it's for PIN check.
Otherwise, KEYNO corresponds to the slot (signing, decipher and auth).
KEYIDSTR is either:
(1) Serial number
(2) Serial number "/" fingerprint
(3) Serial number "[CHV3]"
(4) keygrip
When KEYNO is 0 and KEYIDSTR is for a keygrip, the keygrip should
be to be compared is the first one (keygrip for signing).
When KEYNO is 1, KEYIDSTR is for a keygrip, and R_USE_AUTH is not
NULL, OpenPGP.1 is first tested and then OpenPGP.3. In the latter
case 1 is stored at R_USE_AUTH
*/
static int
check_keyidstr (app_t app, const char *keyidstr, int keyno, int *r_use_auth)
{
int rc;
const char *s;
int n;
const char *fpr = NULL;
if (r_use_auth)
*r_use_auth = 0;
if (strlen (keyidstr) < 32)
return gpg_error (GPG_ERR_INV_ID);
else
{
char *serial;
for (s=keyidstr, n=0; hexdigitp (s); s++, n++)
;
/* Check if it's a keygrip */
if (n == 40)
{
const unsigned char *keygrip_str;
keygrip_str = app->app_local->pk[keyno?keyno-1:0].keygrip_str;
if (!strncmp (keygrip_str, keyidstr, 40))
return 0;
else if (keyno == 1 && r_use_auth
&& !strncmp (app->app_local->pk[2].keygrip_str,
keyidstr, 40))
{
*r_use_auth = 1;
return 0;
}
else
return gpg_error (GPG_ERR_INV_ID);
}
/* For a description of the serialno compare function see
* is_same_serialno. We don't use that function because here we
* are working on a hex string. */
if (n != 32 || ascii_strncasecmp (keyidstr, "D27600012401", 12))
return gpg_error (GPG_ERR_INV_ID);
else if (!*s)
; /* no fingerprint given: we allow this for now. */
else if (*s == '/')
fpr = s + 1;
serial = app_get_serialno (app);
if (!serial || strlen (serial) != 32
|| ascii_memcasecmp (serial, "D27600012401", 12)
|| ascii_memcasecmp (serial+16, keyidstr+16, 16))
{
xfree (serial);
return gpg_error (GPG_ERR_WRONG_CARD);
}
xfree (serial);
}
/* If a fingerprint has been specified check it against the one on
the card. This is allows for a meaningful error message in case
the key on the card has been replaced but the shadow information
known to gpg was not updated. If there is no fingerprint, gpg
will detect a bogus signature anyway due to the
verify-after-signing feature. */
rc = (fpr&&keyno)? check_against_given_fingerprint (app, fpr, keyno) : 0;
return rc;
}
static const unsigned char rmd160_prefix[15] = /* Object ID is 1.3.36.3.2.1 */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x24, 0x03,
0x02, 0x01, 0x05, 0x00, 0x04, 0x14 };
static const unsigned char sha1_prefix[15] = /* (1.3.14.3.2.26) */
{ 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e, 0x03,
0x02, 0x1a, 0x05, 0x00, 0x04, 0x14 };
static const unsigned char sha224_prefix[19] = /* (2.16.840.1.101.3.4.2.4) */
{ 0x30, 0x2D, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86, 0x48,
0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05, 0x00, 0x04,
0x1C };
static const unsigned char sha256_prefix[19] = /* (2.16.840.1.101.3.4.2.1) */
{ 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
0x00, 0x04, 0x20 };
static const unsigned char sha384_prefix[19] = /* (2.16.840.1.101.3.4.2.2) */
{ 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
0x00, 0x04, 0x30 };
static const unsigned char sha512_prefix[19] = /* (2.16.840.1.101.3.4.2.3) */
{ 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
0x00, 0x04, 0x40 };
/* Compute a digital signature on INDATA which is expected to be the
raw message digest. For this application the KEYIDSTR consists of
the serialnumber and the fingerprint delimited by a slash.
Note that this function may return the error code
GPG_ERR_WRONG_CARD to indicate that the card currently present does
not match the one required for the requested action (e.g. the
serial number does not match).
As a special feature a KEYIDSTR of "OPENPGP.3" redirects the
operation to the auth command.
*/
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
int rc;
unsigned char data[19+64];
size_t datalen;
unsigned long sigcount;
int use_auth = 0;
int exmode, le_value;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
/* Strip off known prefixes. */
#define X(a,b,c,d) \
if (hashalgo == GCRY_MD_ ## a \
&& (d) \
&& indatalen == sizeof b ## _prefix + (c) \
&& !memcmp (indata, b ## _prefix, sizeof b ## _prefix)) \
{ \
indata = (const char*)indata + sizeof b ## _prefix; \
indatalen -= sizeof b ## _prefix; \
}
if (indatalen == 20)
; /* Assume a plain SHA-1 or RMD160 digest has been given. */
else X(SHA1, sha1, 20, 1)
else X(RMD160, rmd160, 20, 1)
else X(SHA224, sha224, 28, app->app_local->extcap.is_v2)
else X(SHA256, sha256, 32, app->app_local->extcap.is_v2)
else X(SHA384, sha384, 48, app->app_local->extcap.is_v2)
else X(SHA512, sha512, 64, app->app_local->extcap.is_v2)
else if ((indatalen == 28 || indatalen == 32
|| indatalen == 48 || indatalen ==64)
&& app->app_local->extcap.is_v2)
; /* Assume a plain SHA-3 digest has been given. */
else
{
log_error (_("card does not support digest algorithm %s\n"),
gcry_md_algo_name (hashalgo));
/* Or the supplied digest length does not match an algorithm. */
return gpg_error (GPG_ERR_INV_VALUE);
}
#undef X
/* Check whether an OpenPGP card of any version has been requested. */
if (!ascii_strcasecmp (keyidstr, "OPENPGP.1"))
;
else if (!ascii_strcasecmp (keyidstr, "OPENPGP.3"))
use_auth = 1;
else
{
rc = check_keyidstr (app, keyidstr, 1, &use_auth);
if (rc)
return rc;
}
/* Concatenate prefix and digest. */
#define X(a,b,d) \
if (hashalgo == GCRY_MD_ ## a && (d) ) \
{ \
datalen = sizeof b ## _prefix + indatalen; \
log_assert (datalen <= sizeof data); \
memcpy (data, b ## _prefix, sizeof b ## _prefix); \
memcpy (data + sizeof b ## _prefix, indata, indatalen); \
}
if (use_auth
|| app->app_local->keyattr[use_auth? 2: 0].key_type == KEY_TYPE_RSA)
{
X(SHA1, sha1, 1)
else X(RMD160, rmd160, 1)
else X(SHA224, sha224, app->app_local->extcap.is_v2)
else X(SHA256, sha256, app->app_local->extcap.is_v2)
else X(SHA384, sha384, app->app_local->extcap.is_v2)
else X(SHA512, sha512, app->app_local->extcap.is_v2)
else
return gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
}
else
{
datalen = indatalen;
memcpy (data, indata, indatalen);
}
#undef X
/* Redirect to the AUTH command if asked to. */
if (use_auth)
{
return do_auth (app, ctrl, "OPENPGP.3", pincb, pincb_arg,
data, datalen,
outdata, outdatalen);
}
/* Show the number of signature done using this key. */
sigcount = get_sig_counter (app);
log_info (_("signatures created so far: %lu\n"), sigcount);
/* Check CHV if needed. */
if (!app->did_chv1 || app->force_chv1)
{
char *pinvalue;
size_t pinlen;
rc = verify_a_chv (app, ctrl, pincb, pincb_arg, 1, sigcount,
&pinvalue, &pinlen);
if (rc)
return rc;
app->did_chv1 = 1;
/* For cards with versions < 2 we want to keep CHV1 and CHV2 in
sync, thus we verify CHV2 here using the given PIN. Cards
with version2 to not have the need for a separate CHV2 and
internally use just one. Obviously we can't do that if the
pinpad has been used. */
if (!app->did_chv2 && pinvalue && !app->app_local->extcap.is_v2)
{
rc = iso7816_verify (app_get_slot (app), 0x82, pinvalue, pinlen);
if (gpg_err_code (rc) == GPG_ERR_BAD_PIN)
rc = gpg_error (GPG_ERR_PIN_NOT_SYNCED);
if (rc)
{
log_error (_("verify CHV%d failed: %s\n"), 2, gpg_strerror (rc));
wipe_and_free (pinvalue, pinlen);
flush_cache_after_error (app);
return rc;
}
app->did_chv2 = 1;
cache_pin (app, ctrl, 2, pinvalue);
}
wipe_and_free (pinvalue, pinlen);
}
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[0].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[0].rsa.n_bits > RSA_SMALL_SIZE_OP)
{
exmode = 1; /* Use extended length. */
le_value = app->app_local->keyattr[0].rsa.n_bits / 8;
}
else
{
exmode = 0;
le_value = 0;
}
rc = iso7816_compute_ds (app_get_slot (app), exmode, data, datalen, le_value,
outdata, outdatalen);
if (gpg_err_code (rc) == GPG_ERR_TIMEOUT)
clear_chv_status (app, ctrl, 1);
else if (!rc && app->force_chv1)
{
app->did_chv1 = 0;
cache_pin (app, ctrl, 1, NULL);
}
return rc;
}
/* Generate data to be signed for PKAUTH with --challenge-response. */
static gpg_error_t
gen_challenge (app_t app, const void **r_data, size_t *r_datalen)
{
void *data;
size_t datalen;
int header_size;
const unsigned char *hash_prefix = NULL;
if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC)
{
unsigned int n;
openpgp_curve_to_oid (app->app_local->keyattr[2].ecc.curve, &n, NULL);
/* No hash algo header, and appropriate length of random octets,
determined by field size of the curve. */
datalen = (n+7)/8;
header_size = 0;
}
else
{
/* Hash algo header, and random octets of hash size, the hash
algo is determined by size of key. */
if (app->app_local->keyattr[2].rsa.n_bits <= 2048)
{
datalen = 32;
hash_prefix = sha256_prefix;
}
else if (app->app_local->keyattr[2].rsa.n_bits <= 3072)
{
datalen = 48;
hash_prefix = sha384_prefix;
}
else
{
datalen = 64;
hash_prefix = sha512_prefix;
}
header_size = 19;
}
data = xtrymalloc (datalen+header_size);
if (!data)
return gpg_error_from_syserror ();
if (hash_prefix)
memcpy (data, hash_prefix, header_size);
gcry_create_nonce ((char *)data+header_size, datalen);
*r_data = data;
*r_datalen = datalen+header_size;
return 0;
}
/* Compute a digital signature using the INTERNAL AUTHENTICATE command
on INDATA which is expected to be the raw message digest. For this
application the KEYIDSTR consists of the serialnumber and the
fingerprint delimited by a slash. Optionally the id OPENPGP.3 may
be given.
Note that this function may return the error code
GPG_ERR_WRONG_CARD to indicate that the card currently present does
not match the one required for the requested action (e.g. the
serial number does not match). */
static gpg_error_t
do_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
int rc;
int challenge_generated = 0;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (indatalen == 0)
{
rc = get_public_key (app, 2);
if (rc)
return rc;
rc = gen_challenge (app, &indata, &indatalen);
if (rc)
return rc;
challenge_generated = 1;
goto indata_ready;
}
if (app->app_local->keyattr[2].key_type == KEY_TYPE_RSA)
{
int size_40percent = (app->app_local->keyattr[2].rsa.n_bits+7)/8 * 4;
/* OpenPGP card does PKCS#1 for RSA, data should not be larger
than 40% of the modulus length. */
if (indatalen * 10 > size_40percent)
return gpg_error (GPG_ERR_INV_VALUE);
}
if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC)
{
if (!(app->app_local->keyattr[2].ecc.flags & ECC_FLAG_DJB_TWEAK)
&& (indatalen == 51 || indatalen == 67 || indatalen == 83))
{
const char *p = (const char *)indata + 19;
indata = p;
indatalen -= 19;
}
else
{
const char *p = (const char *)indata + 15;
indata = p;
indatalen -= 15;
}
}
indata_ready:
/* Check whether an OpenPGP card of any version has been requested. */
if (!ascii_strcasecmp (keyidstr, "OPENPGP.3"))
;
else
{
rc = check_keyidstr (app, keyidstr, 3, NULL);
if (rc)
return rc;
}
rc = verify_chv2 (app, ctrl, pincb, pincb_arg);
if (!rc)
{
int exmode, le_value;
if (app->app_local->cardcap.ext_lc_le
&& app->app_local->keyattr[2].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[2].rsa.n_bits > RSA_SMALL_SIZE_OP)
{
exmode = 1; /* Use extended length. */
le_value = app->app_local->keyattr[2].rsa.n_bits / 8;
}
else if (app->app_local->cardcap.cmd_chaining && indatalen > 254)
{
exmode = -254; /* Command chaining with max. 254 bytes. */
le_value = 0;
}
else if (indatalen > 255)
{
if (!app->app_local->cardcap.ext_lc_le)
return gpg_error (GPG_ERR_TOO_LARGE);
exmode = 1;
le_value = 0;
}
else
{
exmode = 0;
le_value = 0;
}
rc = iso7816_internal_authenticate (app_get_slot (app), exmode,
indata, indatalen, le_value,
outdata, outdatalen);
if (gpg_err_code (rc) == GPG_ERR_TIMEOUT)
clear_chv_status (app, ctrl, 1);
/* Verify the result, when CHALLENGE_GENERATED */
if (challenge_generated)
{
gcry_sexp_t s_pkey, s_sig, s_hash;
const char *fmt;
if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC)
{
if (!strcmp (app->app_local->keyattr[2].ecc.curve, "Ed25519"))
fmt = "(data(flags eddsa)(hash-algo sha512)(value %b))";
else
fmt = "(data(value %b))";
}
else
{
void *old_indata = (void *)indata;
unsigned char *new_indata;
size_t new_indatalen;
/* For RSA, it's PKCS#1 padding. */
new_indatalen = app->app_local->keyattr[2].rsa.n_bits / 8;
new_indata = xtrymalloc (new_indatalen);
if (!new_indata)
{
rc = gpg_error_from_syserror ();
xfree (old_indata);
return rc;
}
memset (new_indata, 0xff, new_indatalen);
new_indata[0] = 0x00;
new_indata[1] = 0x01;
new_indata[new_indatalen - indatalen -1] = 0x00;
memcpy (new_indata + new_indatalen - indatalen,
indata, indatalen);
xfree (old_indata);
indata = new_indata;
indatalen = new_indatalen;
fmt = "%b"; /* Old style data format. */
}
rc = gcry_sexp_build (&s_hash, NULL, fmt, (int)indatalen, indata);
if (rc)
{
xfree ((void *)indata);
return rc;
}
if (app->app_local->keyattr[2].key_type == KEY_TYPE_ECC)
{
if (!strcmp (app->app_local->keyattr[2].ecc.curve, "Ed25519")
|| !strcmp (app->app_local->keyattr[2].ecc.curve, "Ed448"))
fmt = "(sig-val(eddsa(r %b)(s %b)))";
else
fmt = "(sig-val(ecdsa(r %b)(s %b)))";
rc = gcry_sexp_build (&s_sig, NULL, fmt,
(int)*outdatalen/2, *outdata,
(int)*outdatalen/2, *outdata+*outdatalen/2);
}
else
{
fmt = "(sig-val(rsa(s %b)))";
rc = gcry_sexp_build (&s_sig, NULL, fmt,
(int)*outdatalen, *outdata);
}
if (rc)
{
gcry_sexp_release (s_hash);
xfree ((void *)indata);
return rc;
}
rc = gcry_sexp_new (&s_pkey, app->app_local->pk[2].key,
app->app_local->pk[2].keylen, 0);
if (rc)
{
gcry_sexp_release (s_hash);
gcry_sexp_release (s_sig);
xfree ((void *)indata);
return rc;
}
rc = gcry_pk_verify (s_sig, s_hash, s_pkey);
gcry_sexp_release (s_hash);
gcry_sexp_release (s_sig);
gcry_sexp_release (s_pkey);
xfree ((void *)indata);
}
}
return rc;
}
static gpg_error_t
do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
int n;
int rc;
int exmode, le_value;
unsigned char *fixbuf = NULL;
int padind = 0;
int fixuplen = 0;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
/* Check whether an OpenPGP card of any version has been requested. */
if (!ascii_strcasecmp (keyidstr, "OPENPGP.2"))
;
else
{
rc = check_keyidstr (app, keyidstr, 2, NULL);
if (rc)
return rc;
}
rc = verify_chv2 (app, ctrl, pincb, pincb_arg);
if (rc)
return rc;
if ((indatalen == 16 + 1 || indatalen == 32 + 1)
&& ((char *)indata)[0] == 0x02)
{
/* PSO:DECIPHER with symmetric key. */
padind = -1;
}
else if (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA)
{
/* We might encounter a couple of leading zeroes in the
cryptogram. Due to internal use of MPIs these leading zeroes
are stripped. However the OpenPGP card expects exactly 128
bytes for the cryptogram (for a 1k key). Thus we need to fix
it up. We do this for up to 16 leading zero bytes; a
cryptogram with more than this is with a very high
probability anyway broken. If a signed conversion was used
we may also encounter one leading zero followed by the correct
length. We fix that as well. */
if (indatalen >= (128-16) && indatalen < 128) /* 1024 bit key. */
fixuplen = 128 - indatalen;
else if (indatalen >= (192-16) && indatalen < 192) /* 1536 bit key. */
fixuplen = 192 - indatalen;
else if (indatalen >= (256-16) && indatalen < 256) /* 2048 bit key. */
fixuplen = 256 - indatalen;
else if (indatalen >= (384-16) && indatalen < 384) /* 3072 bit key. */
fixuplen = 384 - indatalen;
else if (indatalen >= (512-16) && indatalen < 512) /* 4096 bit key. */
fixuplen = 512 - indatalen;
else if (!*(const char *)indata && (indatalen == 129
|| indatalen == 193
|| indatalen == 257
|| indatalen == 385
|| indatalen == 513))
fixuplen = -1;
else
fixuplen = 0;
if (fixuplen > 0)
{
/* While we have to prepend stuff anyway, we can also
include the padding byte here so that iso1816_decipher
does not need to do another data mangling. */
fixuplen++;
fixbuf = xtrymalloc (fixuplen + indatalen);
if (!fixbuf)
return gpg_error_from_syserror ();
memset (fixbuf, 0, fixuplen);
memcpy (fixbuf+fixuplen, indata, indatalen);
indata = fixbuf;
indatalen = fixuplen + indatalen;
padind = -1; /* Already padded. */
}
else if (fixuplen < 0)
{
/* We use the extra leading zero as the padding byte. */
padind = -1;
}
}
else if (app->app_local->keyattr[1].key_type == KEY_TYPE_ECC)
{
int old_format_len = 0;
if ((app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK))
{
if (indatalen > 32 && (indatalen % 2))
{ /*
* Skip the prefix. It may be 0x40 (in new format), or MPI
* head of 0x00 (in old format).
*/
indata = (const char *)indata + 1;
indatalen--;
}
else if (indatalen < 32)
{ /*
* Old format trancated by MPI handling.
*/
old_format_len = indatalen;
indatalen = 32;
}
}
n = 0;
if (indatalen < 128)
fixuplen = 7;
else
fixuplen = 10;
fixbuf = xtrymalloc (fixuplen + indatalen);
if (!fixbuf)
return gpg_error_from_syserror ();
/* Build 'Cipher DO' */
fixbuf[n++] = '\xa6';
if (indatalen < 128)
fixbuf[n++] = (char)(indatalen+5);
else
{
fixbuf[n++] = 0x81;
fixbuf[n++] = (char)(indatalen+7);
}
fixbuf[n++] = '\x7f';
fixbuf[n++] = '\x49';
if (indatalen < 128)
fixbuf[n++] = (char)(indatalen+2);
else
{
fixbuf[n++] = 0x81;
fixbuf[n++] = (char)(indatalen+3);
}
fixbuf[n++] = '\x86';
if (indatalen < 128)
fixbuf[n++] = (char)indatalen;
else
{
fixbuf[n++] = 0x81;
fixbuf[n++] = (char)indatalen;
}
if (old_format_len)
{
memset (fixbuf+fixuplen, 0, 32 - old_format_len);
memcpy (fixbuf+fixuplen + 32 - old_format_len,
indata, old_format_len);
}
else
{
memcpy (fixbuf+fixuplen, indata, indatalen);
}
indata = fixbuf;
indatalen = fixuplen + indatalen;
padind = -1;
}
else
return gpg_error (GPG_ERR_INV_VALUE);
if (app->app_local->cardcap.ext_lc_le
&& (indatalen > 254
|| (app->app_local->keyattr[1].key_type == KEY_TYPE_RSA
&& app->app_local->keyattr[1].rsa.n_bits > RSA_SMALL_SIZE_OP)))
{
exmode = 1; /* Extended length w/o a limit. */
le_value = app->app_local->keyattr[1].rsa.n_bits / 8;
}
else if (app->app_local->cardcap.cmd_chaining && indatalen > 254)
{
exmode = -254; /* Command chaining with max. 254 bytes. */
le_value = 0;
}
else
exmode = le_value = 0;
rc = iso7816_decipher (app_get_slot (app), exmode,
indata, indatalen, le_value, padind,
outdata, outdatalen);
xfree (fixbuf);
if (!rc && app->app_local->keyattr[1].key_type == KEY_TYPE_ECC)
{
unsigned char prefix = 0;
if (app->app_local->keyattr[1].ecc.flags & ECC_FLAG_DJB_TWEAK)
prefix = 0x40;
else if (*outdatalen == 56) /* It's X448 with no prefix. */
;
else if ((*outdatalen % 2) == 0) /* No 0x04 -> x-coordinate only */
prefix = 0x41;
if (prefix)
{ /* Add the prefix */
fixbuf = xtrymalloc (*outdatalen + 1);
if (!fixbuf)
{
xfree (*outdata);
return gpg_error_from_syserror ();
}
fixbuf[0] = prefix;
memcpy (fixbuf+1, *outdata, *outdatalen);
xfree (*outdata);
*outdata = fixbuf;
*outdatalen = *outdatalen + 1;
}
}
if (gpg_err_code (rc) == GPG_ERR_TIMEOUT)
clear_chv_status (app, ctrl, 1);
if (gpg_err_code (rc) == GPG_ERR_CARD /* actual SW is 0x640a */
&& app->app_local->manufacturer == 5
&& app->appversion == 0x0200)
log_info ("NOTE: Cards with manufacturer id 5 and s/n <= 346 (0x15a)"
" do not work with encryption keys > 2048 bits\n");
*r_info |= APP_DECIPHER_INFO_NOPAD;
return rc;
}
/* Perform a simple verify operation for CHV1 and CHV2, so that
further operations won't ask for CHV2 and it is possible to do a
cheap check on the PIN: If there is something wrong with the PIN
entry system, only the regular CHV will get blocked and not the
dangerous CHV3. KEYIDSTR is the usual card's serial number; an
optional fingerprint part will be ignored.
There is a special mode if the keyidstr is "<serialno>[CHV3]" with
the "[CHV3]" being a literal string: The Admin Pin is checked if
and only if the retry counter is still at 3. */
static gpg_error_t
do_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int admin_pin = 0;
int rc;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
rc = check_keyidstr (app, keyidstr, 0, NULL);
if (rc)
return rc;
if ((strlen (keyidstr) >= 32+6 && !strcmp (keyidstr+32, "[CHV3]"))
|| (strlen (keyidstr) >= 40+6 && !strcmp (keyidstr+40, "[CHV3]")))
admin_pin = 1;
/* Yes, there is a race conditions: The user might pull the card
right here and we won't notice that. However this is not a
problem and the check above is merely for a graceful failure
between operations. */
if (admin_pin)
{
void *relptr;
unsigned char *value;
size_t valuelen;
int count;
relptr = get_one_do (app, 0x00C4, &value, &valuelen, NULL);
if (!relptr || valuelen < 7)
{
log_error (_("error retrieving CHV status from card\n"));
xfree (relptr);
return gpg_error (GPG_ERR_CARD);
}
count = value[6];
xfree (relptr);
if (!count)
{
log_info (_("card is permanently locked!\n"));
return gpg_error (GPG_ERR_PIN_BLOCKED);
}
else if (count < 3)
{
log_info (_("verification of Admin PIN is currently prohibited "
"through this command\n"));
return gpg_error (GPG_ERR_GENERAL);
}
app->did_chv3 = 0; /* Force verification. */
return verify_chv3 (app, ctrl, pincb, pincb_arg);
}
else
return verify_chv2 (app, ctrl, pincb, pincb_arg);
}
static void
send_keyinfo_if_available (app_t app, ctrl_t ctrl, char *serial,
int data, int i)
{
char idbuf[50];
const char *usage = get_usage_string (i);
if (app->app_local->pk[i].read_done)
{
sprintf (idbuf, "OPENPGP.%d", i+1);
send_keyinfo (ctrl, data,
app->app_local->pk[i].keygrip_str, serial, idbuf, usage);
}
}
static gpg_error_t
do_with_keygrip (app_t app, ctrl_t ctrl, int action, const char *keygrip_str,
int capability)
{
int i;
/* Make sure we have load the public keys. */
for (i = 0; i < 3; i++)
get_public_key (app, i);
if (action == KEYGRIP_ACTION_LOOKUP)
{
if (keygrip_str == NULL)
return gpg_error (GPG_ERR_NOT_FOUND);
for (i = 0; i < 3; i++)
if (app->app_local->pk[i].read_done
&& !strcmp (keygrip_str, app->app_local->pk[i].keygrip_str))
return 0; /* Found */
}
else
{
int data = (action == KEYGRIP_ACTION_SEND_DATA);
char *serial = app_get_serialno (app);
if (keygrip_str == NULL)
{
if (capability == 0)
{
for (i = 0; i < 3; i++)
send_keyinfo_if_available (app, ctrl, serial, data, i);
}
else
{
if (capability == GCRY_PK_USAGE_SIGN)
i = 0;
else if (capability == GCRY_PK_USAGE_ENCR)
i = 1;
else if (capability == GCRY_PK_USAGE_AUTH)
i = 2;
else
i = -1;
if (i >= 0)
send_keyinfo_if_available (app, ctrl, serial, data, i);
}
xfree (serial);
/* Return an error so that the dispatcher keeps on looping
* over the other applications. Only for clarity we use a
* different error code than for the not_found case. */
return gpg_error (GPG_ERR_TRUE);
}
else
{
for (i = 0; i < 3; i++)
if (!strcmp (keygrip_str, app->app_local->pk[i].keygrip_str))
{
send_keyinfo_if_available (app, ctrl, serial, data, i);
xfree (serial);
return 0;
}
xfree (serial);
}
}
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Show information about card capabilities. */
static void
show_caps (struct app_local_s *s)
{
log_info ("Version-2+ .....: %s\n", s->extcap.is_v2? "yes":"no");
log_info ("Version-3+ .....: %s\n", s->extcap.is_v3? "yes":"no");
log_info ("Button .........: %s\n", s->extcap.has_button? "yes":"no");
log_info ("SM-Support .....: %s", s->extcap.sm_supported? "yes":"no");
if (s->extcap.sm_supported)
log_printf (" (%s)", s->extcap.sm_algo==2? "3DES":
(s->extcap.sm_algo==2? "AES-128" : "AES-256"));
log_info ("Get-Challenge ..: %s", s->extcap.get_challenge? "yes":"no");
if (s->extcap.get_challenge)
log_printf (" (%u bytes max)", s->extcap.max_get_challenge);
log_info ("Key-Import .....: %s\n", s->extcap.key_import? "yes":"no");
log_info ("Change-Force-PW1: %s\n", s->extcap.change_force_chv? "yes":"no");
log_info ("Private-DOs ....: %s\n", s->extcap.private_dos? "yes":"no");
log_info ("Algo-Attr-Change: %s\n", s->extcap.algo_attr_change? "yes":"no");
log_info ("Symmetric Crypto: %s\n", s->extcap.has_decrypt? "yes":"no");
log_info ("KDF-Support ....: %s\n", s->extcap.kdf_do? "yes":"no");
log_info ("Max-Cert-Len ...: %u\n", s->extcap.max_certlen);
if (s->extcap.is_v3)
{
log_info ("PIN-Block-2 ....: %s\n", s->extcap.pin_blk2? "yes":"no");
log_info ("MSE-Support ....: %s\n", s->extcap.mse? "yes":"no");
log_info ("Max-Special-DOs : %u\n", s->extcap.max_special_do);
}
log_info ("Cmd-Chaining ...: %s\n", s->cardcap.cmd_chaining?"yes":"no");
log_info ("Ext-Lc-Le ......: %s\n", s->cardcap.ext_lc_le?"yes":"no");
log_info ("Status-Indicator: %02X\n", s->status_indicator);
log_info ("GnuPG-No-Sync ..: %s\n", s->flags.no_sync? "yes":"no");
log_info ("GnuPG-Def-PW2 ..: %s\n", s->flags.def_chv2? "yes":"no");
}
/* Parse the historical bytes in BUFFER of BUFLEN and store them in
APPLOC. */
static void
parse_historical (struct app_local_s *apploc,
const unsigned char * buffer, size_t buflen)
{
/* Example buffer: 00 31 C5 73 C0 01 80 00 90 00 */
if (buflen < 4)
{
log_error ("warning: historical bytes are too short\n");
return; /* Too short. */
}
if (*buffer)
{
log_error ("warning: bad category indicator in historical bytes\n");
return;
}
/* Skip category indicator. */
buffer++;
buflen--;
/* Get the status indicator. */
apploc->status_indicator = buffer[buflen-3];
buflen -= 3;
/* Parse the compact TLV. */
while (buflen)
{
unsigned int tag = (*buffer & 0xf0) >> 4;
unsigned int len = (*buffer & 0x0f);
if (len+1 > buflen)
{
log_error ("warning: bad Compact-TLV in historical bytes\n");
return; /* Error. */
}
buffer++;
buflen--;
if (tag == 7 && len == 3)
{
/* Card capabilities. */
apploc->cardcap.cmd_chaining = !!(buffer[2] & 0x80);
apploc->cardcap.ext_lc_le = !!(buffer[2] & 0x40);
}
buffer += len;
buflen -= len;
}
}
/*
* Check if the OID in an DER encoding is available by GnuPG/libgcrypt,
* and return the canonical curve name. Return NULL if not available.
* The constant string is not allocated dynamically, never free it.
*/
static const char *
ecc_curve (const unsigned char *buf, size_t buflen)
{
gcry_mpi_t oid;
char *oidstr;
const char *result;
unsigned char *oidbuf;
oidbuf = xtrymalloc (buflen + 1);
if (!oidbuf)
return NULL;
memcpy (oidbuf+1, buf, buflen);
oidbuf[0] = buflen;
oid = gcry_mpi_set_opaque (NULL, oidbuf, (buflen+1) * 8);
if (!oid)
{
xfree (oidbuf);
return NULL;
}
oidstr = openpgp_oid_to_str (oid);
gcry_mpi_release (oid);
if (!oidstr)
return NULL;
result = openpgp_oid_to_curve (oidstr, 1);
xfree (oidstr);
return result;
}
static const char *
get_algorithm_attribute_string (const unsigned char *buffer,
size_t buflen)
{
enum gcry_pk_algos galgo;
const char *curve;
unsigned int nbits = 0;
galgo = map_openpgp_pk_to_gcry (*buffer);
nbits = 0;
curve = NULL;
if (*buffer == PUBKEY_ALGO_RSA && (buflen == 5 || buflen == 6))
nbits = (buffer[1]<<8 | buffer[2]);
else if (*buffer == PUBKEY_ALGO_ECDH || *buffer == PUBKEY_ALGO_ECDSA
|| *buffer == PUBKEY_ALGO_EDDSA)
{
int oidlen = buflen - 1;
if (buffer[buflen-1] == 0x00 || buffer[buflen-1] == 0xff)
{ /* Found "pubkey required"-byte for private key template. */
oidlen--;
}
curve = ecc_curve (buffer + 1, oidlen);
}
else if (opt.verbose)
log_printhex (buffer, buflen, "");
return get_keyalgo_string (galgo, nbits, curve);
}
/* Parse and optionally show the algorithm attributes for KEYNO.
KEYNO must be in the range 0..2. */
static gpg_error_t
parse_algorithm_attribute (app_t app, int keyno)
{
unsigned char *buffer;
size_t buflen;
void *relptr;
const char desc[3][5] = {"sign", "encr", "auth"};
enum gcry_pk_algos galgo;
unsigned int nbits;
const char *curve;
gpg_error_t err = 0;
log_assert (keyno >=0 && keyno <= 2);
app->app_local->keyattr[keyno].key_type = KEY_TYPE_RSA;
app->app_local->keyattr[keyno].rsa.n_bits = 0;
relptr = get_one_do (app, 0xC1+keyno, &buffer, &buflen, NULL);
if (!relptr)
{
log_error ("error reading DO 0x%02X\n", 0xc1+keyno);
return gpg_error (GPG_ERR_CARD);
}
if (buflen < 1)
{
log_error ("error reading DO 0x%02X\n", 0xc1+keyno);
xfree (relptr);
return gpg_error (GPG_ERR_CARD);
}
if (opt.verbose)
log_info ("Key-Attr-%s ..: ", desc[keyno]);
galgo = map_openpgp_pk_to_gcry (*buffer);
nbits = 0;
curve = NULL;
if (*buffer == PUBKEY_ALGO_RSA && (buflen == 5 || buflen == 6))
{
app->app_local->keyattr[keyno].rsa.n_bits = (buffer[1]<<8 | buffer[2]);
app->app_local->keyattr[keyno].rsa.e_bits = (buffer[3]<<8 | buffer[4]);
app->app_local->keyattr[keyno].rsa.format = 0;
if (buflen < 6)
app->app_local->keyattr[keyno].rsa.format = RSA_STD;
else
app->app_local->keyattr[keyno].rsa.format = (buffer[5] == 0? RSA_STD :
buffer[5] == 1? RSA_STD_N :
buffer[5] == 2? RSA_CRT :
buffer[5] == 3? RSA_CRT_N :
RSA_UNKNOWN_FMT);
nbits = app->app_local->keyattr[keyno].rsa.n_bits;
if (opt.verbose)
log_printf
("RSA, n=%u, e=%u, fmt=%s\n",
app->app_local->keyattr[keyno].rsa.n_bits,
app->app_local->keyattr[keyno].rsa.e_bits,
app->app_local->keyattr[keyno].rsa.format == RSA_STD? "std" :
app->app_local->keyattr[keyno].rsa.format == RSA_STD_N?"std+n":
app->app_local->keyattr[keyno].rsa.format == RSA_CRT? "crt" :
app->app_local->keyattr[keyno].rsa.format == RSA_CRT_N?"crt+n":"?");
}
else if (*buffer == PUBKEY_ALGO_ECDH || *buffer == PUBKEY_ALGO_ECDSA
|| *buffer == PUBKEY_ALGO_EDDSA)
{
int oidlen = buflen - 1;
app->app_local->keyattr[keyno].ecc.algo = *buffer;
app->app_local->keyattr[keyno].ecc.flags = 0;
if (APP_CARD(app)->cardtype == CARDTYPE_YUBIKEY)
{
/* Yubikey implementations vary.
* Firmware version 5.2 returns "pubkey required"-byte with
* 0x00, but after removal and second time insertion, it
* returns bogus value there.
* Firmware version 5.4 returns none.
*/
curve = ecc_curve (buffer + 1, oidlen);
if (!curve)
curve = ecc_curve (buffer + 1, oidlen - 1);
}
else
{
if (buffer[buflen-1] == 0x00 || buffer[buflen-1] == 0xff)
{ /* Found "pubkey required"-byte for private key template. */
oidlen--;
if (buffer[buflen-1] == 0xff)
app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_PUBKEY;
}
curve = ecc_curve (buffer + 1, oidlen);
}
if (!curve)
{
log_printhex (buffer+1, buflen-1, "Curve with OID not supported: ");
err = gpg_error (GPG_ERR_CARD);
}
else
{
app->app_local->keyattr[keyno].key_type = KEY_TYPE_ECC;
app->app_local->keyattr[keyno].ecc.curve = curve;
if ((*buffer == PUBKEY_ALGO_EDDSA
&& !strcmp (app->app_local->keyattr[keyno].ecc.curve,
"Ed25519"))
|| (*buffer == PUBKEY_ALGO_ECDH
&& !strcmp (app->app_local->keyattr[keyno].ecc.curve,
"Curve25519")))
app->app_local->keyattr[keyno].ecc.flags |= ECC_FLAG_DJB_TWEAK;
if (opt.verbose)
log_printf
("ECC, curve=%s%s\n", app->app_local->keyattr[keyno].ecc.curve,
!(app->app_local->keyattr[keyno].ecc.flags & ECC_FLAG_DJB_TWEAK)?
"": keyno==1? " (djb-tweak)": " (eddsa)");
}
}
else if (opt.verbose)
log_printhex (buffer, buflen, "");
app->app_local->keyattr[keyno].keyalgo
= get_keyalgo_string (galgo, nbits, curve);
if (opt.verbose)
log_info ("Key-Algo-%s ..: %s\n",
desc[keyno], app->app_local->keyattr[keyno].keyalgo);
xfree (relptr);
return err;
}
/* Prepare a reselect of another application. This is used by cards
* which support on-the-fly switching between applications. The
* function is called to give us a chance to save state for a future
* reselect of us again. */
static gpg_error_t
do_prep_reselect (app_t app, ctrl_t ctrl)
{
gpg_error_t err;
(void)app;
(void)ctrl;
err = 0;
return err;
}
/* Reselect the application. This is used by cards which support
* on-the-fly switching between applications. */
static gpg_error_t
do_reselect (app_t app, ctrl_t ctrl)
{
gpg_error_t err;
(void)ctrl;
/* An extra check which should not be necessary because the caller
* should have made sure that a re-select is only called for
* appropriate cards. */
if (APP_CARD(app)->cardtype != CARDTYPE_YUBIKEY)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* Note that the card can't cope with P2=0xCO, thus we need to pass
* a special flag value. */
err = iso7816_select_application (app_get_slot (app),
openpgp_aid, sizeof openpgp_aid, 0x0001);
if (!err)
{
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
}
return err;
}
/* Check if AID is the correct one. */
static gpg_error_t
do_check_aid (app_t app, ctrl_t ctrl, const unsigned char *aid, size_t aidlen)
{
(void)app;
(void)ctrl;
if (aidlen >= sizeof openpgp_aid
&& memcmp (aid, openpgp_aid, sizeof openpgp_aid) == 0)
return 0;
return gpg_error (GPG_ERR_WRONG_CARD);
}
/* Select the OpenPGP application on the card in SLOT. This function
must be used before any other OpenPGP application functions. */
gpg_error_t
app_select_openpgp (app_t app)
{
int slot = app_get_slot (app);
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
void *relptr;
/* Note that the card can't cope with P2=0xCO, thus we need to pass a
special flag value. */
err = iso7816_select_application (slot,
openpgp_aid, sizeof openpgp_aid, 0x0001);
if (!err)
{
unsigned int manufacturer;
app->apptype = APPTYPE_OPENPGP;
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
app->app_local = NULL;
/* The OpenPGP card returns the serial number as part of the
AID; because we prefer to use OpenPGP serial numbers, we
replace a possibly already set one from a EF.GDO with this
one. Note, that for current OpenPGP cards, no EF.GDO exists
and thus it won't matter at all. */
err = iso7816_get_data (slot, 0, 0x004F, &buffer, &buflen);
if (err)
goto leave;
if (opt.verbose)
{
log_info ("AID: ");
log_printhex (buffer, buflen, "");
}
app->appversion = buffer[6] << 8;
app->appversion |= buffer[7];
manufacturer = (buffer[8]<<8 | buffer[9]);
/* For Yubikey, serialno is set in app.c, already. The problem
* is that the OpenPGP appversion has been set to 0.0 because we
* are not able to deduce this if the OpenPGP app has not been
* enabled. Thus we here to to use the appversion from DO 0x4f
* but return a serialno with a version 0.0 as set by app.c.
* Users of scdaemon taking the version from the serialno won't
* work anymore and need to be modified. Recall that our
* architecture requires exactly one serilano per card.
*/
if (APP_CARD(app)->cardtype == CARDTYPE_YUBIKEY)
xfree (buffer);
else
{
xfree (APP_CARD(app)->serialno);
APP_CARD(app)->serialno = buffer;
APP_CARD(app)->serialnolen = buflen;
}
buffer = NULL;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* We want to temporary cache the DO 6E. */
app->app_local->override.cache_6e = 1;
app->app_local->manufacturer = manufacturer;
if (app->appversion >= 0x0200)
app->app_local->extcap.is_v2 = 1;
if (app->appversion >= 0x0300)
app->app_local->extcap.is_v3 = 1;
/* Read the historical bytes. */
relptr = get_one_do (app, 0x5f52, &buffer, &buflen, NULL);
if (relptr)
{
if (opt.verbose)
{
log_info ("Historical Bytes: ");
log_printhex (buffer, buflen, "");
}
parse_historical (app->app_local, buffer, buflen);
xfree (relptr);
}
/* Read the force-chv1 flag. */
relptr = get_one_do (app, 0x00C4, &buffer, &buflen, NULL);
if (!relptr)
{
log_error (_("can't access %s - invalid OpenPGP card?\n"),
"CHV Status Bytes");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
app->force_chv1 = (buflen && *buffer == 0);
xfree (relptr);
/* Read the extended capabilities. */
relptr = get_one_do (app, 0x00C0, &buffer, &buflen, NULL);
if (!relptr)
{
log_error (_("can't access %s - invalid OpenPGP card?\n"),
"Extended Capability Flags" );
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (buflen)
{
app->app_local->extcap.sm_supported = !!(*buffer & 0x80);
app->app_local->extcap.get_challenge = !!(*buffer & 0x40);
app->app_local->extcap.key_import = !!(*buffer & 0x20);
app->app_local->extcap.change_force_chv = !!(*buffer & 0x10);
app->app_local->extcap.private_dos = !!(*buffer & 0x08);
app->app_local->extcap.algo_attr_change = !!(*buffer & 0x04);
app->app_local->extcap.has_decrypt = !!(*buffer & 0x02);
app->app_local->extcap.kdf_do = !!(*buffer & 0x01);
}
if (buflen >= 10)
{
/* Available with cards of v2 or later. */
app->app_local->extcap.sm_algo = buffer[1];
app->app_local->extcap.max_get_challenge
= (buffer[2] << 8 | buffer[3]);
app->app_local->extcap.max_certlen = (buffer[4] << 8 | buffer[5]);
/* Interpretation is different between v2 and v3, unfortunately. */
if (app->app_local->extcap.is_v3)
{
app->app_local->extcap.max_special_do
= (buffer[6] << 8 | buffer[7]);
app->app_local->extcap.pin_blk2 = !!(buffer[8] & 0x01);
app->app_local->extcap.mse= !!(buffer[9] & 0x01);
}
}
xfree (relptr);
/* Some of the first cards accidentally don't set the
CHANGE_FORCE_CHV bit but allow it anyway. */
if (app->appversion <= 0x0100 && manufacturer == 1)
app->app_local->extcap.change_force_chv = 1;
/* Check optional DO of "General Feature Management" for button. */
relptr = get_one_do (app, 0x7f74, &buffer, &buflen, NULL);
if (relptr)
{
/* It must be: 03 81 01 20 */
app->app_local->extcap.has_button = 1;
}
parse_login_data (app);
if (opt.verbose)
show_caps (app->app_local);
err = parse_algorithm_attribute (app, 0);
if (!err)
err = parse_algorithm_attribute (app, 1);
if (!err)
err = parse_algorithm_attribute (app, 2);
if (err)
goto leave;
if (opt.verbose > 1)
dump_all_do (slot);
app->app_local->override.cache_6e = 0;
flush_cache_item (app, 0x6E);
app->fnc.deinit = do_deinit;
app->fnc.prep_reselect = do_prep_reselect;
app->fnc.reselect = do_reselect;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
app->fnc.writecert = do_writecert;
app->fnc.writekey = do_writekey;
app->fnc.genkey = do_genkey;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_pin;
app->fnc.check_pin = do_check_pin;
app->fnc.with_keygrip = do_with_keygrip;
app->fnc.check_aid = do_check_aid;
}
leave:
if (err)
do_deinit (app);
return err;
}
diff --git a/scd/app-p15.c b/scd/app-p15.c
index 6af10b46b..377cd5024 100644
--- a/scd/app-p15.c
+++ b/scd/app-p15.c
@@ -1,6607 +1,6607 @@
/* app-p15.c - The pkcs#15 card application.
* Copyright (C) 2005 Free Software Foundation, Inc.
* Copyright (C) 2020, 2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/* Information pertaining to the BELPIC developer card samples:
Unblock PUK: "222222111111"
Reset PIN: "333333111111")
e.g. the APDUs 00:20:00:02:08:2C:33:33:33:11:11:11:FF
and 00:24:01:01:08:24:12:34:FF:FF:FF:FF:FF
should change the PIN into 1234.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "scdaemon.h"
#include "iso7816.h"
#include "../common/i18n.h"
#include "../common/tlv.h"
#include "../common/host2net.h"
#include "../common/openpgpdefs.h"
#include "apdu.h" /* fixme: we should move the card detection to a
separate file */
static const char oid_kp_codeSigning[] = "1.3.6.1.5.5.7.3.3";
static const char oid_kp_timeStamping[] = "1.3.6.1.5.5.7.3.8";
static const char oid_kp_ocspSigning[] = "1.3.6.1.5.5.7.3.9";
static const char oid_kp_ms_documentSigning[] = "1.3.6.1.4.1.311.10.3.12";
static const char oid_kp_ms_old_documentSigning[] = "1.3.6.1.4.1.311.3.10.3.12";
static const char oid_kp_emailProtection[]= "1.3.6.1.5.5.7.3.4";
static const char oid_kp_serverAuth[] = "1.3.6.1.5.5.7.3.1";
static const char oid_kp_clientAuth[] = "1.3.6.1.5.5.7.3.2";
static const char oid_kp_ms_smartcardLogon[] = "1.3.6.1.4.1.311.20.2.2";
static const char oid_kp_anyExtendedKeyUsage[] = "2.5.29.37.0";
static const char oid_kp_gpgUsageCert[] = "1.3.6.1.4.1.11591.2.6.1";
static const char oid_kp_gpgUsageSign[] = "1.3.6.1.4.1.11591.2.6.2";
static const char oid_kp_gpgUsageEncr[] = "1.3.6.1.4.1.11591.2.6.3";
static const char oid_kp_gpgUsageAuth[] = "1.3.6.1.4.1.11591.2.6.4";
/* Types of cards we know and which needs special treatment. */
typedef enum
{
CARD_TYPE_UNKNOWN,
CARD_TYPE_TCOS,
CARD_TYPE_MICARDO,
CARD_TYPE_CARDOS_50,
CARD_TYPE_CARDOS_53,
CARD_TYPE_CARDOS_54,
CARD_TYPE_AET, /* A.E.T. Europe JCOP card. */
CARD_TYPE_BELPIC, /* Belgian eID card specs. */
CARD_TYPE_STARCOS_32
}
card_type_t;
/* The OS of card as specified by card_type_t is not always
* sufficient. Thus we also distinguish the actual product build upon
* the given OS. */
typedef enum
{
CARD_PRODUCT_UNKNOWN,
CARD_PRODUCT_RSCS, /* Rohde&Schwarz Cybersecurity */
CARD_PRODUCT_DTRUST3, /* D-Trust GmbH (bundesdruckerei.de) */
CARD_PRODUCT_DTRUST4,
CARD_PRODUCT_GENUA, /* GeNUA mbH */
CARD_PRODUCT_NEXUS, /* Technology Nexus */
CARD_PRODUCT_CVISION /* Cryptovision GmbH */
}
card_product_t;
/* A list card types with ATRs noticed with these cards. */
#define X(a) ((unsigned char const *)(a))
static struct
{
size_t atrlen;
unsigned char const *atr;
card_type_t type;
} card_atr_list[] = {
{ 19, X("\x3B\xBA\x13\x00\x81\x31\x86\x5D\x00\x64\x05\x0A\x02\x01\x31\x80"
"\x90\x00\x8B"),
CARD_TYPE_TCOS }, /* SLE44 */
{ 19, X("\x3B\xBA\x14\x00\x81\x31\x86\x5D\x00\x64\x05\x14\x02\x02\x31\x80"
"\x90\x00\x91"),
CARD_TYPE_TCOS }, /* SLE66S */
{ 19, X("\x3B\xBA\x96\x00\x81\x31\x86\x5D\x00\x64\x05\x60\x02\x03\x31\x80"
"\x90\x00\x66"),
CARD_TYPE_TCOS }, /* SLE66P */
{ 27, X("\x3B\xFF\x94\x00\xFF\x80\xB1\xFE\x45\x1F\x03\x00\x68\xD2\x76\x00"
"\x00\x28\xFF\x05\x1E\x31\x80\x00\x90\x00\x23"),
CARD_TYPE_MICARDO }, /* German BMI card */
{ 19, X("\x3B\x6F\x00\xFF\x00\x68\xD2\x76\x00\x00\x28\xFF\x05\x1E\x31\x80"
"\x00\x90\x00"),
CARD_TYPE_MICARDO }, /* German BMI card (ATR due to reader problem) */
{ 26, X("\x3B\xFE\x94\x00\xFF\x80\xB1\xFA\x45\x1F\x03\x45\x73\x74\x45\x49"
"\x44\x20\x76\x65\x72\x20\x31\x2E\x30\x43"),
CARD_TYPE_MICARDO }, /* EstEID (Estonian Big Brother card) */
{ 11, X("\x3b\xd2\x18\x00\x81\x31\xfe\x58\xc9\x01\x14"),
CARD_TYPE_CARDOS_50 }, /* CardOS 5.0 */
{ 11, X("\x3b\xd2\x18\x00\x81\x31\xfe\x58\xc9\x03\x16"),
CARD_TYPE_CARDOS_53 }, /* CardOS 5.3 */
{ 11, X("\x3b\xd2\x18\x00\x81\x31\xfe\x58\xc9\x04\x11"),
CARD_TYPE_CARDOS_54 }, /* CardOS 5.4 */
{ 24, X("\x3b\xfe\x18\x00\x00\x80\x31\xfe\x45\x53\x43\x45"
"\x36\x30\x2d\x43\x44\x30\x38\x31\x2d\x6e\x46\xa9"),
CARD_TYPE_AET },
{ 25, X("\x3b\x9f\x96\x81\xb1\xfe\x45\x1f\x07\x00\x64\x05"
"\x1e\xb2\x00\x31\xb0\x73\x96\x21\xdb\x05\x90\x00\x5c"),
CARD_TYPE_STARCOS_32 },
{ 0 }
};
#undef X
/* Macro to test for CardOS 5.0, 5.3 and 5.4. */
#define IS_CARDOS_5(a) ((a)->app_local->card_type == CARD_TYPE_CARDOS_50 \
|| (a)->app_local->card_type == CARD_TYPE_CARDOS_53 \
|| (a)->app_local->card_type == CARD_TYPE_CARDOS_54)
#define IS_STARCOS_3(a) ((a)->app_local->card_type == CARD_TYPE_STARCOS_32)
/* The default PKCS-15 home DF */
#define DEFAULT_HOME_DF 0x5015
/* The AID of PKCS15. */
static char const pkcs15_aid[] = { 0xA0, 0, 0, 0, 0x63,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The Belgian eID variant - they didn't understood why a shared AID
is useful for a standard. Oh well. */
static char const pkcs15be_aid[] = { 0xA0, 0, 0, 0x01, 0x77,
0x50, 0x4B, 0x43, 0x53, 0x2D, 0x31, 0x35 };
/* The D-TRUST Card 4.x variant - dito */
static char const pkcs15dtrust4_aid[] = { 0xE8, 0x28, 0xBD, 0x08, 0x0F, 0xA0,
0x00, 0x00, 0x01, 0x67, 0x45, 0x53,
0x49, 0x47, 0x4E };
/* The PIN types as defined in pkcs#15 v1.1 */
typedef enum
{
PIN_TYPE_BCD = 0,
PIN_TYPE_ASCII_NUMERIC = 1,
PIN_TYPE_UTF8 = 2,
PIN_TYPE_HALF_NIBBLE_BCD = 3,
PIN_TYPE_ISO9564_1 = 4
} pin_type_t;
/* The AuthenticationTypes as defined in pkcs#15 v1.1 (6.8.1) */
typedef enum
{
AUTH_TYPE_PIN = -1,
AUTH_TYPE_BIOMETRIC = 0,
AUTH_TYPE_AUTHKEY = 1,
AUTH_TYPE_EXTERNAL = 2,
} auth_type_t;
/* A bit array with for the key usage flags from the
commonKeyAttributes. */
struct keyusage_flags_s
{
unsigned int encrypt: 1;
unsigned int decrypt: 1;
unsigned int sign: 1;
unsigned int sign_recover: 1;
unsigned int wrap: 1;
unsigned int unwrap: 1;
unsigned int verify: 1;
unsigned int verify_recover: 1;
unsigned int derive: 1;
unsigned int non_repudiation: 1;
};
typedef struct keyusage_flags_s keyusage_flags_t;
/* A bit array with for the key access flags from the
commonKeyAttributes. */
struct keyaccess_flags_s
{
unsigned int any:1; /* Any access flag set. */
unsigned int sensitive:1;
unsigned int extractable:1;
unsigned int always_sensitive:1;
unsigned int never_extractable:1;
unsigned int local:1;
};
typedef struct keyaccess_flags_s keyaccess_flags_t;
/* A bit array with for the gpg usage flags. */
struct gpgusage_flags_s
{
unsigned int any:1; /* Any of the next flags are set. */
unsigned int cert:1; /* 1.3.6.1.4.1.11591.2.6.1 */
unsigned int sign:1; /* 1.3.6.1.4.1.11591.2.6.2 */
unsigned int encr:1; /* 1.3.6.1.4.1.11591.2.6.3 */
unsigned int auth:1; /* 1.3.6.1.4.1.11591.2.6.4 */
};
typedef struct gpgusage_flags_s gpgusage_flags_t;
/* This is an object to store information about a Certificate
Directory File (CDF) in a format suitable for further processing by
us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire CDF. */
struct cdf_object_s
{
/* Link to next item when used in a linked list. */
struct cdf_object_s *next;
/* Flags to indicate whether fields are valid. */
unsigned int have_off:1;
/* Length and allocated buffer with the Id of this object.
* This field is used for X.509 in PKCS#11 to make it easier to
* match a private key with a certificate. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* NULL or the malloced label of this object. */
char *label;
/* To avoid reading and parsing a certificate more than once, we
* cache the ksba object. */
ksba_cert_t cert;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the CDF and the path itself.
path[0] is the top DF (usually 0x3f00). The path will never be
empty. */
size_t pathlen;
unsigned short path[1];
};
typedef struct cdf_object_s *cdf_object_t;
/* This is an object to store information about a Private Key
Directory File (PrKDF) in a format suitable for further processing
by us. To keep memory management, simple we use a linked list of
items; i.e. one such object represents one certificate and the list
the entire PrKDF. */
struct prkdf_object_s
{
/* Link to next item when used in a linked list. */
struct prkdf_object_s *next;
/* Flags to indicate whether fields are valid. */
unsigned int keygrip_valid:1;
unsigned int key_reference_valid:1;
unsigned int have_off:1;
unsigned int have_keytime:1;
/* Flag indicating that the corresponding PIN has already been
* verified. Note that for cards which are able to return the
* verification stus, this flag is not used. */
unsigned int pin_verified:1;
/* PKCS#15 info whether this is an EC key. Default is RSA. Note
* that there is also a KEYALGO field which is derived from the
- * publick key via Libgcrypt. */
+ * public key via Libgcrypt. */
unsigned int is_ecc:1;
/* The key's usage flags. */
keyusage_flags_t usageflags;
/* The key's access flags. */
keyaccess_flags_t accessflags;
/* Extended key usage flags. Only used if .valid is set. This
* information is computed from an associated certificate. */
struct {
unsigned int valid:1;
unsigned int sign:1;
unsigned int encr:1;
unsigned int auth:1;
} extusage;
/* OpenPGP key features for this key. This is taken from special
* extended key usage flags different from those tracked in EXTUSAGE
* above. There is also no valid flag as in EXTUSAGE. */
gpgusage_flags_t gpgusage;
/* The keygrip of the key. This is used as a cache. */
char keygrip[2*KEYGRIP_LEN+1];
/* A malloced algorithm string or NULL if not known. */
char *keyalgostr;
/* The Gcrypt algo identifier for the key. It is valid if the
* keygrip is also valid. See also is_ecc above. */
int keyalgo;
/* The length of the key in bits (e.g. for RSA the length of the
* modulus). It is valid if the keygrip is also valid. */
unsigned int keynbits;
/* The creation time of the key or 0 if not known. */
u32 keytime;
/* Malloced CN from the Subject-DN of the corresponding certificate
* or NULL if not known. */
char *common_name;
/* Malloced SerialNumber from the Subject-DN of the corresponding
* certificate or NULL if not known. */
char *serial_number;
/* KDF/KEK parameter for OpenPGP's ECDH. First byte is zero if not
* available. */
unsigned char ecdh_kdf[4];
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* NULL or the malloced label of this object. */
char *label;
/* The keyReference and a flag telling whether it is valid. */
unsigned long key_reference;
/* The offset and length of the object. They are only valid if
* HAVE_OFF is true otherwise they are set to 0. */
unsigned long off, len;
/* The length of the path as given in the PrKDF and the path itself.
path[0] is the top DF (usually 0x3f00). */
size_t pathlen;
unsigned short path[1];
};
typedef struct prkdf_object_s *prkdf_object_t;
typedef struct prkdf_object_s *pukdf_object_t;
/* This is an object to store information about a Authentication
Object Directory File (AODF) in a format suitable for further
processing by us. To keep memory management, simple we use a linked
list of items; i.e. one such object represents one authentication
object and the list the entire AOKDF. */
struct aodf_object_s
{
/* Link to next item when used in a linked list. */
struct aodf_object_s *next;
/* Flags to indicate whether fields are valid. */
unsigned int have_off:1;
/* Length and allocated buffer with the Id of this object. */
size_t objidlen;
unsigned char *objid;
/* Length and allocated buffer with the authId of this object or
NULL if no authID is known. */
size_t authidlen;
unsigned char *authid;
/* NULL or the malloced label of this object. */
char *label;
/* The file ID of this AODF. */
unsigned short fid;
/* The type of this authentication object. */
auth_type_t auth_type;
/* Info used for AUTH_TYPE_PIN: */
/* The PIN Flags. */
struct
{
unsigned int case_sensitive: 1;
unsigned int local: 1;
unsigned int change_disabled: 1;
unsigned int unblock_disabled: 1;
unsigned int initialized: 1;
unsigned int needs_padding: 1;
unsigned int unblocking_pin: 1;
unsigned int so_pin: 1;
unsigned int disable_allowed: 1;
unsigned int integrity_protected: 1;
unsigned int confidentiality_protected: 1;
unsigned int exchange_ref_data: 1;
} pinflags;
/* The PIN Type. */
pin_type_t pintype;
/* The minimum length of a PIN. */
unsigned long min_length;
/* The stored length of a PIN. */
unsigned long stored_length;
/* The maximum length of a PIN and a flag telling whether it is valid. */
unsigned long max_length;
int max_length_valid;
/* The pinReference and a flag telling whether it is valid. */
unsigned long pin_reference;
int pin_reference_valid;
/* The padChar and a flag telling whether it is valid. */
char pad_char;
int pad_char_valid;
/* The offset and length of the object. They are only valid if
HAVE_OFF is true and set to 0 if HAVE_OFF is false. */
unsigned long off, len;
/* The length of the path as given in the Aodf and the path itself.
path[0] is the top DF (usually 0x3f00). PATH is optional and thus
may be NULL. Malloced.*/
size_t pathlen;
unsigned short *path;
/* Info used for AUTH_TYPE_AUTHKEY: */
};
typedef struct aodf_object_s *aodf_object_t;
/* Context local to this application. */
struct app_local_s
{
/* The home DF. Note, that we don't yet support a multilevel
hierarchy. Thus we assume this is directly below the MF. */
unsigned short home_df;
/* The type of the card's OS. */
card_type_t card_type;
/* The vendor's product. */
card_product_t card_product;
/* Flag indicating that extended_mode is not supported. */
unsigned int no_extended_mode : 1;
/* Flag indicating whether we may use direct path selection. */
unsigned int direct_path_selection : 1;
/* Flag indicating whether the card has any key with a gpgusage set. */
unsigned int any_gpgusage : 1;
/* Structure with the EFIDs of the objects described in the ODF
file. */
struct
{
unsigned short private_keys;
unsigned short public_keys;
unsigned short trusted_public_keys;
unsigned short secret_keys;
unsigned short certificates;
unsigned short trusted_certificates;
unsigned short useful_certificates;
unsigned short data_objects;
unsigned short auth_objects;
} odf;
/* The PKCS#15 serialnumber from EF(TokenInfo) or NULL. Malloced. */
unsigned char *serialno;
size_t serialnolen;
/* The manufacturerID from the TokenInfo EF. Malloced or NULL. */
char *manufacturer_id;
/* The label from the TokenInfo EF. Malloced or NULL. */
char *token_label;
/* The tokenflags from the TokenInfo EF. Malloced or NULL. */
unsigned char *tokenflags;
unsigned int tokenflagslen;
/* Information on all certificates. */
cdf_object_t certificate_info;
/* Information on all trusted certificates. */
cdf_object_t trusted_certificate_info;
/* Information on all useful certificates. */
cdf_object_t useful_certificate_info;
/* Counter to make object ids of certificates unique. */
unsigned int cdf_dup_counter;
/* Information on all public keys. */
prkdf_object_t public_key_info;
/* Information on all private keys. */
pukdf_object_t private_key_info;
/* Information on all authentication objects. */
aodf_object_t auth_object_info;
};
/*** Local prototypes. ***/
static gpg_error_t select_ef_by_path (app_t app, const unsigned short *path,
size_t pathlen);
static gpg_error_t keygrip_from_prkdf (app_t app, prkdf_object_t prkdf);
static gpg_error_t readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen);
static char *get_dispserialno (app_t app, prkdf_object_t prkdf);
static gpg_error_t do_getattr (app_t app, ctrl_t ctrl, const char *name);
static const char *
cardtype2str (card_type_t cardtype)
{
switch (cardtype)
{
case CARD_TYPE_UNKNOWN: return "";
case CARD_TYPE_TCOS: return "TCOS";
case CARD_TYPE_MICARDO: return "Micardo";
case CARD_TYPE_CARDOS_50: return "CardOS 5.0";
case CARD_TYPE_CARDOS_53: return "CardOS 5.3";
case CARD_TYPE_CARDOS_54: return "CardOS 5.4";
case CARD_TYPE_BELPIC: return "Belgian eID";
case CARD_TYPE_AET: return "AET";
case CARD_TYPE_STARCOS_32:return "STARCOS 3.2";
}
return "";
}
static const char *
cardproduct2str (card_product_t cardproduct)
{
switch (cardproduct)
{
case CARD_PRODUCT_UNKNOWN: return "";
case CARD_PRODUCT_RSCS: return "R&S";
case CARD_PRODUCT_DTRUST3: return "D-Trust 3";
case CARD_PRODUCT_DTRUST4: return "D-Trust 4.1/4.4";
case CARD_PRODUCT_GENUA: return "GeNUA";
case CARD_PRODUCT_NEXUS: return "Nexus";
case CARD_PRODUCT_CVISION: return "Cryptovison";
}
return "";
}
/* Release the CDF object A */
static void
release_cdflist (cdf_object_t a)
{
while (a)
{
cdf_object_t tmp = a->next;
ksba_free (a->cert);
xfree (a->objid);
xfree (a->authid);
xfree (a->label);
xfree (a);
a = tmp;
}
}
/* Release the PrKDF object A. */
static void
release_prkdflist (prkdf_object_t a)
{
while (a)
{
prkdf_object_t tmp = a->next;
xfree (a->keyalgostr);
xfree (a->common_name);
xfree (a->serial_number);
xfree (a->objid);
xfree (a->authid);
xfree (a->label);
xfree (a);
a = tmp;
}
}
static void
release_pukdflist (pukdf_object_t a)
{
release_prkdflist (a);
}
/* Release just one aodf object. */
void
release_aodf_object (aodf_object_t a)
{
if (a)
{
xfree (a->objid);
xfree (a->authid);
xfree (a->label);
xfree (a->path);
xfree (a);
}
}
/* Release the AODF list A. */
static void
release_aodflist (aodf_object_t a)
{
while (a)
{
aodf_object_t tmp = a->next;
release_aodf_object (a);
a = tmp;
}
}
static void
release_lists (app_t app)
{
release_cdflist (app->app_local->certificate_info);
app->app_local->certificate_info = NULL;
release_cdflist (app->app_local->trusted_certificate_info);
app->app_local->trusted_certificate_info = NULL;
release_cdflist (app->app_local->useful_certificate_info);
app->app_local->useful_certificate_info = NULL;
release_pukdflist (app->app_local->public_key_info);
app->app_local->public_key_info = NULL;
release_prkdflist (app->app_local->private_key_info);
app->app_local->private_key_info = NULL;
release_aodflist (app->app_local->auth_object_info);
app->app_local->auth_object_info = NULL;
}
static void
release_tokeninfo (app_t app)
{
xfree (app->app_local->manufacturer_id);
app->app_local->manufacturer_id = NULL;
xfree (app->app_local->token_label);
app->app_local->token_label = NULL;
xfree (app->app_local->tokenflags);
app->app_local->tokenflags = NULL;
xfree (app->app_local->serialno);
app->app_local->serialno = NULL;
}
/* Release all local resources. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
release_lists (app);
release_tokeninfo (app);
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Do a select and a read for the file with EFID. EFID_DESC is a
desctription of the EF to be used with error messages. On success
BUFFER and BUFLEN contain the entire content of the EF. The caller
must free BUFFER only on success. If EFID is 0 no seelct is done. */
static gpg_error_t
select_and_read_binary (app_t app, unsigned short efid, const char *efid_desc,
unsigned char **buffer, size_t *buflen)
{
gpg_error_t err;
int sw;
if (efid)
{
err = select_ef_by_path (app, &efid, 1);
if (err)
{
log_error ("p15: error selecting %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
return err;
}
}
err = iso7816_read_binary_ext (app_get_slot (app),
0, 0, 0, buffer, buflen, &sw);
if (err)
log_error ("p15: error reading %s (0x%04X): %s (sw=%04X)\n",
efid_desc, efid, gpg_strerror (err), sw);
return err;
}
/* If EFID is not 0 do a select and then read the record RECNO.
* EFID_DESC is a description of the EF to be used with error
* messages. On success BUFFER and BUFLEN contain the entire content
* of the EF. The caller must free BUFFER only on success. */
static gpg_error_t
select_and_read_record (app_t app, unsigned short efid, int recno,
const char *efid_desc,
unsigned char **buffer, size_t *buflen, int *r_sw)
{
gpg_error_t err;
int sw = 0;
if (r_sw)
*r_sw = 0x9000;
if (efid)
{
err = select_ef_by_path (app, &efid, 1);
if (err)
{
log_error ("p15: error selecting %s (0x%04X): %s\n",
efid_desc, efid, gpg_strerror (err));
if (r_sw)
*r_sw = sw;
return err;
}
}
err = iso7816_read_record_ext (app_get_slot (app),
recno, 1, 0, buffer, buflen, &sw);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err && sw == SW_FILE_STRUCT)
;
else
log_error ("p15: error reading %s (0x%04X) record %d: %s (sw=%04X)\n",
efid_desc, efid, recno, gpg_strerror (err), sw);
if (r_sw)
*r_sw = sw;
return err;
}
/* On CardOS with a Linear TLV file structure the records starts
* with some tag (often the record number) followed by the length
* byte for this record. Detect and remove this prefix. */
if (*buflen == 2 && !(*buffer)[0] && !(*buffer)[1])
; /* deleted record. */
else if (*buflen > 3 && (*buffer)[0] == 0xff
&& buf16_to_uint ((*buffer)+1) == *buflen - 3)
{
memmove (*buffer, *buffer + 3, *buflen - 3);
*buflen = *buflen - 3;
}
else if (*buflen > 2 && (*buffer)[0] != 0x30 && (*buffer)[1] == *buflen - 2)
{
memmove (*buffer, *buffer + 2, *buflen - 2);
*buflen = *buflen - 2;
}
return 0;
}
/* This function calls select file to read a file using a complete
* path which may or may not start at the master file (MF). If
* EXPECT_DF is set a directory or file is expected - otherwise an
* elementary file expected. */
static gpg_error_t
select_by_path (app_t app, const unsigned short *path, size_t pathlen,
int expect_df)
{
gpg_error_t err;
int i, j;
int home_df_used = 0;
if (!pathlen)
return gpg_error (GPG_ERR_INV_VALUE);
if (opt.debug)
{
log_debug ("%s: path=", __func__);
for (j=0; j < pathlen; j++)
log_printf ("%s%04hX", j? "/":"", path[j]);
log_printf ("%s", expect_df?" (DF requested)":"");
log_printf ("%s\n",app->app_local->direct_path_selection?" (direct)":"");
}
if (app->app_local->direct_path_selection)
{
if (pathlen && *path == 0x3f00 )
{
if (pathlen == 1)
err = iso7816_select_mf (app_get_slot (app));
else
err = iso7816_select_path (app_get_slot (app), path+1, pathlen-1,
0);
}
else
{
home_df_used = 1;
err = iso7816_select_path (app_get_slot (app), path, pathlen,
app->app_local->home_df);
}
if (err)
{
log_error ("p15: error selecting path ");
goto err_print_path;
}
}
else
{
if (pathlen && *path != 0x3f00 )
log_error ("p15: warning: relative path select not yet implemented\n");
/* FIXME: Use home_df. */
for (i=0; i < pathlen; i++)
{
err = iso7816_select_file (app_get_slot (app),
path[i], (expect_df || (i+1 < pathlen)));
if (err)
{
log_error ("p15: error selecting part %d from path ", i);
goto err_print_path;
}
}
}
return 0;
err_print_path:
if (pathlen && *path != 0x3f00 )
log_printf ("3F00/");
else if (home_df_used)
log_printf ("%04hX/", app->app_local->home_df);
for (j=0; j < pathlen; j++)
log_printf ("%s%04hX", j? "/":"", path[j]);
log_printf (": %s\n", gpg_strerror (err));
return err;
}
static gpg_error_t
select_ef_by_path (app_t app, const unsigned short *path, size_t pathlen)
{
return select_by_path (app, path, pathlen, 0);
}
#if 0 /* Currently not used. */
static gpg_error_t
select_df_by_path (app_t app, const unsigned short *path, size_t pathlen)
{
return select_by_path (app, path, pathlen, 1);
}
#endif
/* Parse a cert Id string (or a key Id string) and return the binary
object Id string in a newly allocated buffer stored at R_OBJID and
R_OBJIDLEN. On Error NULL will be stored there and an error code
returned. On success caller needs to free the buffer at R_OBJID. */
static gpg_error_t
parse_certid (app_t app, const char *certid,
unsigned char **r_objid, size_t *r_objidlen)
{
char tmpbuf[10];
const char *s;
size_t objidlen;
unsigned char *objid;
int i;
*r_objid = NULL;
*r_objidlen = 0;
if (certid[0] != 'P' && strlen (certid) == 40) /* This is a keygrip. */
{
prkdf_object_t prkdf;
for (prkdf = app->app_local->private_key_info;
prkdf; prkdf = prkdf->next)
if (!keygrip_from_prkdf (app, prkdf)
&& !strcmp (certid, prkdf->keygrip))
break;
if (!prkdf || !prkdf->objidlen || !prkdf->objid)
return gpg_error (GPG_ERR_NOT_FOUND);
objidlen = prkdf->objidlen;
objid = xtrymalloc (objidlen);
if (!objid)
return gpg_error_from_syserror ();
memcpy (objid, prkdf->objid, prkdf->objidlen);
}
else /* This is a usual keyref. */
{
if (app->app_local->home_df != DEFAULT_HOME_DF)
snprintf (tmpbuf, sizeof tmpbuf, "P15-%04X.",
(unsigned int)(app->app_local->home_df & 0xffff));
else
strcpy (tmpbuf, "P15.");
if (strncmp (certid, tmpbuf, strlen (tmpbuf)) )
{
if (!strncmp (certid, "P15.", 4)
|| (!strncmp (certid, "P15-", 4)
&& hexdigitp (certid+4)
&& hexdigitp (certid+5)
&& hexdigitp (certid+6)
&& hexdigitp (certid+7)
&& certid[8] == '.'))
return gpg_error (GPG_ERR_NOT_FOUND);
return gpg_error (GPG_ERR_INV_ID);
}
certid += strlen (tmpbuf);
for (s=certid, objidlen=0; hexdigitp (s); s++, objidlen++)
;
if (*s || !objidlen || (objidlen%2))
return gpg_error (GPG_ERR_INV_ID);
objidlen /= 2;
objid = xtrymalloc (objidlen);
if (!objid)
return gpg_error_from_syserror ();
for (s=certid, i=0; i < objidlen; i++, s+=2)
objid[i] = xtoi_2 (s);
}
*r_objid = objid;
*r_objidlen = objidlen;
return 0;
}
/* Find a certificate object by its object ID and store a pointer to
* it at R_CDF. */
static gpg_error_t
cdf_object_from_objid (app_t app, size_t objidlen, const unsigned char *objid,
cdf_object_t *r_cdf)
{
cdf_object_t cdf;
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
break;
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_cdf = cdf;
return 0;
}
/* Find a certificate object by its label and store a pointer to it at
* R_CDF. */
static gpg_error_t
cdf_object_from_label (app_t app, const char *label, cdf_object_t *r_cdf)
{
cdf_object_t cdf;
if (!label)
return gpg_error (GPG_ERR_NOT_FOUND);
for (cdf = app->app_local->certificate_info; cdf; cdf = cdf->next)
if (cdf->label && !strcmp (cdf->label, label))
break;
if (!cdf)
for (cdf = app->app_local->trusted_certificate_info; cdf; cdf = cdf->next)
if (cdf->label && !strcmp (cdf->label, label))
break;
if (!cdf)
for (cdf = app->app_local->useful_certificate_info; cdf; cdf = cdf->next)
if (cdf->label && !strcmp (cdf->label, label))
break;
if (!cdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_cdf = cdf;
return 0;
}
/* Find a certificate object by the certificate ID CERTID and store a
* pointer to it at R_CDF. */
static gpg_error_t
cdf_object_from_certid (app_t app, const char *certid, cdf_object_t *r_cdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
cdf_object_t cdf;
prkdf_object_t prkdf;
err = parse_certid (app, certid, &objid, &objidlen);
if (err)
return err;
err = cdf_object_from_objid (app, objidlen, objid, &cdf);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* Try again by finding the certid in the prkdf and matching by
* label. */
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
if (prkdf->objidlen == objidlen
&& !memcmp (prkdf->objid, objid, objidlen))
break;
if (prkdf)
err = cdf_object_from_label (app, prkdf->label, &cdf);
}
xfree (objid);
if (err)
return err;
*r_cdf = cdf;
return 0;
}
/* Find a private key object by the key Id string KEYIDSTR and store a
pointer to it at R_PRKDF. */
static gpg_error_t
prkdf_object_from_keyidstr (app_t app, const char *keyidstr,
prkdf_object_t *r_prkdf)
{
gpg_error_t err;
size_t objidlen;
unsigned char *objid;
prkdf_object_t prkdf;
err = parse_certid (app, keyidstr, &objid, &objidlen);
if (err)
return err;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
if (prkdf->objidlen == objidlen && !memcmp (prkdf->objid, objid, objidlen))
break;
xfree (objid);
if (!prkdf)
return gpg_error (GPG_ERR_NOT_FOUND);
*r_prkdf = prkdf;
return 0;
}
/* Read and parse the Object Directory File and store away the
pointers. ODF_FID shall contain the FID of the ODF.
Example of such a file:
A0 06 30 04 04 02 60 34 = Private Keys
A4 06 30 04 04 02 60 35 = Certificates
A5 06 30 04 04 02 60 36 = Trusted Certificates
A7 06 30 04 04 02 60 37 = Data Objects
A8 06 30 04 04 02 60 38 = Auth Objects
These are all PathOrObjects using the path CHOICE element. The
paths are octet strings of length 2. Using this Path CHOICE
element is recommended, so we only implement that for now.
*/
static gpg_error_t
read_ef_odf (app_t app, unsigned short odf_fid)
{
gpg_error_t err;
unsigned char *buffer, *p;
size_t buflen, n;
unsigned short value;
size_t offset;
unsigned short home_df = 0;
app->app_local->odf.private_keys = 0;
app->app_local->odf.public_keys = 0;
app->app_local->odf.trusted_public_keys = 0;
app->app_local->odf.secret_keys = 0;
app->app_local->odf.certificates = 0;
app->app_local->odf.trusted_certificates = 0;
app->app_local->odf.useful_certificates = 0;
app->app_local->odf.data_objects = 0;
app->app_local->odf.auth_objects = 0;
err = select_and_read_binary (app, odf_fid, "ODF",
&buffer, &buflen);
if (err)
return err;
if (buflen < 8)
{
log_error ("p15: error: ODF too short\n");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
home_df = app->app_local->home_df;
p = buffer;
while (buflen && *p && *p != 0xff)
{
if ( buflen >= 8
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x06\x30\x04\x04\x02", 5) )
{
offset = 6;
}
else if ( buflen >= 12
&& (p[0] & 0xf0) == 0xA0
&& !memcmp (p+1, "\x0a\x30\x08\x04\x06\x3F\x00", 7)
&& (!home_df || home_df == ((p[8]<<8)|p[9])) )
{
/* FIXME: Is this hack still required? */
/* If we do not know the home DF, we take it from the first
* ODF object. Here are sample values:
* a0 0a 30 08 0406 3f00 5015 4401
* a1 0a 30 08 0406 3f00 5015 4411
* a4 0a 30 08 0406 3f00 5015 4441
* a5 0a 30 08 0406 3f00 5015 4451
* a8 0a 30 08 0406 3f00 5015 4481
* 00000000 */
if (!home_df)
{
home_df = ((p[8]<<8)|p[9]);
app->app_local->home_df = home_df;
log_info ("p15: application directory detected as 0x%04hX\n",
home_df);
/* We assume that direct path selection is possible. */
app->app_local->direct_path_selection = 1;
}
/* We only allow a full path if all files are at the same
level and below the home directory. To extend this we
would need to make use of new data type capable of
keeping a full path. */
offset = 10;
}
else
{
log_printhex (p, buflen, "p15: ODF format not supported:");
xfree (buffer);
return gpg_error (GPG_ERR_INV_OBJ);
}
switch ((p[0] & 0x0f))
{
case 0: value = app->app_local->odf.private_keys; break;
case 1: value = app->app_local->odf.public_keys; break;
case 2: value = app->app_local->odf.trusted_public_keys; break;
case 3: value = app->app_local->odf.secret_keys; break;
case 4: value = app->app_local->odf.certificates; break;
case 5: value = app->app_local->odf.trusted_certificates; break;
case 6: value = app->app_local->odf.useful_certificates; break;
case 7: value = app->app_local->odf.data_objects; break;
case 8: value = app->app_local->odf.auth_objects; break;
default: value = 0; break;
}
if (value)
{
log_error ("p15: duplicate object type %d in ODF ignored\n",
(p[0]&0x0f));
continue;
}
value = ((p[offset] << 8) | p[offset+1]);
switch ((p[0] & 0x0f))
{
case 0: app->app_local->odf.private_keys = value; break;
case 1: app->app_local->odf.public_keys = value; break;
case 2: app->app_local->odf.trusted_public_keys = value; break;
case 3: app->app_local->odf.secret_keys = value; break;
case 4: app->app_local->odf.certificates = value; break;
case 5: app->app_local->odf.trusted_certificates = value; break;
case 6: app->app_local->odf.useful_certificates = value; break;
case 7: app->app_local->odf.data_objects = value; break;
case 8: app->app_local->odf.auth_objects = value; break;
default:
log_error ("p15: unknown object type %d in ODF ignored\n",
(p[0]&0x0f));
}
offset += 2;
if (buflen < offset)
break;
p += offset;
buflen -= offset;
}
if (buflen)
{
/* Print a warning if non-null garbage is left over. */
for (n=0; n < buflen && !p[n]; n++)
;
if (n < buflen)
{
log_info ("p15: warning: garbage detected at end of ODF: ");
log_printhex (p, buflen, "");
}
}
xfree (buffer);
return 0;
}
/* Helper for the read_ef_foo functions to read the first record or
* the entire data. */
static gpg_error_t
read_first_record (app_t app, unsigned short fid, const char *fid_desc,
unsigned char **r_buffer, size_t *r_buflen,
int *r_use_read_record)
{
gpg_error_t err;
int sw;
*r_buffer = NULL;
*r_buflen = 0;
*r_use_read_record = 0;
if (!fid)
return gpg_error (GPG_ERR_NO_DATA); /* No such file. */
if (IS_CARDOS_5 (app))
{
*r_use_read_record = 1;
err = select_and_read_record (app, fid, 1, fid_desc,
r_buffer, r_buflen, &sw);
if (err && sw == SW_FILE_STRUCT)
{
*r_use_read_record = 0;
err = select_and_read_binary (app, 0, fid_desc, r_buffer, r_buflen);
}
}
else
err = select_and_read_binary (app, fid, fid_desc, r_buffer, r_buflen);
/* We get a not_found state in read_record mode if the select
* succeeded but reading the record failed. Map that to no_data
* which is what the caller of the read_ef_foo functions expect. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_NO_DATA);
return err;
}
/* Parse the BIT STRING with the keyUsageFlags from the
CommonKeyAttributes. */
static gpg_error_t
parse_keyusage_flags (const unsigned char *der, size_t derlen,
keyusage_flags_t *usageflags)
{
unsigned int bits, mask;
int i, unused, full;
memset (usageflags, 0, sizeof *usageflags);
if (!derlen)
return gpg_error (GPG_ERR_INV_OBJ);
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
return gpg_error (GPG_ERR_ENCODING_PROBLEM);
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->encrypt = 1;
if ((bits & 0x40)) usageflags->decrypt = 1;
if ((bits & 0x20)) usageflags->sign = 1;
if ((bits & 0x10)) usageflags->sign_recover = 1;
if ((bits & 0x08)) usageflags->wrap = 1;
if ((bits & 0x04)) usageflags->unwrap = 1;
if ((bits & 0x02)) usageflags->verify = 1;
if ((bits & 0x01)) usageflags->verify_recover = 1;
/* Second octet. */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
else
bits = 0;
if ((bits & 0x80)) usageflags->derive = 1;
if ((bits & 0x40)) usageflags->non_repudiation = 1;
return 0;
}
static void
dump_keyusage_flags (keyusage_flags_t usageflags)
{
const char *s = "";
log_info ("p15: usage=");
if (usageflags.encrypt)
log_printf ("%sencrypt", s), s = ",";
if (usageflags.decrypt)
log_printf ("%sdecrypt", s), s = ",";
if (usageflags.sign )
log_printf ("%ssign", s), s = ",";
if (usageflags.sign_recover)
log_printf ("%ssign_recover", s), s = ",";
if (usageflags.wrap )
log_printf ("%swrap", s), s = ",";
if (usageflags.unwrap )
log_printf ("%sunwrap", s), s = ",";
if (usageflags.verify )
log_printf ("%sverify", s), s = ",";
if (usageflags.verify_recover)
log_printf ("%sverify_recover", s), s = ",";
if (usageflags.derive )
log_printf ("%sderive", s), s = ",";
if (usageflags.non_repudiation)
log_printf ("%snon_repudiation", s), s = ",";
}
static void
dump_keyaccess_flags (keyaccess_flags_t accessflags)
{
const char *s = "";
log_info ("p15: access=");
if (accessflags.sensitive)
log_printf ("%ssensitive", s), s = ",";
if (accessflags.extractable)
log_printf ("%sextractable", s), s = ",";
if (accessflags.always_sensitive)
log_printf ("%salways_sensitive", s), s = ",";
if (accessflags.never_extractable)
log_printf ("%snever_extractable", s), s = ",";
if (accessflags.local)
log_printf ("%slocal", s), s = ",";
}
static void
dump_gpgusage_flags (gpgusage_flags_t gpgusage)
{
const char *s = "";
log_info ("p15: gpgusage=");
if (gpgusage.cert)
log_printf ("%scert", s), s = ",";
if (gpgusage.sign)
log_printf ("%ssign", s), s = ",";
if (gpgusage.encr)
log_printf ("%sencr", s), s = ",";
if (gpgusage.auth)
log_printf ("%sauth", s), s = ",";
}
/* Parse the BIT STRING with the keyAccessFlags from the
CommonKeyAttributes. */
static gpg_error_t
parse_keyaccess_flags (const unsigned char *der, size_t derlen,
keyaccess_flags_t *accessflags)
{
unsigned int bits, mask;
int i, unused, full;
memset (accessflags, 0, sizeof *accessflags);
if (!derlen)
return gpg_error (GPG_ERR_INV_OBJ);
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
return gpg_error (GPG_ERR_ENCODING_PROBLEM);
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x10)) accessflags->local = 1;
if ((bits & 0x08)) accessflags->never_extractable = 1;
if ((bits & 0x04)) accessflags->always_sensitive = 1;
if ((bits & 0x02)) accessflags->extractable = 1;
if ((bits & 0x01)) accessflags->sensitive = 1;
accessflags->any = 1;
return 0;
}
/* Parse the commonObjectAttributes and store a malloced authid at
* (r_authid,r_authidlen). (NULL,0) is stored on error or if no
* authid is found. IF R_LABEL is not NULL the label is stored there
* as a malloced string (spaces are replaced by underscores).
*
* Example data:
* 2 30 17: SEQUENCE { -- commonObjectAttributes
* 4 0C 8: UTF8String 'SK.CH.DS' -- label
* 14 03 2: BIT STRING 6 unused bits
* : '01'B (bit 0)
* 18 04 1: OCTET STRING --authid
* : 07
* : }
*/
static gpg_error_t
parse_common_obj_attr (unsigned char const **buffer, size_t *size,
unsigned char **r_authid, size_t *r_authidlen,
char **r_label)
{
gpg_error_t err;
int where;
int class, tag, constructed, ndef;
size_t objlen, hdrlen, nnn;
const unsigned char *ppp;
int ignore_eof = 0;
char *p;
*r_authid = NULL;
*r_authidlen = 0;
if (r_label)
*r_label = NULL;
where = __LINE__;
err = parse_ber_header (buffer, size, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > *size || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
ppp = *buffer;
nnn = objlen;
*buffer += objlen;
*size -= objlen;
/* Search the optional AuthId. */
ignore_eof = 1;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
if (tag == TAG_UTF8_STRING)
{
if (r_label)
{
*r_label = xtrymalloc (objlen + 1);
if (!*r_label)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_label, ppp, objlen);
(*r_label)[objlen] = 0;
/* We don't want spaces in the labels due to the properties
* of CHV-LABEL. */
for (p = *r_label; *p; p++)
if (ascii_isspace (*p))
*p = '_';
}
ppp += objlen;
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
}
if (tag == TAG_BIT_STRING)
{
ppp += objlen; /* Skip the CommonObjectFlags. */
nnn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn || class != CLASS_UNIVERSAL))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
}
if (tag == TAG_OCTET_STRING && objlen)
{
*r_authid = xtrymalloc (objlen);
if (!*r_authid)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_authid, ppp, objlen);
*r_authidlen = objlen;
}
leave:
if (ignore_eof && gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
else if (err)
log_error ("p15: error parsing commonObjectAttributes at %d: %s\n",
where, gpg_strerror (err));
if (err && r_label)
{
xfree (*r_label);
*r_label = NULL;
}
return err;
}
/* Parse the commonKeyAttributes. On success store the objid at
* (R_OBJID/R_OBJIDLEN), sets the key usage flags at USAGEFLAGS and
- * the optiona key refrence at R_KEY_REFERENCE. The latter is only
+ * the optional key reference at R_KEY_REFERENCE. The latter is only
* valid if true is also stored at R_KEY_REFERENCE_VALID.
*
* Example data:
*
* 21 30 12: SEQUENCE { -- commonKeyAttributes
* 23 04 1: OCTET STRING
* : 01
* 26 03 3: BIT STRING 6 unused bits
* : '1000000000'B (bit 9)
* 31 02 2: INTEGER 80 -- keyReference (optional)
* : }
*/
static gpg_error_t
parse_common_key_attr (unsigned char const **buffer, size_t *size,
unsigned char **r_objid, size_t *r_objidlen,
keyusage_flags_t *usageflags,
keyaccess_flags_t *accessflags,
unsigned long *r_key_reference,
int *r_key_reference_valid)
{
gpg_error_t err;
int where;
int class, tag, constructed, ndef;
size_t objlen, hdrlen, nnn;
const unsigned char *ppp;
int ignore_eof = 0;
unsigned long ul;
const unsigned char *objid = NULL;
size_t objidlen;
unsigned long key_reference = 0;
int key_reference_valid = 0;
*r_objid = NULL;
*r_objidlen = 0;
memset (usageflags, 0, sizeof *usageflags);
memset (accessflags, 0, sizeof *accessflags);
*r_key_reference_valid = 0;
where = __LINE__;
err = parse_ber_header (buffer, size, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > *size || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
ppp = *buffer;
nnn = objlen;
*buffer += objlen;
*size -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
objid = ppp;
objidlen = objlen;
ppp += objlen;
nnn -= objlen;
/* Get the KeyUsageFlags. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
err = parse_keyusage_flags (ppp, objlen, usageflags);
if (err)
goto leave;
ppp += objlen;
nnn -= objlen;
ignore_eof = 1; /* Remaining items are optional. */
/* Find the keyReference */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_BOOLEAN)
{
/* Skip the native element. */
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
}
if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING)
{
/* These are the keyAccessFlags. */
err = parse_keyaccess_flags (ppp, objlen, accessflags);
if (err)
goto leave;
ppp += objlen;
nnn -= objlen;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
}
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
/* This is the keyReference. */
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
key_reference = ul;
key_reference_valid = 1;
}
leave:
if (ignore_eof && gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
if (!err)
{
if (!objid || !objidlen)
err = gpg_error (GPG_ERR_INV_OBJ);
else
{
*r_objid = xtrymalloc (objidlen);
if (!*r_objid)
err = gpg_error_from_syserror ();
else
{
memcpy (*r_objid, objid, objidlen);
*r_objidlen = objidlen;
}
}
}
if (!err && key_reference_valid)
{
*r_key_reference = key_reference;
*r_key_reference_valid = 1;
}
if (err)
log_error ("p15: error parsing commonKeyAttributes at %d: %s\n",
where, gpg_strerror (err));
return err;
}
/* Read and parse the Private Key Directory Files.
*
* Sample object:
* SEQUENCE {
* SEQUENCE { -- commonObjectAttributes
* UTF8String 'SK.CH.DS'
* BIT STRING 6 unused bits
* '01'B (bit 0) -- flags: non-modifiable,private
* OCTET STRING --authid
* 07
* }
* SEQUENCE { -- commonKeyAttributes
* OCTET STRING
* 01
* BIT STRING 6 unused bits
* '1000000000'B (bit 9) -- keyusage: non-repudiation
* INTEGER 80 -- keyReference (optional)
* }
* [1] { -- keyAttributes
* SEQUENCE { -- privateRSAKeyAttributes
* SEQUENCE { -- objectValue
* OCTET STRING --path
* 3F 00 40 16 00 50
* }
* INTEGER 1024 -- modulus
* }
* }
* }
*
* Sample part for EC objects:
* [1] { -- keyAttributes
* [1] { -- privateECkeyAttributes
* SEQUENCE { -- objectValue
* SEQUENCE { --path
* OCTET STRING 50 72 4B 03
* }
* INTEGER 33 -- Not in PKCS#15v1.1, need to buy 7816-15?
* }
* }
*/
static gpg_error_t
read_ef_prkdf (app_t app, unsigned short fid, prkdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
prkdf_object_t prkdflist = NULL;
int i;
int recno = 1;
unsigned char *authid = NULL;
size_t authidlen = 0;
unsigned char *objid = NULL;
size_t objidlen = 0;
char *label = NULL;
int record_mode;
err = read_first_record (app, fid, "PrKDF", &buffer, &buflen, &record_mode);
if (err)
return err;
p = buffer;
n = buflen;
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. Note the
special handling for record mode at the end of the loop. */
if (record_mode && buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
prkdf_object_t prkdf = NULL;
unsigned long ul;
keyusage_flags_t usageflags;
keyaccess_flags_t accessflags;
unsigned long key_reference = 0;
int key_reference_valid = 0;
int is_ecc = 0;
where = __LINE__;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
;
else if (objlen > n)
err = gpg_error (GPG_ERR_INV_OBJ);
else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* PrivateRSAKeyAttributes */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: is_ecc = 1; break; /* PrivateECKeyAttributes */
case 1: errstr = "DH key objects are not supported"; break;
case 2: errstr = "DSA key objects are not supported"; break;
case 3: errstr = "KEA key objects are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
if (errstr)
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
if (err)
{
log_error ("p15: error parsing PrKDF record: %s\n",
gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
xfree (authid);
xfree (label);
err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label);
if (err)
goto parse_error;
/* Parse the commonKeyAttributes. */
where = __LINE__;
xfree (objid);
err = parse_common_key_attr (&pp, &nn,
&objid, &objidlen,
&usageflags, &accessflags,
&key_reference, &key_reference_valid);
if (err)
goto parse_error;
log_assert (objid);
/* Skip commonPrivateKeyAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_CONTEXT && tag == 0)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
}
/* Parse the keyAttributes. */
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
;
else if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* A typeAttribute always starts with a sequence. */
else
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element has a path of even length
* (FIDs are two bytes each). We should check that the path
* length is non-zero but some cards return a zero length path
* nevertheless (e.g. A.E.T. Europe Java applets). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new PrKDF list item. */
prkdf = xtrycalloc (1, (sizeof *prkdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!prkdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
prkdf->is_ecc = is_ecc;
prkdf->objidlen = objidlen;
prkdf->objid = objid;
objid = NULL;
if (authid)
{
prkdf->authidlen = authidlen;
prkdf->authid = authid;
authid = NULL;
}
if (label)
{
prkdf->label = label;
label = NULL;
}
prkdf->pathlen = objlen/2;
for (i=0; i < prkdf->pathlen; i++, pp += 2, nn -= 2)
prkdf->path[i] = ((pp[0] << 8) | pp[1]);
prkdf->usageflags = usageflags;
prkdf->accessflags = accessflags;
prkdf->key_reference = key_reference;
prkdf->key_reference_valid = key_reference_valid;
if (nn)
{
/* An index and length follows. */
prkdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
prkdf->len = ul;
}
/* The info is printed later in read_p15_info because we also
* want to look at the certificates. */
/* Put it into the list. */
prkdf->next = prkdflist;
prkdflist = prkdf;
prkdf = NULL;
goto next_record; /* Ready with this record. */
parse_error:
log_error ("p15: error parsing PrKDF record at %d: %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
if (prkdf)
{
xfree (prkdf->objid);
xfree (prkdf->authid);
xfree (prkdf->label);
xfree (prkdf);
}
err = 0;
next_record:
/* If the card uses a record oriented file structure, read the
* next record. Otherwise we keep on parsing the current buffer. */
recno++;
if (record_mode)
{
xfree (buffer); buffer = NULL;
err = select_and_read_record (app, 0, recno, "PrKDF",
&buffer, &buflen, NULL);
if (err) {
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
goto leave;
}
if (buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
p = buffer;
n = buflen;
}
} /* End looping over all records. */
leave:
xfree (authid);
xfree (label);
xfree (objid);
xfree (buffer);
if (err)
release_prkdflist (prkdflist);
else
*result = prkdflist;
return err;
}
/* Read and parse the Public Keys Directory File. */
static gpg_error_t
read_ef_pukdf (app_t app, unsigned short fid, pukdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
pukdf_object_t pukdflist = NULL;
int i;
int recno = 1;
unsigned char *authid = NULL;
size_t authidlen = 0;
unsigned char *objid = NULL;
size_t objidlen = 0;
char *label = NULL;
int record_mode;
err = read_first_record (app, fid, "PuKDF", &buffer, &buflen, &record_mode);
if (err)
return err;
p = buffer;
n = buflen;
/* Loop over the records. We stop as soon as we detect a new record
* starting with 0x00 or 0xff as these values are commonly used to
* pad data blocks and are no valid ASN.1 encoding. Note the
* special handling for record mode at the end of the loop. */
if (record_mode && buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
pukdf_object_t pukdf = NULL;
unsigned long ul;
keyusage_flags_t usageflags;
keyaccess_flags_t accessflags;
unsigned long key_reference = 0;
int key_reference_valid = 0;
where = __LINE__;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
;
else if (objlen > n)
err = gpg_error (GPG_ERR_INV_OBJ);
else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* PublicRSAKeyAttributes */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: break; /* EC key object */
case 1: errstr = "DH key objects are not supported"; break;
case 2: errstr = "DSA key objects are not supported"; break;
case 3: errstr = "KEA key objects are not supported"; break;
default: errstr = "unknown publicKeyObject"; break;
}
if (errstr)
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
if (err)
{
log_error ("p15: error parsing PuKDF record: %s\n",
gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
xfree (authid);
xfree (label);
err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label);
if (err)
goto parse_error;
/* Parse the commonKeyAttributes. */
where = __LINE__;
xfree (objid);
err = parse_common_key_attr (&pp, &nn,
&objid, &objidlen,
&usageflags, &accessflags,
&key_reference, &key_reference_valid);
if (err)
goto parse_error;
log_assert (objid);
/* Parse the subClassAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_CONTEXT && tag == 0)
{
/* Skip this CommonPublicKeyAttribute. */
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
}
/* We expect a typeAttribute. */
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error; /* No typeAttribute. */
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
;
else if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* A typeAttribute always starts with a sequence. */
else
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element has a path of even length
* (FIDs are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new PuKDF list item. */
pukdf = xtrycalloc (1, (sizeof *pukdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!pukdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
pukdf->objidlen = objidlen;
pukdf->objid = objid;
objid = NULL;
if (authid)
{
pukdf->authidlen = authidlen;
pukdf->authid = authid;
authid = NULL;
}
if (label)
{
pukdf->label = label;
label = NULL;
}
pukdf->pathlen = objlen/2;
for (i=0; i < pukdf->pathlen; i++, pp += 2, nn -= 2)
pukdf->path[i] = ((pp[0] << 8) | pp[1]);
pukdf->usageflags = usageflags;
pukdf->accessflags = accessflags;
pukdf->key_reference = key_reference;
pukdf->key_reference_valid = key_reference_valid;
if (nn)
{
/* An index and length follows. */
pukdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
pukdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
pukdf->len = ul;
}
if (opt.verbose)
{
log_info ("p15: PuKDF %04hX: id=", fid);
for (i=0; i < pukdf->objidlen; i++)
log_printf ("%02X", pukdf->objid[i]);
if (pukdf->label)
log_printf (" (%s)", pukdf->label);
log_info ("p15: path=");
for (i=0; i < pukdf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"",pukdf->path[i]);
if (pukdf->have_off)
log_printf ("[%lu/%lu]", pukdf->off, pukdf->len);
if (pukdf->authid)
{
log_printf (" authid=");
for (i=0; i < pukdf->authidlen; i++)
log_printf ("%02X", pukdf->authid[i]);
}
if (pukdf->key_reference_valid)
log_printf (" keyref=0x%02lX", pukdf->key_reference);
if (pukdf->accessflags.any)
dump_keyaccess_flags (pukdf->accessflags);
dump_keyusage_flags (pukdf->usageflags);
log_printf ("\n");
}
/* Put it into the list. */
pukdf->next = pukdflist;
pukdflist = pukdf;
pukdf = NULL;
goto next_record; /* Ready with this record. */
parse_error:
log_error ("p15: error parsing PuKDF record at %d: %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
if (pukdf)
{
xfree (pukdf->objid);
xfree (pukdf->authid);
xfree (pukdf->label);
xfree (pukdf);
}
err = 0;
next_record:
/* If the card uses a record oriented file structure, read the
* next record. Otherwise we keep on parsing the current buffer. */
recno++;
if (record_mode)
{
xfree (buffer); buffer = NULL;
err = select_and_read_record (app, 0, recno, "PuKDF",
&buffer, &buflen, NULL);
if (err) {
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
goto leave;
}
if (buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
p = buffer;
n = buflen;
}
} /* End looping over all records. */
leave:
xfree (authid);
xfree (label);
xfree (objid);
xfree (buffer);
if (err)
release_pukdflist (pukdflist);
else
*result = pukdflist;
return err;
}
/* Return true id CDFLIST has the given object id. */
static int
objid_in_cdflist_p (cdf_object_t cdflist,
const unsigned char *objid, size_t objidlen)
{
cdf_object_t cdf;
if (!objid || !objidlen)
return 0;
for (cdf = cdflist; cdf; cdf = cdf->next)
if (cdf->objidlen == objidlen && !memcmp (cdf->objid, objid, objidlen))
return 1;
return 0;
}
/* Read and parse the Certificate Directory Files identified by FID.
On success a newlist of CDF object gets stored at RESULT and the
caller is then responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_cdf (app_t app, unsigned short fid, int cdftype, cdf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
cdf_object_t cdflist = NULL;
int i;
int recno = 1;
unsigned char *authid = NULL;
size_t authidlen = 0;
char *label = NULL;
int record_mode;
err = read_first_record (app, fid, "CDF", &buffer, &buflen, &record_mode);
if (err)
return err;
p = buffer;
n = buflen;
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. Note the
special handling for record mode at the end of the loop. */
if (record_mode && buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
cdf_object_t cdf = NULL;
unsigned long ul;
const unsigned char *objid;
size_t objidlen;
int objidextralen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("p15: error parsing CDF record: %s\n", gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Parse the commonObjectAttributes. */
where = __LINE__;
xfree (authid);
xfree (label);
err = parse_common_obj_attr (&pp, &nn, &authid, &authidlen, &label);
if (err)
goto parse_error;
/* Parse the commonCertificateAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
objid = ppp;
objidlen = objlen;
}
/* Parse the certAttribute. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
/* Check that the reference is a Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class != CLASS_UNIVERSAL || tag != TAG_SEQUENCE)
{
errstr = "unsupported reference type";
goto parse_error;
}
nn = objlen;
/* Parse the Path object. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element has a path of even length
* (FIDs are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
/* Create a new CDF list item. */
cdf = xtrycalloc (1, (sizeof *cdf
- sizeof(unsigned short)
+ objlen/2 * sizeof(unsigned short)));
if (!cdf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (authid)
{
cdf->authidlen = authidlen;
cdf->authid = authid;
authid = NULL;
}
if (label)
{
cdf->label = label;
label = NULL;
}
/* Card's have been found in the wild which do not have unique
* IDs for their certificate objects. If we detect this we
* append a counter to the ID. */
objidextralen =
(objid_in_cdflist_p (cdflist, objid, objidlen)
|| objid_in_cdflist_p (app->app_local->certificate_info,
objid, objidlen)
|| objid_in_cdflist_p (app->app_local->trusted_certificate_info,
objid, objidlen)
|| objid_in_cdflist_p (app->app_local->useful_certificate_info,
objid, objidlen));
cdf->objidlen = objidlen + objidextralen;
cdf->objid = xtrymalloc (objidlen + objidextralen);
if (!cdf->objid)
{
err = gpg_error_from_syserror ();
xfree (cdf);
goto leave;
}
memcpy (cdf->objid, objid, objidlen);
if (objidextralen)
{
if (app->app_local->cdf_dup_counter == 255)
{
log_error ("p15: too many duplicate certificate ids\n");
err = gpg_error (GPG_ERR_TOO_MANY);
goto parse_error;
}
cdf->objid[objidlen] = ++app->app_local->cdf_dup_counter;
}
cdf->pathlen = objlen/2;
for (i=0; i < cdf->pathlen; i++, pp += 2, nn -= 2)
cdf->path[i] = ((pp[0] << 8) | pp[1]);
if (nn)
{
/* An index and length follows. */
cdf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->off = ul;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
cdf->len = ul;
}
if (opt.verbose)
{
log_info ("p15: CDF-%c %04hX: id=", cdftype, fid);
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
if (cdf->label)
log_printf (" (%s)", cdf->label);
log_info ("p15: path=");
for (i=0; i < cdf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"", cdf->path[i]);
if (cdf->have_off)
log_printf ("[%lu/%lu]", cdf->off, cdf->len);
if (cdf->authid)
{
log_printf (" authid=");
for (i=0; i < cdf->authidlen; i++)
log_printf ("%02X", cdf->authid[i]);
}
log_printf ("\n");
}
/* Put it into the list. */
cdf->next = cdflist;
cdflist = cdf;
cdf = NULL;
goto next_record; /* Ready with this record. */
parse_error:
log_error ("p15: error parsing CDF record at %d: %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
xfree (cdf);
err = 0;
next_record:
xfree (authid); authid = NULL;
xfree (label); label = NULL;
/* If the card uses a record oriented file structure, read the
* next record. Otherwise we keep on parsing the current buffer. */
recno++;
if (record_mode)
{
xfree (buffer); buffer = NULL;
err = select_and_read_record (app, 0, recno, "CDF",
&buffer, &buflen, NULL);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
goto leave;
}
if (buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
p = buffer;
n = buflen;
}
} /* End loop over all records. */
leave:
xfree (authid);
xfree (label);
xfree (buffer);
if (err)
release_cdflist (cdflist);
else
*result = cdflist;
return err;
}
/*
* SEQUENCE {
* SEQUENCE { -- CommonObjectAttributes
* UTF8String 'specific PIN for DS'
* BIT STRING 0 unused bits
* '00000011'B
* }
* SEQUENCE { -- CommonAuthenticationObjectAttributes
* OCTET STRING
* 07 -- iD
* }
*
* [1] { -- typeAttributes
* SEQUENCE { -- PinAttributes
* BIT STRING 0 unused bits
* '0000100000110010'B -- local,initialized,needs-padding
* -- exchangeRefData
* ENUMERATED 1 -- ascii-numeric
* INTEGER 6 -- minLength
* INTEGER 6 -- storedLength
* INTEGER 8 -- maxLength
* [0]
* 02 -- pinReference
* GeneralizedTime 19/04/2002 12:12 GMT -- lastPinChange
* SEQUENCE {
* OCTET STRING
* 3F 00 40 16 -- path to DF of PIN
* }
* }
* }
* }
*
* Or for an authKey:
*
* [1] { -- typeAttributes
* SEQUENCE { -- AuthKeyAttributes
* BOOLEAN TRUE -- derivedKey
* OCTET STRING 02 -- authKeyId
* }
* }
* }
*/
/* Read and parse an Authentication Object Directory File identified
by FID. On success a newlist of AODF objects gets stored at RESULT
and the caller is responsible of releasing this list. On error a
error code is returned and RESULT won't get changed. */
static gpg_error_t
read_ef_aodf (app_t app, unsigned short fid, aodf_object_t *result)
{
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
aodf_object_t aodflist = NULL;
int i;
int recno = 1;
int record_mode;
err = read_first_record (app, fid, "AODF", &buffer, &buflen, &record_mode);
if (err)
return err;
p = buffer;
n = buflen;
/* Loop over the records. We stop as soon as we detect a new record
starting with 0x00 or 0xff as these values are commonly used to
pad data blocks and are no valid ASN.1 encoding. Note the
special handling for record mode at the end of the loop. */
if (record_mode && buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
while (n && *p && *p != 0xff)
{
const unsigned char *pp;
size_t nn;
int where;
const char *errstr = NULL;
auth_type_t auth_type;
aodf_object_t aodf = NULL;
unsigned long ul;
const char *s;
where = __LINE__;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
;
else if (objlen > n)
err = gpg_error (GPG_ERR_INV_OBJ);
else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
auth_type = AUTH_TYPE_PIN; /* PinAttributes */
else if (class == CLASS_CONTEXT && tag == 1 )
auth_type = AUTH_TYPE_AUTHKEY; /* AuthKeyAttributes */
else if (class == CLASS_CONTEXT)
{
switch (tag)
{
case 0: errstr = "biometric auth types are not supported"; break;
case 2: errstr = "external auth type are not supported"; break;
default: errstr = "unknown privateKeyObject"; break;
}
p += objlen;
n -= objlen;
goto parse_error;
}
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto parse_error;
}
if (err)
{
log_error ("p15: error parsing AODF record: %s\n",
gpg_strerror (err));
goto leave;
}
pp = p;
nn = objlen;
p += objlen;
n -= objlen;
/* Allocate memory for a new AODF list item. */
aodf = xtrycalloc (1, sizeof *aodf);
if (!aodf)
goto no_core;
aodf->fid = fid;
aodf->auth_type = auth_type;
/* Parse the commonObjectAttributes. */
where = __LINE__;
err = parse_common_obj_attr (&pp, &nn, &aodf->authid, &aodf->authidlen,
&aodf->label);
if (err)
goto parse_error;
/* Parse the CommonAuthenticationObjectAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
/* Get the Id. */
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
aodf->objidlen = objlen;
aodf->objid = xtrymalloc (objlen);
if (!aodf->objid)
goto no_core;
memcpy (aodf->objid, ppp, objlen);
}
/* Parse the typeAttributes. */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || class != CLASS_CONTEXT || tag != 1))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
;
else if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
else if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE)
; /* Okay */
else
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
nn = objlen;
if (auth_type == AUTH_TYPE_PIN)
{
/* PinFlags */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn || !objlen
|| class != CLASS_UNIVERSAL || tag != TAG_BIT_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
{
unsigned int bits, mask;
int unused, full;
unused = *pp++; nn--; objlen--;
if ((!objlen && unused) || unused/8 > objlen)
{
err = gpg_error (GPG_ERR_ENCODING_PROBLEM);
goto parse_error;
}
full = objlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* The first octet */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
if ((bits & 0x80)) /* ASN.1 bit 0. */
aodf->pinflags.case_sensitive = 1;
if ((bits & 0x40)) /* ASN.1 bit 1. */
aodf->pinflags.local = 1;
if ((bits & 0x20))
aodf->pinflags.change_disabled = 1;
if ((bits & 0x10))
aodf->pinflags.unblock_disabled = 1;
if ((bits & 0x08))
aodf->pinflags.initialized = 1;
if ((bits & 0x04))
aodf->pinflags.needs_padding = 1;
if ((bits & 0x02))
aodf->pinflags.unblocking_pin = 1;
if ((bits & 0x01))
aodf->pinflags.so_pin = 1;
/* The second octet. */
bits = 0;
if (objlen)
{
bits = *pp++; nn--; objlen--;
if (full)
full--;
else
{
bits &= ~mask;
}
}
if ((bits & 0x80))
aodf->pinflags.disable_allowed = 1;
if ((bits & 0x40))
aodf->pinflags.integrity_protected = 1;
if ((bits & 0x20))
aodf->pinflags.confidentiality_protected = 1;
if ((bits & 0x10))
aodf->pinflags.exchange_ref_data = 1;
/* Skip remaining bits. */
pp += objlen;
nn -= objlen;
}
/* PinType */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_ENUMERATED))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pintype = ul;
/* minLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->min_length = ul;
/* storedLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nn
|| class != CLASS_UNIVERSAL || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err && objlen > sizeof (ul))
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->stored_length = ul;
/* optional maxLength */
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
if (class == CLASS_UNIVERSAL && tag == TAG_INTEGER)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->max_length = ul;
aodf->max_length_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional pinReference. */
if (class == CLASS_CONTEXT && tag == 0)
{
if (objlen > sizeof (ul))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto parse_error;
}
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*pp++) & 0xff;
nn--;
}
aodf->pin_reference = ul;
aodf->pin_reference_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional padChar. */
if (class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING)
{
if (objlen != 1)
{
errstr = "padChar is not of size(1)";
goto parse_error;
}
aodf->pad_char = *pp++; nn--;
aodf->pad_char_valid = 1;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Skip optional lastPinChange. */
if (class == CLASS_UNIVERSAL && tag == TAG_GENERALIZED_TIME)
{
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&pp, &nn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (gpg_err_code (err) == GPG_ERR_EOF)
goto ready;
if (!err && objlen > nn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
}
/* Optional Path object. */
if (class == CLASS_UNIVERSAL || tag == TAG_SEQUENCE)
{
const unsigned char *ppp = pp;
size_t nnn = objlen;
pp += objlen;
nn -= objlen;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && objlen > nnn)
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
/* Make sure that the next element has a path of even
* length (FIDs are two bytes each). */
if (class != CLASS_UNIVERSAL || tag != TAG_OCTET_STRING
|| (objlen & 1) )
{
errstr = "invalid path reference";
goto parse_error;
}
aodf->pathlen = objlen/2;
aodf->path = xtrycalloc (aodf->pathlen, sizeof *aodf->path);
if (!aodf->path)
goto no_core;
for (i=0; i < aodf->pathlen; i++, ppp += 2, nnn -= 2)
aodf->path[i] = ((ppp[0] << 8) | ppp[1]);
if (nnn)
{
/* An index and length follows. */
aodf->have_off = 1;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag,
&constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_UNIVERSAL
|| tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->off = ul;
where = __LINE__;
err = parse_ber_header (&ppp, &nnn, &class, &tag,
&constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > nnn
|| class != CLASS_CONTEXT || tag != 0))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto parse_error;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*ppp++) & 0xff;
nnn--;
}
aodf->len = ul;
}
}
}
else if (auth_type == AUTH_TYPE_AUTHKEY)
{
}
/* Ignore further objects which might be there due to future
extensions of pkcs#15. */
ready:
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
if (opt.verbose)
{
log_info ("p15: AODF %04hX: id=", fid);
for (i=0; i < aodf->objidlen; i++)
log_printf ("%02X", aodf->objid[i]);
if (aodf->label)
log_printf (" (%s)", aodf->label);
log_info ("p15: ");
log_printf (" %s",
aodf->auth_type == AUTH_TYPE_PIN? "pin" :
aodf->auth_type == AUTH_TYPE_AUTHKEY? "authkey" : "?");
if (aodf->pathlen)
{
log_printf (" path=");
for (i=0; i < aodf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"",aodf->path[i]);
if (aodf->have_off)
log_printf ("[%lu/%lu]", aodf->off, aodf->len);
}
if (aodf->authid)
{
log_printf (" authid=");
for (i=0; i < aodf->authidlen; i++)
log_printf ("%02X", aodf->authid[i]);
}
if (aodf->auth_type == AUTH_TYPE_PIN)
{
if (aodf->pin_reference_valid)
log_printf (" pinref=0x%02lX", aodf->pin_reference);
log_printf (" min=%lu", aodf->min_length);
log_printf (" stored=%lu", aodf->stored_length);
if (aodf->max_length_valid)
log_printf (" max=%lu", aodf->max_length);
if (aodf->pad_char_valid)
log_printf (" pad=0x%02x", (unsigned char)aodf->pad_char);
log_info ("p15: flags=");
s = "";
if (aodf->pinflags.case_sensitive)
log_printf ("%scase_sensitive", s), s = ",";
if (aodf->pinflags.local)
log_printf ("%slocal", s), s = ",";
if (aodf->pinflags.change_disabled)
log_printf ("%schange_disabled", s), s = ",";
if (aodf->pinflags.unblock_disabled)
log_printf ("%sunblock_disabled", s), s = ",";
if (aodf->pinflags.initialized)
log_printf ("%sinitialized", s), s = ",";
if (aodf->pinflags.needs_padding)
log_printf ("%sneeds_padding", s), s = ",";
if (aodf->pinflags.unblocking_pin)
log_printf ("%sunblocking_pin", s), s = ",";
if (aodf->pinflags.so_pin)
log_printf ("%sso_pin", s), s = ",";
if (aodf->pinflags.disable_allowed)
log_printf ("%sdisable_allowed", s), s = ",";
if (aodf->pinflags.integrity_protected)
log_printf ("%sintegrity_protected", s), s = ",";
if (aodf->pinflags.confidentiality_protected)
log_printf ("%sconfidentiality_protected", s), s = ",";
if (aodf->pinflags.exchange_ref_data)
log_printf ("%sexchange_ref_data", s), s = ",";
{
char numbuf[50];
const char *s2;
switch (aodf->pintype)
{
case PIN_TYPE_BCD: s2 = "bcd"; break;
case PIN_TYPE_ASCII_NUMERIC: s2 = "ascii-numeric"; break;
case PIN_TYPE_UTF8: s2 = "utf8"; break;
case PIN_TYPE_HALF_NIBBLE_BCD: s2 = "half-nibble-bcd"; break;
case PIN_TYPE_ISO9564_1: s2 = "iso9564-1"; break;
default:
sprintf (numbuf, "%lu", (unsigned long)aodf->pintype);
s2 = numbuf;
}
log_printf ("%stype=%s", s, s2); s = ",";
}
}
else if (aodf->auth_type == AUTH_TYPE_AUTHKEY)
{
}
log_printf ("\n");
}
/* Put it into the list. */
aodf->next = aodflist;
aodflist = aodf;
aodf = NULL;
goto next_record; /* Ready with this record. */
no_core:
err = gpg_error_from_syserror ();
release_aodf_object (aodf);
goto leave;
parse_error:
log_error ("p15: error parsing AODF record at %d: %s - skipped\n",
where, errstr? errstr : gpg_strerror (err));
err = 0;
release_aodf_object (aodf);
next_record:
/* If the card uses a record oriented file structure, read the
* next record. Otherwise we keep on parsing the current buffer. */
recno++;
if (record_mode)
{
xfree (buffer); buffer = NULL;
err = select_and_read_record (app, 0, recno, "AODF",
&buffer, &buflen, NULL);
if (err) {
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
goto leave;
}
if (buflen == 2 && !buffer[0] && !buffer[1])
goto next_record; /* Deleted record - continue with next */
p = buffer;
n = buflen;
}
} /* End looping over all records. */
leave:
xfree (buffer);
if (err)
release_aodflist (aodflist);
else
*result = aodflist;
return err;
}
/* Print the BIT STRING with the tokenflags from the TokenInfo. */
static void
print_tokeninfo_tokenflags (const unsigned char *der, size_t derlen)
{
unsigned int bits, mask;
int i, unused, full;
int other = 0;
if (!derlen)
{
log_printf (" [invalid object]");
return;
}
unused = *der++; derlen--;
if ((!derlen && unused) || unused/8 > derlen)
{
log_printf (" [wrong encoding]");
return;
}
full = derlen - (unused+7)/8;
unused %= 8;
mask = 0;
for (i=1; unused; i <<= 1, unused--)
mask |= i;
/* First octet */
if (derlen)
{
bits = *der++; derlen--;
if (full)
full--;
else
{
bits &= ~mask;
mask = 0;
}
}
else
bits = 0;
if ((bits & 0x80)) log_printf (" readonly");
if ((bits & 0x40)) log_printf (" loginRequired");
if ((bits & 0x20)) log_printf (" prnGeneration");
if ((bits & 0x10)) log_printf (" eidCompliant");
if ((bits & 0x08)) other = 1;
if ((bits & 0x04)) other = 1;
if ((bits & 0x02)) other = 1;
if ((bits & 0x01)) other = 1;
/* Next octet. */
if (derlen)
other = 1;
if (other)
log_printf (" [unknown]");
}
/* Read and parse the EF(TokenInfo).
*
* TokenInfo ::= SEQUENCE {
* version INTEGER {v1(0)} (v1,...),
* serialNumber OCTET STRING,
* manufacturerID Label OPTIONAL,
* label [0] Label OPTIONAL,
* tokenflags TokenFlags,
* seInfo SEQUENCE OF SecurityEnvironmentInfo OPTIONAL,
* recordInfo [1] RecordInfo OPTIONAL,
* supportedAlgorithms [2] SEQUENCE OF AlgorithmInfo OPTIONAL,
* ...,
* issuerId [3] Label OPTIONAL,
* holderId [4] Label OPTIONAL,
* lastUpdate [5] LastUpdate OPTIONAL,
* preferredLanguage PrintableString OPTIONAL -- In accordance with
* -- IETF RFC 1766
* } (CONSTRAINED BY { -- Each AlgorithmInfo.reference value must be unique --})
*
* TokenFlags ::= BIT STRING {
* readOnly (0),
* loginRequired (1),
* prnGeneration (2),
* eidCompliant (3)
* }
*
*
* Sample EF 5032:
* 30 31 02 01 00 04 04 05 45 36 9F 0C 0C 44 2D 54 01......E6...D-T
* 72 75 73 74 20 47 6D 62 48 80 14 4F 66 66 69 63 rust GmbH..Offic
* 65 20 69 64 65 6E 74 69 74 79 20 63 61 72 64 03 e identity card.
* 02 00 40 20 63 61 72 64 03 02 00 40 00 00 00 00 ..@ card...@....
* 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
*
* 0 49: SEQUENCE {
* 2 1: INTEGER 0
* 5 4: OCTET STRING 05 45 36 9F
* 11 12: UTF8String 'D-Trust GmbH'
* 25 20: [0] 'Office identity card'
* 47 2: BIT STRING
* : '00000010'B (bit 1)
* : Error: Spurious zero bits in bitstring.
* : }
*/
static gpg_error_t
read_ef_tokeninfo (app_t app)
{
gpg_error_t err;
unsigned char *buffer = NULL;
size_t buflen;
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, constructed, ndef;
unsigned long ul;
release_tokeninfo (app);
app->app_local->card_product = CARD_PRODUCT_UNKNOWN;
err = select_and_read_binary (app, 0x5032, "TokenInfo", &buffer, &buflen);
if (err)
return err;
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
log_error ("p15: error parsing TokenInfo: %s\n", gpg_strerror (err));
goto leave;
}
n = objlen;
/* Version. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_INTEGER))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
for (ul=0; objlen; objlen--)
{
ul <<= 8;
ul |= (*p++) & 0xff;
n--;
}
if (ul > 1)
{
log_error ("p15: invalid version %lu in TokenInfo\n", ul);
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
/* serialNumber. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || tag != TAG_OCTET_STRING || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
xfree (app->app_local->serialno);
app->app_local->serialno = xtrymalloc (objlen);
if (!app->app_local->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (app->app_local->serialno, p, objlen);
app->app_local->serialnolen = objlen;
p += objlen;
n -= objlen;
/* Is there an optional manufacturerID? */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_UTF8_STRING)
{
app->app_local->manufacturer_id = percent_data_escape (0, NULL,
p, objlen);
p += objlen;
n -= objlen;
/* Get next TLV. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
}
if (class == CLASS_CONTEXT && tag == 0)
{
app->app_local->token_label = percent_data_escape (0, NULL, p, objlen);
p += objlen;
n -= objlen;
/* Get next TLV. */
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > n || !objlen))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
}
/* The next is the mandatory tokenflags object. */
if (class == CLASS_UNIVERSAL && tag == TAG_BIT_STRING)
{
app->app_local->tokenflagslen = objlen;
app->app_local->tokenflags = xtrymalloc (objlen);
if (!app->app_local->tokenflags)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (app->app_local->tokenflags, p, objlen);
p += objlen;
n -= objlen;
}
leave:
xfree (buffer);
return err;
}
/* Get all the basic information from the pkcs#15 card, check the
structure and initialize our local context. This is used once at
application initialization. */
static gpg_error_t
read_p15_info (app_t app)
{
gpg_error_t err;
prkdf_object_t prkdf;
unsigned int flag;
const char *manu;
err = read_ef_tokeninfo (app);
if (err)
return err;
/* If we don't have a serial number yet but the TokenInfo provides
* one, use that. */
if (!APP_CARD(app)->serialno && app->app_local->serialno)
{
APP_CARD(app)->serialno = app->app_local->serialno;
APP_CARD(app)->serialnolen = app->app_local->serialnolen;
app->app_local->serialno = NULL;
app->app_local->serialnolen = 0;
err = app_munge_serialno (APP_CARD(app));
if (err)
return err;
}
release_lists (app);
/* Set a product type from the manufacturer_id. */
if (!(manu = app->app_local->manufacturer_id) || !*manu)
; /* No manufacturer_id. */
else if (app->app_local->card_product)
; /* Already set. */
else if (IS_CARDOS_5 (app))
{
if (!ascii_strcasecmp (manu, "GeNUA mbH"))
app->app_local->card_product = CARD_PRODUCT_GENUA;
else if (!ascii_strcasecmp (manu, "Technology Nexus"))
app->app_local->card_product = CARD_PRODUCT_NEXUS;
}
else if (app->app_local->card_type == CARD_TYPE_STARCOS_32)
{
if (strstr (manu, "cryptovision"))
app->app_local->card_product = CARD_PRODUCT_CVISION;
}
/* Read the ODF so that we know the location of all directory
files. */
/* Fixme: We might need to get a non-standard ODF FID from TokenInfo. */
err = read_ef_odf (app, 0x5031);
if (err)
return err;
/* Read certificate information. */
log_assert (!app->app_local->certificate_info);
log_assert (!app->app_local->trusted_certificate_info);
log_assert (!app->app_local->useful_certificate_info);
app->app_local->cdf_dup_counter = 0;
err = read_ef_cdf (app, app->app_local->odf.certificates, 'c',
&app->app_local->certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.trusted_certificates, 't',
&app->app_local->trusted_certificate_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_cdf (app, app->app_local->odf.useful_certificates, 'u',
&app->app_local->useful_certificate_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about public keys. */
log_assert (!app->app_local->public_key_info);
err = read_ef_pukdf (app, app->app_local->odf.public_keys,
&app->app_local->public_key_info);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
err = read_ef_pukdf (app, app->app_local->odf.trusted_public_keys,
&app->app_local->public_key_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about private keys. */
log_assert (!app->app_local->private_key_info);
err = read_ef_prkdf (app, app->app_local->odf.private_keys,
&app->app_local->private_key_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
if (err)
return err;
/* Read information about authentication objects. */
log_assert (!app->app_local->auth_object_info);
err = read_ef_aodf (app, app->app_local->odf.auth_objects,
&app->app_local->auth_object_info);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
err = 0;
/* See whether we can extend the private key information using
* information from certificates. We use only the first matching
* certificate; if we want to change this strategy we should walk
- * over the certificates and then find the corresponsing private key
+ * over the certificates and then find the corresponding private key
* objects. */
app->app_local->any_gpgusage = 0;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
{
cdf_object_t cdf;
char *extusage;
char *p, *pend;
int seen, i;
if (opt.debug)
log_printhex (prkdf->objid, prkdf->objidlen, "p15: prkdf id=");
if (cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf)
&& cdf_object_from_label (app, prkdf->label, &cdf))
continue; /* No matching certificate. */
if (!cdf->cert) /* Read and parse the certificate. */
readcert_by_cdf (app, cdf, NULL, NULL);
if (!cdf->cert)
continue; /* Unsupported or broken certificate. */
if (prkdf->is_ecc)
{
const char *oid;
const unsigned char *der;
size_t off, derlen, objlen, hdrlen;
int class, tag, constructed, ndef;
for (i=0; !(err = ksba_cert_get_extension
(cdf->cert, i, &oid, NULL, &off, &derlen)); i++)
if (!strcmp (oid, "1.3.6.1.4.1.11591.2.2.10") )
break;
if (!err && (der = ksba_cert_get_image (cdf->cert, NULL)))
{
der += off;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_OCTET_STRING || ndef))
err = gpg_error (GPG_ERR_INV_OBJ);
if (!err)
{
derlen = objlen;
if (opt.debug)
log_printhex (der, derlen, "p15: OpenPGP KDF parms:");
/* Store them if they match the known OpenPGP format. */
if (derlen == 4 && der[0] == 3 && der[1] == 1)
memcpy (prkdf->ecdh_kdf, der, 4);
}
}
err = 0;
}
if (ksba_cert_get_ext_key_usages (cdf->cert, &extusage))
continue; /* No extended key usage attribute. */
if (opt.debug)
log_debug ("p15: ExtKeyUsages: %s\n", extusage);
p = extusage;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
if ( *pend == 'C' ) /* Look only at critical usages. */
{
prkdf->extusage.valid = 1;
seen = 1;
if (!strcmp (p, oid_kp_codeSigning)
|| !strcmp (p, oid_kp_timeStamping)
|| !strcmp (p, oid_kp_ocspSigning)
|| !strcmp (p, oid_kp_ms_documentSigning)
|| !strcmp (p, oid_kp_ms_old_documentSigning))
prkdf->extusage.sign = 1;
else if (!strcmp (p, oid_kp_emailProtection))
prkdf->extusage.encr = 1;
else if (!strcmp (p, oid_kp_serverAuth)
|| !strcmp (p, oid_kp_clientAuth)
|| !strcmp (p, oid_kp_ms_smartcardLogon))
prkdf->extusage.auth = 1;
else if (!strcmp (p, oid_kp_anyExtendedKeyUsage))
{
prkdf->extusage.sign = 1;
prkdf->extusage.encr = 1;
prkdf->extusage.auth = 1;
}
else
seen = 0;
}
else
seen = 0;
/* Now check the gpg Usage. Here we don't care about
* critical or non-critical here. */
if (seen)
; /* No more need to look for other caps. */
else if (!strcmp (p, oid_kp_gpgUsageCert))
{
prkdf->gpgusage.cert = 1;
prkdf->gpgusage.any = 1;
app->app_local->any_gpgusage = 1;
}
else if (!strcmp (p, oid_kp_gpgUsageSign))
{
prkdf->gpgusage.sign = 1;
prkdf->gpgusage.any = 1;
app->app_local->any_gpgusage = 1;
}
else if (!strcmp (p, oid_kp_gpgUsageEncr))
{
prkdf->gpgusage.encr = 1;
prkdf->gpgusage.any = 1;
app->app_local->any_gpgusage = 1;
}
else if (!strcmp (p, oid_kp_gpgUsageAuth))
{
prkdf->gpgusage.auth = 1;
prkdf->gpgusage.any = 1;
app->app_local->any_gpgusage = 1;
}
/* Skip to next item. */
if ((p = strchr (pend, '\n')))
p++;
}
xfree (extusage);
}
/* See whether we can figure out something about the card. */
if (!app->app_local->card_product
&& app->app_local->manufacturer_id
&& !strcmp (app->app_local->manufacturer_id, "www.atos.net/cardos")
&& IS_CARDOS_5 (app))
{
/* This is a modern CARDOS card. */
flag = 0;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
{
if (prkdf->label && !strcmp (prkdf->label, "IdentityKey")
&& prkdf->key_reference_valid && prkdf->key_reference == 1
&& !prkdf->authid)
flag |= 1;
else if (prkdf->label && !strcmp (prkdf->label, "TransportKey")
&& prkdf->key_reference_valid && prkdf->key_reference==2
&& prkdf->authid)
flag |= 2;
}
if (flag == 3)
app->app_local->card_product = CARD_PRODUCT_RSCS;
}
if (!app->app_local->card_product
&& app->app_local->token_label
&& !strncmp (app->app_local->token_label, "D-TRUST Card V3", 15)
&& app->app_local->card_type == CARD_TYPE_CARDOS_50)
{
app->app_local->card_product = CARD_PRODUCT_DTRUST3;
}
if (!app->app_local->card_product
&& app->app_local->token_label
&& !strncmp (app->app_local->token_label, "D-TRUST Card 4.", 15)
&& app->app_local->card_type == CARD_TYPE_CARDOS_54)
{
app->app_local->card_product = CARD_PRODUCT_DTRUST4;
}
/* Now print the info about the PrKDF. */
if (opt.verbose)
{
int i;
unsigned char *atr;
size_t atrlen;
const char *cardstr;
for (prkdf = app->app_local->private_key_info; prkdf; prkdf = prkdf->next)
{
log_info ("p15: PrKDF %04hX: id=", app->app_local->odf.private_keys);
for (i=0; i < prkdf->objidlen; i++)
log_printf ("%02X", prkdf->objid[i]);
if (prkdf->label)
log_printf (" (%s)", prkdf->label);
log_info ("p15: path=");
for (i=0; i < prkdf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"",prkdf->path[i]);
if (prkdf->have_off)
log_printf ("[%lu/%lu]", prkdf->off, prkdf->len);
if (prkdf->authid)
{
log_printf (" authid=");
for (i=0; i < prkdf->authidlen; i++)
log_printf ("%02X", prkdf->authid[i]);
}
if (prkdf->key_reference_valid)
log_printf (" keyref=0x%02lX", prkdf->key_reference);
log_printf (" type=%s", prkdf->is_ecc? "ecc":"rsa");
if (prkdf->accessflags.any)
dump_keyaccess_flags (prkdf->accessflags);
dump_keyusage_flags (prkdf->usageflags);
if (prkdf->extusage.valid)
log_info ("p15: extusage=%s%s%s%s%s",
prkdf->extusage.sign? "sign":"",
(prkdf->extusage.sign
&& prkdf->extusage.encr)?",":"",
prkdf->extusage.encr? "encr":"",
((prkdf->extusage.sign || prkdf->extusage.encr)
&& prkdf->extusage.auth)?",":"",
prkdf->extusage.auth? "auth":"");
if (prkdf->gpgusage.any)
dump_gpgusage_flags (prkdf->gpgusage);
log_printf ("\n");
}
log_info ("p15: TokenInfo:\n");
if (app->app_local->serialno)
{
log_info ("p15: serialNumber .: ");
log_printhex (app->app_local->serialno, app->app_local->serialnolen,
"");
}
else if (APP_CARD(app)->serialno)
{
log_info ("p15: serialNumber .: ");
log_printhex (APP_CARD(app)->serialno, APP_CARD(app)->serialnolen,
"");
}
if (app->app_local->manufacturer_id)
log_info ("p15: manufacturerID: %s\n",
app->app_local->manufacturer_id);
if (app->app_local->card_product)
{
cardstr = cardproduct2str (app->app_local->card_product);
log_info ("p15: product ......: %d%s%s%s\n",
app->app_local->card_product,
*cardstr? " (":"", cardstr, *cardstr? ")":"");
}
if (app->app_local->token_label)
log_info ("p15: label ........: %s\n", app->app_local->token_label);
if (app->app_local->tokenflags)
{
log_info ("p15: tokenflags ...:");
print_tokeninfo_tokenflags (app->app_local->tokenflags,
app->app_local->tokenflagslen);
log_printf ("\n");
}
atr = apdu_get_atr (app_get_slot (app), &atrlen);
log_info ("p15: atr ..........: ");
if (!atr)
log_printf ("[error]\n");
else
{
log_printhex (atr, atrlen, "");
xfree (atr);
}
cardstr = cardtype2str (app->app_local->card_type);
log_info ("p15: cardtype .....: %d%s%s%s\n",
app->app_local->card_type,
*cardstr? " (":"", cardstr, *cardstr? ")":"");
}
return err;
}
/* Helper to do_learn_status: Send information about all certificates
listed in CERTINFO back. Use CERTTYPE as type of the
certificate. */
static gpg_error_t
send_certinfo (app_t app, ctrl_t ctrl, const char *certtype,
cdf_object_t certinfo)
{
for (; certinfo; certinfo = certinfo->next)
{
char *buf, *p;
const char *label;
char *labelbuf;
buf = xtrymalloc (9 + certinfo->objidlen*2 + 1);
if (!buf)
return gpg_error_from_syserror ();
p = stpcpy (buf, "P15");
if (app->app_local->home_df != DEFAULT_HOME_DF)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (certinfo->objid, certinfo->objidlen, p);
label = (certinfo->label && *certinfo->label)? certinfo->label : "-";
labelbuf = percent_data_escape (0, NULL, label, strlen (label));
if (!labelbuf)
{
xfree (buf);
return gpg_error_from_syserror ();
}
send_status_info (ctrl, "CERTINFO",
certtype, strlen (certtype),
buf, strlen (buf),
labelbuf, strlen (labelbuf),
NULL, (size_t)0);
xfree (buf);
xfree (labelbuf);
}
return 0;
}
/* Get the keygrip of the private key object PRKDF. On success the
* keygrip, the algo and the length are stored in the KEYGRIP,
* KEYALGO, and KEYNBITS fields of the PRKDF object. */
static gpg_error_t
keygrip_from_prkdf (app_t app, prkdf_object_t prkdf)
{
gpg_error_t err;
cdf_object_t cdf;
unsigned char *der;
size_t derlen;
ksba_cert_t cert;
gcry_sexp_t s_pkey = NULL;
/* Easy if we got a cached version. */
if (prkdf->keygrip_valid)
return 0;
xfree (prkdf->common_name);
prkdf->common_name = NULL;
xfree (prkdf->serial_number);
prkdf->serial_number = NULL;
/* We could have also checked whether a public key directory file
* and a matching public key for PRKDF is available. This would
* make extraction of the key faster. However, this way we don't
* have a way to look at extended key attributes to check gpgusage.
* FIXME: Add public key lookup if no certificate was found. */
/* Look for a matching certificate. A certificate matches if the id
* matches the one of the private key info. If none was found we
* also try to match on the label. */
err = cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = cdf_object_from_label (app, prkdf->label, &cdf);
if (!err && !cdf)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
goto leave;
err = readcert_by_cdf (app, cdf, &der, &derlen);
if (err)
goto leave;
err = ksba_cert_new (&cert);
if (!err)
err = ksba_cert_init_from_mem (cert, der, derlen);
xfree (der);
if (!err)
err = app_help_get_keygrip_string (cert, prkdf->keygrip, &s_pkey, NULL);
if (!err && !prkdf->gpgusage.any)
{
/* Try to get the CN and the SerialNumber from the certificate;
* we use a very simple approach here which should work in many
* cases. Eventually we should add a rfc-2253 parser into
* libksba to make it easier to parse such a string.
* We don't do this if this is marked as gpg key and thus
* has only a dummy certificate.
*
* First example string:
* "CN=Otto Schily,O=Miniluv,C=DE"
* Second example string:
* "2.5.4.5=#445452323030303236333531,2.5.4.4=#4B6F6368,"
* "2.5.4.42=#5765726E6572,CN=Werner Koch,OU=For testing"
* " purposes only!,O=Testorganisation,C=DE"
*/
char *dn = ksba_cert_get_subject (cert, 0);
if (dn)
{
char *p, *pend, *buf;
p = strstr (dn, "CN=");
if (p && (p==dn || p[-1] == ','))
{
p += 3;
if (!(pend = strchr (p, ',')))
pend = p + strlen (p);
if (pend && pend > p
&& (prkdf->common_name = xtrymalloc ((pend - p) + 1)))
{
memcpy (prkdf->common_name, p, pend-p);
prkdf->common_name[pend-p] = 0;
}
}
p = strstr (dn, "2.5.4.5=#"); /* OID of the SerialNumber */
if (p && (p==dn || p[-1] == ','))
{
p += 9;
if (!(pend = strchr (p, ',')))
pend = p + strlen (p);
if (pend && pend > p
&& (buf = xtrymalloc ((pend - p) + 1)))
{
memcpy (buf, p, pend-p);
buf[pend-p] = 0;
if (!hex2str (buf, buf, strlen (buf)+1, NULL))
xfree (buf); /* Invalid hex encoding. */
else
prkdf->serial_number = buf;
}
}
ksba_free (dn);
}
}
if (!err && !prkdf->keytime)
{
ksba_isotime_t isot;
time_t t;
ksba_cert_get_validity (cert, 0, isot);
t = isotime2epoch (isot);
prkdf->keytime = (t == (time_t)(-1))? 0 : (u32)t;
prkdf->have_keytime = 1;
}
if (!err && !prkdf->keyalgostr)
prkdf->keyalgostr = pubkey_algo_string (s_pkey, NULL);
ksba_cert_release (cert);
if (err)
goto leave;
prkdf->keyalgo = get_pk_algo_from_key (s_pkey);
if (!prkdf->keyalgo)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
prkdf->keynbits = gcry_pk_get_nbits (s_pkey);
if (!prkdf->keynbits)
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
prkdf->keygrip_valid = 1; /* Yeah, got everything. */
leave:
gcry_sexp_release (s_pkey);
return err;
}
/* Return a malloced keyref string for PRKDF. Returns NULL on
* malloc failure. */
static char *
keyref_from_prkdf (app_t app, prkdf_object_t prkdf)
{
char *buf, *p;
buf = xtrymalloc (4 + 5 + prkdf->objidlen*2 + 1);
if (!buf)
return NULL;
p = stpcpy (buf, "P15");
if (app->app_local->home_df != DEFAULT_HOME_DF)
{
snprintf (p, 6, "-%04X",
(unsigned int)(app->app_local->home_df & 0xffff));
p += 5;
}
p = stpcpy (p, ".");
bin2hex (prkdf->objid, prkdf->objidlen, p);
return buf;
}
static void
set_usage_string (char usage[5], prkdf_object_t prkdf)
{
size_t usagelen = 0;
if (prkdf->gpgusage.any)
{
if (prkdf->gpgusage.sign)
usage[usagelen++] = 's';
if (prkdf->gpgusage.cert)
usage[usagelen++] = 'c';
if (prkdf->gpgusage.encr)
usage[usagelen++] = 'e';
if (prkdf->gpgusage.auth)
usage[usagelen++] = 'a';
}
else
{
if ((prkdf->usageflags.sign
|| prkdf->usageflags.sign_recover
|| prkdf->usageflags.non_repudiation)
&& (!prkdf->extusage.valid
|| prkdf->extusage.sign))
usage[usagelen++] = 's';
if ((prkdf->usageflags.sign
|| prkdf->usageflags.sign_recover)
&& (!prkdf->extusage.valid || prkdf->extusage.sign))
usage[usagelen++] = 'c';
if ((prkdf->usageflags.decrypt
|| prkdf->usageflags.unwrap
|| prkdf->usageflags.derive)
&& (!prkdf->extusage.valid || prkdf->extusage.encr))
usage[usagelen++] = 'e';
if ((prkdf->usageflags.sign
|| prkdf->usageflags.sign_recover)
&& (!prkdf->extusage.valid || prkdf->extusage.auth))
usage[usagelen++] = 'a';
}
if (!usagelen)
usage[usagelen++] = '-';
usage[usagelen++] = 0;
}
/* Helper to do_learn_status: Send information about all known
keypairs back. FIXME: much code duplication from
send_certinfo(). */
static gpg_error_t
send_keypairinfo (app_t app, ctrl_t ctrl, prkdf_object_t prkdf)
{
gpg_error_t err;
for (; prkdf; prkdf = prkdf->next)
{
char *buf;
int j;
buf = keyref_from_prkdf (app, prkdf);
if (!buf)
return gpg_error_from_syserror ();
err = keygrip_from_prkdf (app, prkdf);
if (err)
{
log_error ("p15: error getting keygrip from ");
for (j=0; j < prkdf->pathlen; j++)
log_printf ("%s%04hX", j?"/":"", prkdf->path[j]);
log_printf (": %s\n", gpg_strerror (err));
}
else
{
char usage[5];
char keytime[20];
const char *algostr;
log_assert (strlen (prkdf->keygrip) == 40);
if (prkdf->keytime && prkdf->have_keytime)
snprintf (keytime, sizeof keytime, "%lu",
(unsigned long)prkdf->keytime);
else
strcpy (keytime, "-");
algostr = prkdf->keyalgostr;
set_usage_string (usage, prkdf);
send_status_info (ctrl, "KEYPAIRINFO",
prkdf->keygrip, 2*KEYGRIP_LEN,
buf, strlen (buf),
usage, strlen (usage),
keytime, strlen (keytime),
algostr, strlen (algostr?algostr:""),
NULL, (size_t)0);
}
xfree (buf);
}
return 0;
}
/* This is the handler for the LEARN command. Note that if
* APP_LEARN_FLAG_REREAD is set and this function returns an error,
* the caller must deinitialize this application. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err;
if (flags & APP_LEARN_FLAG_REREAD)
{
err = read_p15_info (app);
if (err)
return err;
}
if ((flags & APP_LEARN_FLAG_KEYPAIRINFO))
err = 0;
else
{
err = do_getattr (app, ctrl, "MANUFACTURER");
if (!err)
err = send_certinfo (app, ctrl, "100",
app->app_local->certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "101",
app->app_local->trusted_certificate_info);
if (!err)
err = send_certinfo (app, ctrl, "102",
app->app_local->useful_certificate_info);
}
if (!err)
err = send_keypairinfo (app, ctrl, app->app_local->private_key_info);
if (!err)
err = do_getattr (app, ctrl, "CHV-STATUS");
if (!err)
err = do_getattr (app, ctrl, "CHV-LABEL");
return err;
}
/* Read a certificate using the information in CDF and return the
* certificate in a newly malloced buffer R_CERT and its length
* R_CERTLEN. Also parses the certificate. R_CERT and R_CERTLEN may
* be NULL to do just the caching. */
static gpg_error_t
readcert_by_cdf (app_t app, cdf_object_t cdf,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
unsigned char *buffer = NULL;
const unsigned char *p, *save_p;
size_t buflen, n;
int class, tag, constructed, ndef;
size_t totobjlen, objlen, hdrlen;
int rootca;
int i;
if (r_cert)
*r_cert = NULL;
if (r_certlen)
*r_certlen = 0;
/* First check whether it has been cached. */
if (cdf->cert)
{
const unsigned char *image;
size_t imagelen;
if (!r_cert || !r_certlen)
return 0; /* Caller does not actually want the result. */
image = ksba_cert_get_image (cdf->cert, &imagelen);
if (!image)
{
log_error ("p15: ksba_cert_get_image failed\n");
return gpg_error (GPG_ERR_INTERNAL);
}
*r_cert = xtrymalloc (imagelen);
if (!*r_cert)
return gpg_error_from_syserror ();
memcpy (*r_cert, image, imagelen);
*r_certlen = imagelen;
return 0;
}
if (DBG_CARD)
{
log_info ("p15: Reading CDF: id=");
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
if (cdf->label)
log_printf (" (%s)", cdf->label);
log_info ("p15: path=");
for (i=0; i < cdf->pathlen; i++)
log_printf ("%s%04hX", i?"/":"", cdf->path[i]);
if (cdf->have_off)
log_printf ("[%lu/%lu]", cdf->off, cdf->len);
if (cdf->authid)
{
log_printf (" authid=");
for (i=0; i < cdf->authidlen; i++)
log_printf ("%02X", cdf->authid[i]);
}
log_printf ("\n");
}
/* Read the entire file. fixme: This could be optimized by first
reading the header to figure out how long the certificate
actually is. */
err = select_ef_by_path (app, cdf->path, cdf->pathlen);
if (err)
goto leave;
if (app->app_local->no_extended_mode || !cdf->len)
err = iso7816_read_binary_ext (app_get_slot (app), 0, cdf->off, 0,
&buffer, &buflen, NULL);
else
err = iso7816_read_binary_ext (app_get_slot (app), 1, cdf->off, cdf->len,
&buffer, &buflen, NULL);
if (!err && (!buflen || *buffer == 0xff))
err = gpg_error (GPG_ERR_NOT_FOUND);
if (err)
{
log_error ("p15: error reading certificate id=");
for (i=0; i < cdf->objidlen; i++)
log_printf ("%02X", cdf->objid[i]);
log_printf (" at ");
for (i=0; i < cdf->pathlen; i++)
log_printf ("%s%04hX", i? "/":"", cdf->path[i]);
log_printf (": %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether this is really a certificate. */
p = buffer;
n = buflen;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed)
rootca = 0;
else if ( class == CLASS_UNIVERSAL && tag == TAG_SET && constructed )
rootca = 1;
else
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
log_assert (totobjlen <= buflen);
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if (!rootca
&& class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !constructed)
{
/* The certificate seems to be contained in a userCertificate
container. Skip this and assume the following sequence is
the certificate. */
if (n < objlen)
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
p += objlen;
n -= objlen;
save_p = p;
err = parse_ber_header (&p, &n, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (err)
goto leave;
if ( !(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && constructed) )
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
totobjlen = objlen + hdrlen;
log_assert (save_p + totobjlen <= buffer + buflen);
memmove (buffer, save_p, totobjlen);
}
/* Try to parse and cache the certificate. */
err = ksba_cert_new (&cdf->cert);
if (!err)
{
err = ksba_cert_init_from_mem (cdf->cert, buffer, totobjlen);
if (!err) /* Call us to use the just cached cert object. */
err = readcert_by_cdf (app, cdf, r_cert, r_certlen);
if (err)
{
ksba_cert_release (cdf->cert);
cdf->cert = NULL;
}
}
if (err)
{
log_error ("p15: caching certificate failed: %s\n",
gpg_strerror (err));
/* We return the certificate anyway so that the caller has a
* chance to get an even unsupported or broken certificate. */
if (r_cert && r_certlen)
{
*r_cert = buffer;
buffer = NULL;
*r_certlen = totobjlen;
}
}
leave:
xfree (buffer);
return err;
}
/* Handler for the READCERT command.
Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer to be stored at R_CERT and its length at R_CERTLEN. A error
code will be returned on failure and R_CERT and R_CERTLEN will be
set to (NULL,0). */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
cdf_object_t cdf;
*r_cert = NULL;
*r_certlen = 0;
err = cdf_object_from_certid (app, certid, &cdf);
if (!err)
err = readcert_by_cdf (app, cdf, r_cert, r_certlen);
return err;
}
/* Sort helper for an array of authentication objects. */
static int
compare_aodf_objid (const void *arg_a, const void *arg_b)
{
const aodf_object_t a = *(const aodf_object_t *)arg_a;
const aodf_object_t b = *(const aodf_object_t *)arg_b;
int rc;
rc = memcmp (a->objid, b->objid,
a->objidlen < b->objidlen? a->objidlen : b->objidlen);
if (!rc)
{
if (a->objidlen < b->objidlen)
rc = -1;
else if (a->objidlen > b->objidlen)
rc = 1;
}
return rc;
}
static void
send_key_fpr_line (ctrl_t ctrl, int number, const unsigned char *fpr)
{
char buf[41];
char numbuf[25];
bin2hex (fpr, 20, buf);
if (number == -1)
*numbuf = 0; /* Don't print the key number */
else
snprintf (numbuf, sizeof numbuf, "%d", number);
send_status_info (ctrl, "KEY-FPR",
numbuf, (size_t)strlen(numbuf),
buf, (size_t)strlen (buf),
NULL, 0);
}
/* If possible emit a FPR-KEY status line for the private key object
* PRKDF using NUMBER as index. */
static void
send_key_fpr (app_t app, ctrl_t ctrl, prkdf_object_t prkdf, int number)
{
gpg_error_t err;
cdf_object_t cdf;
unsigned char *pk, *fixed_pk;
size_t pklen, fixed_pklen;
const unsigned char *m, *e, *q;
size_t mlen, elen, qlen;
unsigned char fpr20[20];
if (cdf_object_from_objid (app, prkdf->objidlen, prkdf->objid, &cdf)
&& cdf_object_from_label (app, prkdf->label, &cdf))
return;
if (!cdf->cert)
readcert_by_cdf (app, cdf, NULL, NULL);
if (!cdf->cert)
return;
if (!prkdf->have_keytime)
return;
pk = ksba_cert_get_public_key (cdf->cert);
if (!pk)
return;
pklen = gcry_sexp_canon_len (pk, 0, NULL, &err);
if (uncompress_ecc_q_in_canon_sexp (pk, pklen, &fixed_pk, &fixed_pklen))
{
xfree (pk);
return;
}
if (fixed_pk)
{
xfree (pk); pk = NULL;
pk = fixed_pk;
pklen = fixed_pklen;
}
switch (prkdf->keyalgo)
{
case GCRY_PK_RSA:
if (!get_rsa_pk_from_canon_sexp (pk, pklen,
&m, &mlen, &e, &elen)
&& !compute_openpgp_fpr_rsa (4,
prkdf->keytime,
m, mlen, e, elen,
fpr20, NULL))
send_key_fpr_line (ctrl, number, fpr20);
break;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH:
case GCRY_PK_EDDSA:
/* Note that NUMBER 2 indicates the encryption key. */
if (!get_ecc_q_from_canon_sexp (pk, pklen, &q, &qlen)
&& !compute_openpgp_fpr_ecc (4,
prkdf->keytime,
prkdf->keyalgostr,
number == 2,
q, qlen,
prkdf->ecdh_kdf, 4,
fpr20, NULL))
send_key_fpr_line (ctrl, number, fpr20);
break;
default: /* No Fingerprint for an unknown algo. */
break;
}
xfree (pk);
}
/* Implement the GETATTR command. This is similar to the LEARN
command but returns just one value via the status interface. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
prkdf_object_t prkdf;
if (!strcmp (name, "$AUTHKEYID")
|| !strcmp (name, "$ENCRKEYID")
|| !strcmp (name, "$SIGNKEYID"))
{
char *buf;
/* We return the ID of the first private key capable of the
* requested action. If any gpgusage flag has been set for the
* card we consult the gpgusage flags and not the regular usage
* flags.
*/
/* FIXME: This changed: Note that we do not yet return
* non_repudiation keys for $SIGNKEYID because our D-Trust
* testcard uses rsaPSS, which is not supported by gpgsm and not
* covered by the VS-NfD approval. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
{
if (app->app_local->any_gpgusage)
{
if ((name[1] == 'A' && prkdf->gpgusage.auth)
|| (name[1] == 'E' && prkdf->gpgusage.encr)
|| (name[1] == 'S' && prkdf->gpgusage.sign))
break;
}
else
{
if ((name[1] == 'A' && (prkdf->usageflags.sign
|| prkdf->usageflags.sign_recover))
|| (name[1] == 'E' && (prkdf->usageflags.decrypt
|| prkdf->usageflags.unwrap
|| prkdf->usageflags.derive))
|| (name[1] == 'S' && (prkdf->usageflags.sign
|| prkdf->usageflags.sign_recover)))
break;
}
}
if (prkdf)
{
buf = keyref_from_prkdf (app, prkdf);
if (!buf)
return gpg_error_from_syserror ();
send_status_info (ctrl, name, buf, strlen (buf), NULL, 0);
xfree (buf);
}
return 0;
}
else if (!strcmp (name, "$DISPSERIALNO"))
{
/* For certain cards we return special IDs. There is no
general rule for it so we need to decide case by case. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
/* The eID card has a card number printed on the front matter
which seems to be a good indication. */
unsigned char *buffer;
const unsigned char *p;
size_t buflen, n;
unsigned short path[] = { 0x3F00, 0xDF01, 0x4031 };
err = select_ef_by_path (app, path, DIM(path) );
if (!err)
err = iso7816_read_binary (app_get_slot (app), 0, 0,
&buffer, &buflen);
if (err)
{
log_error ("p15: error accessing EF(ID): %s\n",
gpg_strerror (err));
return err;
}
p = find_tlv (buffer, buflen, 1, &n);
if (p && n == 12)
{
char tmp[12+2+1];
memcpy (tmp, p, 3);
tmp[3] = '-';
memcpy (tmp+4, p+3, 7);
tmp[11] = '-';
memcpy (tmp+12, p+10, 2);
tmp[14] = 0;
send_status_info (ctrl, name, tmp, strlen (tmp), NULL, 0);
xfree (buffer);
return 0;
}
xfree (buffer);
}
else
{
char *sn;
/* We use the first private key object which has a serial
* number set. If none was found, we parse the first
* object and see whether this has then a serial number. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
if (prkdf->serial_number)
break;
if (!prkdf && app->app_local->private_key_info)
{
prkdf = app->app_local->private_key_info;
keygrip_from_prkdf (app, prkdf);
if (!prkdf->serial_number)
prkdf = NULL;
}
sn = get_dispserialno (app, prkdf);
/* Unless there is a bogus S/N in the cert, or the product
* has a different strategy for the display-s/n, we should
* have a suitable one from the cert now. */
if (sn)
{
err = send_status_printf (ctrl, name, "%s", sn);
xfree (sn);
return err;
}
}
/* No abbreviated serial number. */
}
else if (!strcmp (name, "MANUFACTURER"))
{
if (app->app_local->manufacturer_id
&& !strchr (app->app_local->manufacturer_id, '[')
&& app->app_local->card_product)
return send_status_printf (ctrl, "MANUFACTURER", "0 %s [%s]",
app->app_local->manufacturer_id,
cardproduct2str (app->app_local->card_product));
else if (app->app_local->manufacturer_id)
return send_status_printf (ctrl, "MANUFACTURER", "0 %s",
app->app_local->manufacturer_id);
else
return 0;
}
else if (!strcmp (name, "CHV-STATUS") || !strcmp (name, "CHV-LABEL"))
{
int is_label = (name[4] == 'L');
aodf_object_t aodf;
aodf_object_t aodfarray[16];
int naodf = 0;
membuf_t mb;
char *p;
int i;
/* Put the AODFs into an array for easier sorting. Note that we
- * handle onl the first 16 encountrer which should be more than
+ * handle only the first 16 encountrer which should be more than
* enough. */
for (aodf = app->app_local->auth_object_info;
aodf && naodf < DIM(aodfarray); aodf = aodf->next)
if (aodf->objidlen && aodf->pin_reference_valid)
aodfarray[naodf++] = aodf;
qsort (aodfarray, naodf, sizeof *aodfarray, compare_aodf_objid);
init_membuf (&mb, 256);
for (i = 0; i < naodf; i++)
{
/* int j; */
/* log_debug ("p15: AODF[%d] pinref=%lu id=", */
/* i, aodfarray[i]->pin_reference); */
/* for (j=0; j < aodfarray[i]->objidlen; j++) */
/* log_printf ("%02X", aodfarray[i]->objid[j]); */
/* Note that there is no need to percent escape the label
* because all white space have been replaced by '_'. */
if (is_label)
put_membuf_printf (&mb, "%s%s", i? " ":"",
(aodfarray[i]->label
&& *aodfarray[i]->label)?
aodfarray[i]->label:"X");
else
put_membuf_printf
(&mb, "%s%d", i? " ":"",
iso7816_verify_status (app_get_slot (app),
aodfarray[i]->pin_reference));
}
put_membuf( &mb, "", 1);
p = get_membuf (&mb, NULL);
if (!p)
return gpg_error_from_syserror ();
err = send_status_direct (ctrl, is_label? "CHV-LABEL":"CHV-STATUS", p);
xfree (p);
return err;
}
else if (!strcmp (name, "KEY-LABEL"))
{
/* Send KEY-LABEL lines for all private key objects. */
const char *label;
char *idbuf, *labelbuf;
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
{
idbuf = keyref_from_prkdf (app, prkdf);
if (!idbuf)
return gpg_error_from_syserror ();
label = (prkdf->label && *prkdf->label)? prkdf->label : "-";
labelbuf = percent_data_escape (0, NULL, label, strlen (label));
if (!labelbuf)
{
xfree (idbuf);
return gpg_error_from_syserror ();
}
send_status_info (ctrl, name,
idbuf, strlen (idbuf),
labelbuf, strlen(labelbuf),
NULL, 0);
xfree (idbuf);
xfree (labelbuf);
}
return 0;
}
else if (!strcmp (name, "KEY-FPR"))
{
/* Send KEY-FPR for the two openpgp keys. */
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
{
if (app->app_local->any_gpgusage)
{
if (prkdf->gpgusage.sign)
break;
}
else
{
if (prkdf->usageflags.sign || prkdf->usageflags.sign_recover)
break;
}
}
if (prkdf)
send_key_fpr (app, ctrl, prkdf, 1);
for (prkdf = app->app_local->private_key_info; prkdf;
prkdf = prkdf->next)
{
if (app->app_local->any_gpgusage)
{
if (prkdf->gpgusage.encr)
break;
}
else
{
if (prkdf->usageflags.decrypt || prkdf->usageflags.unwrap
|| prkdf->usageflags.derive)
break;
}
}
if (prkdf)
send_key_fpr (app, ctrl, prkdf, 2);
return 0;
}
return gpg_error (GPG_ERR_INV_NAME);
}
/* Micardo cards require special treatment. This is a helper for the
crypto functions to manage the security environment. We expect that
the key file has already been selected. FID is the one of the
selected key. */
static gpg_error_t
micardo_mse (app_t app, unsigned short fid)
{
gpg_error_t err;
int recno;
unsigned short refdata = 0;
int se_num;
unsigned char msebuf[10];
/* Read the KeyD file containing extra information on keys. */
err = iso7816_select_file (app_get_slot (app), 0x0013, 0);
if (err)
{
log_error ("p15: error reading EF_keyD: %s\n", gpg_strerror (err));
return err;
}
for (recno = 1, se_num = -1; ; recno++)
{
unsigned char *buffer;
size_t buflen;
size_t n, nn;
const unsigned char *p, *pp;
err = iso7816_read_record (app_get_slot (app), recno, 1, 0,
&buffer, &buflen);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
break; /* ready */
if (err)
{
log_error ("p15: error reading EF_keyD record: %s\n",
gpg_strerror (err));
return err;
}
if (opt.verbose)
{
log_info (buffer, buflen, "p15: keyD record: ");
log_printhex (buffer, buflen, "");
}
p = find_tlv (buffer, buflen, 0x83, &n);
if (p && n == 4 && ((p[2]<<8)|p[3]) == fid)
{
refdata = ((p[0]<<8)|p[1]);
/* Locate the SE DO and the there included sec env number. */
p = find_tlv (buffer, buflen, 0x7b, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x80, &nn);
if (pp && nn == 1)
{
se_num = *pp;
xfree (buffer);
break; /* found. */
}
}
}
xfree (buffer);
}
if (se_num == -1)
{
log_error ("p15: CRT for keyfile %04hX not found\n", fid);
return gpg_error (GPG_ERR_NOT_FOUND);
}
/* Restore the security environment to SE_NUM if needed */
if (se_num)
{
err = iso7816_manage_security_env (app_get_slot (app),
0xf3, se_num, NULL, 0);
if (err)
{
log_error ("p15: restoring SE to %d failed: %s\n",
se_num, gpg_strerror (err));
return err;
}
}
/* Set the DST reference data. */
msebuf[0] = 0x83;
msebuf[1] = 0x03;
msebuf[2] = 0x80;
msebuf[3] = (refdata >> 8);
msebuf[4] = refdata;
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xb6, msebuf, 5);
if (err)
{
log_error ("p15: setting SE to reference file %04hX failed: %s\n",
refdata, gpg_strerror (err));
return err;
}
return 0;
}
/* Prepare the verification of the PIN for the key PRKDF by checking
* the AODF and selecting the key file. KEYREF is used for error
* messages. AODF may be NULL if no verification needs to be done. */
static gpg_error_t
prepare_verify_pin (app_t app, const char *keyref,
prkdf_object_t prkdf, aodf_object_t aodf)
{
gpg_error_t err;
int i;
if (aodf)
{
if (opt.verbose)
{
log_info ("p15: using AODF %04hX id=", aodf->fid);
for (i=0; i < aodf->objidlen; i++)
log_printf ("%02X", aodf->objid[i]);
log_printf ("\n");
}
if (aodf->authid && opt.verbose)
log_info ("p15: PIN is controlled by another authentication token\n");
if (aodf->pinflags.integrity_protected
|| aodf->pinflags.confidentiality_protected)
{
log_error ("p15: PIN verification requires"
" unsupported protection method\n");
return gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (!aodf->stored_length && aodf->pinflags.needs_padding)
{
log_error ("p15: PIN verification requires"
" padding but no length known\n");
return gpg_error (GPG_ERR_INV_CARD);
}
}
if (app->app_local->card_product == CARD_PRODUCT_DTRUST3)
{
/* According to our protocol analysis we need to select a
* special AID here. Before that the master file needs to be
* selected. (RID A000000167 is assigned to IBM) */
static char const dtrust_aid[] =
{ 0xA0, 0x00, 0x00, 0x01, 0x67, 0x45, 0x53, 0x49, 0x47, 0x4E };
err = iso7816_select_mf (app_get_slot (app));
if (!err)
err = iso7816_select_application (app_get_slot (app),
dtrust_aid, sizeof dtrust_aid, 0);
if (err)
log_error ("p15: error selecting D-TRUST's AID for key %s: %s\n",
keyref, gpg_strerror (err));
}
else if (app->app_local->card_product == CARD_PRODUCT_CVISION)
{
/* According to our protocol analysis we need to select the
* PKCS#15 AID here. The traces actually show that this is done
* two times but that looks more like a bug. Before that the
* master file needs to be selected. */
err = iso7816_select_mf (app_get_slot (app));
if (!err)
err = iso7816_select_application (app_get_slot (app),
pkcs15_aid, sizeof pkcs15_aid, 0);
if (err)
log_error ("p15: error selecting PKCS#15 AID for key %s: %s\n",
keyref, gpg_strerror (err));
}
else if (prkdf)
{
/* Standard case: Select the key file. Note that this may
* change the security environment thus we need to do it before
* PIN verification. */
err = select_ef_by_path (app, prkdf->path, prkdf->pathlen);
if (err)
log_error ("p15: error selecting file for key %s: %s\n",
keyref, gpg_strerror (err));
}
else
{
log_info ("p15: skipping EF selection for auth object '%s'\n", keyref);
err = 0;
}
return err;
}
static int
any_control_or_space (const char *string)
{
const unsigned char *s;
for (s = string; *s; s++)
if (*s <= 0x20 || *s >= 0x7f)
return 1;
return 0;
}
static int
any_control_or_space_mem (const void *buffer, size_t buflen)
{
const unsigned char *s;
for (s = buffer; buflen; s++, buflen--)
if (*s <= 0x20 || *s >= 0x7f)
return 1;
return 0;
}
/* Return a malloced serial number to be shown to the user. PRKDF is
* used to get it from a certificate; PRKDF may be NULL. */
static char *
get_dispserialno (app_t app, prkdf_object_t prkdf)
{
char *serial;
const unsigned char *s;
int i;
size_t n;
/* We prefer the SerialNumber RDN from the Subject-DN but we don't
* use it if it features a percent sign (special character in pin
* prompts) or has any control character. For some cards we use a
* different strategy. */
if (app->app_local->card_product == CARD_PRODUCT_RSCS)
{
/* We use only the right 8 hex digits. */
serial = app_get_serialno (app);
if (serial && (n=strlen (serial)) > 8)
memmove (serial, serial + n - 8, 9);
}
else if (app->app_local->card_product == CARD_PRODUCT_NEXUS
&& APP_CARD(app)->serialno && APP_CARD(app)->serialnolen == 4+9
&& !memcmp (APP_CARD(app)->serialno, "\xff\x00\x00\xff", 4)
&& !any_control_or_space_mem (APP_CARD(app)->serialno + 4, 9))
{
/* Sample: ff0000ff354830313232363537 -> "5H01 2265 7" */
serial = xtrymalloc (9+2+1);
if (serial)
{
s = APP_CARD(app)->serialno + 4;
for (i=0; i < 4; i++)
serial[i] = *s++;
serial[i++] = ' ';
for (; i < 9; i++)
serial[i] = *s++;
serial[i++] = ' ';
serial[i++] = *s;
serial[i] = 0;
}
}
else if (prkdf && prkdf->serial_number && *prkdf->serial_number
&& !strchr (prkdf->serial_number, '%')
&& !any_control_or_space (prkdf->serial_number))
{
serial = xtrystrdup (prkdf->serial_number);
}
else
{
serial = app_get_serialno (app);
}
return serial;
}
/* Return an allocated string to be used as prompt. PRKDF may be
* NULL. Returns NULL on malloc error. */
static char *
make_pin_prompt (app_t app, int remaining, const char *firstline,
prkdf_object_t prkdf)
{
char *serial, *tmpbuf, *result;
const char *holder = NULL;
serial = get_dispserialno (app, prkdf);
if (app->app_local->card_product == CARD_PRODUCT_GENUA)
{
/* The label of the first non SO-PIN is used for the holder. */
aodf_object_t aodf;
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->auth_type == AUTH_TYPE_PIN
&& !aodf->pinflags.so_pin
&& aodf->label)
{
holder = aodf->label;
break;
}
}
if (holder)
;
else if (prkdf && prkdf->common_name)
holder = prkdf->common_name;
else if (app->app_local->token_label)
holder = app->app_local->token_label;
else
holder = "";
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
holder,
"");
xfree (serial);
if (!result)
return NULL; /* Out of core. */
/* Append a "remaining attempts" info if needed. */
if (remaining != -1 && remaining < 3)
{
char *rembuf;
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (rembuf)
{
tmpbuf = strconcat (firstline, "%0A%0A", result,
"%0A%0A", rembuf, NULL);
xfree (rembuf);
}
else
tmpbuf = NULL;
xfree (result);
result = tmpbuf;
}
else
{
tmpbuf = strconcat (firstline, "%0A%0A", result, NULL);
xfree (result);
result = tmpbuf;
}
return result;
}
/* Given the private key object PRKDF and its authentication object
* AODF ask for the PIN and verify that PIN. If AODF is NULL, no
* authentication is done. */
static gpg_error_t
verify_pin (app_t app,
gpg_error_t (*pincb)(void*, const char *, char **), void *pincb_arg,
prkdf_object_t prkdf, aodf_object_t aodf)
{
gpg_error_t err;
char *pinvalue;
size_t pinvaluelen;
const char *label;
const char *errstr;
const char *s;
int remaining;
unsigned int min_length;
int pin_reference;
int verified = 0;
int i;
if (!aodf)
return 0;
pin_reference = aodf->pin_reference_valid? aodf->pin_reference : 0;
if (IS_CARDOS_5 (app))
{
/* We know that this card supports a verify status check. Note
* that in contrast to PIV cards ISO7816_VERIFY_NOT_NEEDED is
* not supported. We also don't use the pin_verified cache
* status because that is not as reliable as to ask the card
* about its state. */
if (prkdf) /* Clear the cache which we don't use. */
prkdf->pin_verified = 0;
remaining = iso7816_verify_status (app_get_slot (app), pin_reference);
if (remaining == ISO7816_VERIFY_NOT_NEEDED)
{
verified = 1;
remaining = -1;
}
else if (remaining < 0)
remaining = -1; /* We don't care about the concrete error. */
else if (remaining < 3)
log_info ("p15: PIN has %d attempts left\n", remaining);
}
else
remaining = -1; /* Unknown. */
/* Check whether we already verified it. */
if (prkdf && (prkdf->pin_verified || verified))
return 0; /* Already done. */
if (prkdf
&& prkdf->usageflags.non_repudiation
&& (app->app_local->card_type == CARD_TYPE_BELPIC
|| app->app_local->card_product == CARD_PRODUCT_DTRUST3
|| app->app_local->card_product == CARD_PRODUCT_DTRUST4))
label = _("||Please enter the PIN for the key to create "
"qualified signatures.");
else if (aodf->pinflags.so_pin)
label = _("|A|Please enter the Admin PIN");
else if (aodf->pinflags.unblocking_pin)
label = _("|P|Please enter the PIN Unblocking Code (PUK) "
"for the standard keys.");
else
label = _("||Please enter the PIN for the standard keys.");
{
char *prompt = make_pin_prompt (app, remaining, label, prkdf);
if (!prompt)
err = gpg_error_from_syserror ();
else
err = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
}
if (err)
{
log_info ("p15: PIN callback returned error: %s\n", gpg_strerror (err));
return err;
}
/* We might need to cope with UTF8 things here. Not sure how
min_length etc. are exactly defined, for now we take them as a
plain octet count. For RSCS we enforce 6 despite that some cards
give 4 has min. length. */
min_length = aodf->min_length;
if (app->app_local->card_product == CARD_PRODUCT_RSCS && min_length < 6)
min_length = 6;
if (strlen (pinvalue) < min_length)
{
log_error ("p15: PIN is too short; minimum length is %u\n", min_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->stored_length && strlen (pinvalue) > aodf->stored_length)
{
/* This would otherwise truncate the PIN silently. */
log_error ("p15: PIN is too large; maximum length is %lu\n",
aodf->stored_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
else if (aodf->max_length_valid && strlen (pinvalue) > aodf->max_length)
{
log_error ("p15: PIN is too large; maximum length is %lu\n",
aodf->max_length);
err = gpg_error (GPG_ERR_BAD_PIN);
}
if (err)
{
xfree (pinvalue);
return err;
}
errstr = NULL;
err = 0;
switch (aodf->pintype)
{
case PIN_TYPE_BCD:
for (s=pinvalue; digitp (s); s++)
;
if (*s)
{
errstr = "Non-numeric digits found in PIN";
err = gpg_error (GPG_ERR_BAD_PIN);
}
break;
case PIN_TYPE_ASCII_NUMERIC:
for (s=pinvalue; *s && !(*s & 0x80); s++)
;
if (*s)
{
errstr = "Non-ascii characters found in PIN";
err = gpg_error (GPG_ERR_BAD_PIN);
}
break;
case PIN_TYPE_UTF8:
break;
case PIN_TYPE_HALF_NIBBLE_BCD:
errstr = "PIN type Half-Nibble-BCD is not supported";
break;
case PIN_TYPE_ISO9564_1:
errstr = "PIN type ISO9564-1 is not supported";
break;
default:
errstr = "Unknown PIN type";
break;
}
if (errstr)
{
log_error ("p15: can't verify PIN: %s\n", errstr);
xfree (pinvalue);
return err? err : gpg_error (GPG_ERR_BAD_PIN_METHOD);
}
if (aodf->pintype == PIN_TYPE_BCD )
{
char *paddedpin;
int ndigits;
for (ndigits=0, s=pinvalue; *s; ndigits++, s++)
;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
i = 0;
paddedpin[i++] = 0x20 | (ndigits & 0x0f);
for (s=pinvalue; i < aodf->stored_length && *s && s[1]; s = s+2 )
paddedpin[i++] = (((*s - '0') << 4) | ((s[1] - '0') & 0x0f));
if (i < aodf->stored_length && *s)
paddedpin[i++] = (((*s - '0') << 4)
|((aodf->pad_char_valid?aodf->pad_char:0)&0x0f));
if (aodf->pinflags.needs_padding)
{
while (i < aodf->stored_length)
paddedpin[i++] = aodf->pad_char_valid? aodf->pad_char : 0;
}
xfree (pinvalue);
pinvalue = paddedpin;
pinvaluelen = i;
}
else if (aodf->pinflags.needs_padding)
{
char *paddedpin;
paddedpin = xtrymalloc (aodf->stored_length+1);
if (!paddedpin)
{
err = gpg_error_from_syserror ();
xfree (pinvalue);
return err;
}
for (i=0, s=pinvalue; i < aodf->stored_length && *s; i++, s++)
paddedpin[i] = *s;
/* Not sure what padding char to use if none has been set.
For now we use 0x00; maybe a space would be better. */
for (; i < aodf->stored_length; i++)
paddedpin[i] = aodf->pad_char_valid? aodf->pad_char : 0;
paddedpin[i] = 0;
pinvaluelen = i;
xfree (pinvalue);
pinvalue = paddedpin;
}
else
pinvaluelen = strlen (pinvalue);
/* log_printhex (pinvalue, pinvaluelen, */
/* "about to verify with ref %lu pin:", pin_reference); */
err = iso7816_verify (app_get_slot (app), pin_reference,
pinvalue, pinvaluelen);
xfree (pinvalue);
if (err)
{
log_error ("p15: PIN verification failed: %s\n", gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info ("p15: PIN verification succeeded\n");
if (prkdf)
prkdf->pin_verified = 1;
return 0;
}
/* Handler for the PKSIGN command.
Create the signature and return the allocated result in OUTDATA.
If a PIN is required, the PINCB will be used to ask for the PIN;
that callback should return the PIN in an allocated buffer and
store that as the 3rd argument. */
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
prkdf_object_t prkdf; /* The private key object. */
aodf_object_t aodf; /* The associated authentication object. */
int mse_done = 0; /* Set to true if the MSE has been done. */
unsigned int digestlen; /* Length of the hash. */
int exmode, le_value;
unsigned char oidbuf[64];
size_t oidbuflen;
size_t n;
int i;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
(void)ctrl;
if (!keyidstr || !*keyidstr || !indatalen)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.sign
|| prkdf->usageflags.sign_recover
|| prkdf->usageflags.non_repudiation
|| prkdf->gpgusage.cert
|| prkdf->gpgusage.sign
|| prkdf->gpgusage.auth ))
{
log_error ("p15: key %s may not be used for signing\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
if (!prkdf->authid)
{
log_error ("p15: no authentication object defined for %s\n", keyidstr);
/* fixme: we might want to go ahead and do without PIN
verification. */
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
}
/* Find the authentication object to this private key object. */
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->objidlen == prkdf->authidlen
&& !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen))
break;
if (!aodf)
log_info ("p15: no authentication for %s needed\n", keyidstr);
/* We need some more info about the key - get the keygrip to
* populate these fields. */
err = keygrip_from_prkdf (app, prkdf);
if (err)
{
log_error ("p15: keygrip_from_prkdf failed: %s\n", gpg_strerror (err));
return err;
}
digestlen = gcry_md_get_algo_dlen (hashalgo);
/* We handle ECC separately from RSA so that we do not need to touch
* working code. In particular we prepare the input data before the
* verify and a possible MSE. */
if (prkdf->is_ecc)
{
if (digestlen != 32 && digestlen != 48 && digestlen != 64)
{
log_error ("p15: ECC signing not possible: dlen=%u\n", digestlen);
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto leave;
}
if (indatalen == digestlen)
; /* Already prepared. */
else if (indatalen > digestlen)
{
/* Assume a PKCS#1 prefix and remove it. */
oidbuflen = sizeof oidbuf;
err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen);
if (err)
{
log_error ("p15: no OID for hash algo %d\n", hashalgo);
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
if (indatalen != oidbuflen + digestlen
|| memcmp (indata, oidbuf, oidbuflen))
{
log_error ("p15: input data too long for ECC: len=%zu\n",
indatalen);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
indata = (const char*)indata + oidbuflen;
indatalen -= oidbuflen;
}
else
{
log_error ("p15: input data too short for ECC: len=%zu\n",
indatalen);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
}
else /* Prepare RSA input. */
{
unsigned int framelen;
unsigned char *frame;
framelen = (prkdf->keynbits+7) / 8;
if (!framelen)
{
log_error ("p15: key length unknown"
" - can't prepare PKCS#v1.5 frame\n");
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
oidbuflen = sizeof oidbuf;
if (!hashalgo)
{
/* We assume that indata already has the required
* digestinfo; thus merely prepend the padding below. */
}
else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen)))
{
log_debug ("p15: no OID for hash algo %d\n", hashalgo);
goto leave;
}
else
{
if (indatalen == digestlen)
{
/* Plain hash in INDATA; prepend the digestinfo. */
indata_buffer = xtrymalloc (oidbuflen + indatalen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer, oidbuf, oidbuflen);
memcpy (indata_buffer+oidbuflen, indata, indatalen);
indata = indata_buffer;
indatalen = oidbuflen + indatalen;
}
else if (indatalen == oidbuflen + digestlen
&& !memcmp (indata, oidbuf, oidbuflen))
; /* We already got the correct prefix. */
else
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("p15: bad input for signing with RSA and hash %d\n",
hashalgo);
goto leave;
}
}
/* Now prepend the pkcs#v1.5 padding. We require at least 8
* byte of padding and 3 extra bytes for the prefix and the
* delimiting nul. */
if (!indatalen || indatalen + 8 + 4 > framelen)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("p15: input does not fit into a %u bit PKCS#v1.5 frame\n",
8*framelen);
goto leave;
}
frame = xtrymalloc (framelen);
if (!frame)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (app->app_local->card_type == CARD_TYPE_BELPIC
|| app->app_local->card_product == CARD_PRODUCT_NEXUS
|| app->app_local->card_product == CARD_PRODUCT_DTRUST4)
{
/* The default for these cards is to use a plain hash. We
* assume that due to the used certificate the correct hash
* algo is used. */
memcpy (frame, indata, indatalen);
framelen = indatalen;
}
else if (hashalgo && IS_STARCOS_3 (app)
&& app->app_local->card_product != CARD_PRODUCT_CVISION)
{
/* For Starcos we need the plain hash w/o the prefix. */
/* Note: This has never been tested because the cvision
* sample cards seem not to work this way. */
if (indatalen != oidbuflen + digestlen
|| memcmp (indata, oidbuf, oidbuflen))
{
log_error ("p15: non-matching input data for Starcos:"
" hash=%d len=%zu\n", hashalgo, indatalen);
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
framelen = indatalen - oidbuflen;
memcpy (frame, (const char*)indata + oidbuflen, framelen);
}
else
{
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* Block type. */
i = framelen - indatalen - 3 ;
memset (frame+n, 0xff, i);
n += i;
frame[n++] = 0; /* Delimiter. */
memcpy (frame+n, indata, indatalen);
n += indatalen;
log_assert (n == framelen);
}
/* And now put it into the indata_buffer. */
xfree (indata_buffer);
indata_buffer = frame;
indata = indata_buffer;
indatalen = framelen;
}
/* Prepare PIN verification. This is split so that we can do
* MSE operation for some task after having selected the key file but
* before sending the verify APDU. */
err = prepare_verify_pin (app, keyidstr, prkdf, aodf);
if (err)
return err;
/* Due to the fact that the non-repudiation signature on a BELPIC
card requires a verify immediately before the DSO we set the
MSE before we do the verification. Other cards might also allow
this but I don't want to break anything, thus we do it only
for the BELPIC card here.
FIXME: see comment above about these cards. */
if (app->app_local->card_type == CARD_TYPE_BELPIC)
{
unsigned char mse[5];
mse[0] = 4; /* Length of the template. */
mse[1] = 0x80; /* Algorithm reference tag. */
if (hashalgo == MD_USER_TLS_MD5SHA1)
mse[2] = 0x01; /* Let card do pkcs#1 0xFF padding. */
else
mse[2] = 0x02; /* RSASSA-PKCS1-v1.5 using SHA1. */
mse[3] = 0x84; /* Private key reference tag. */
mse[4] = prkdf->key_reference_valid? prkdf->key_reference : 0x82;
err = iso7816_manage_security_env (app_get_slot (app),
0x41, 0xB6,
mse, sizeof mse);
mse_done = 1;
}
if (err)
{
log_error ("p15: MSE failed: %s\n", gpg_strerror (err));
goto leave;
}
/* Now that we have all the information available run the actual PIN
* verification.*/
err = verify_pin (app, pincb, pincb_arg, prkdf, aodf);
if (err)
return err;
/* Manage security environment needs to be tweaked for certain cards. */
if (mse_done)
err = 0;
else if (app->app_local->card_type == CARD_TYPE_TCOS)
{
/* TCOS creates signatures always using the local key 0. MSE
may not be used. */
}
else if (app->app_local->card_type == CARD_TYPE_MICARDO)
{
if (!prkdf->pathlen)
err = gpg_error (GPG_ERR_BUG);
else
err = micardo_mse (app, prkdf->path[prkdf->pathlen-1]);
}
else if (app->app_local->card_product == CARD_PRODUCT_DTRUST4)
{
if (prkdf->is_ecc)
{
err = iso7816_manage_security_env (app_get_slot (app),
0xf3, 0x21, NULL, 0);
}
else
{
/* The D-TRUST Card 4.x doesn't support setting a security
* environment, at least as specified in the specs. Instead a
* predefined security environment has to be loaded depending on the
* cipher and message digest used. The spec states SE-ID 0x25 for
* SHA256, 0x26 for SHA384 and 0x27 for SHA512, when using PKCS#1
* padding. But this matters only if the message digest is computed
* on the card. When providing the digest info and a pre-calculated
* hash, all security environments yield the same result. Thus we
* choose 0x25.
*
* Note: For PSS signatures, different values apply. */
err = iso7816_manage_security_env (app_get_slot (app),
0xf3, 0x25, NULL, 0);
}
}
else if (app->app_local->card_product == CARD_PRODUCT_CVISION)
{
/* I can't make the Starcos 3.2 work the correct way, so let's
* make them work in the same way the cryptovision product seems
* to do it: Use the plain RSA decryption instead. */
unsigned char mse[9];
i = 0;
mse[i++] = 0x84; /* Key reference. */
mse[i++] = 1;
mse[i++] = prkdf->key_reference;
mse[i++] = 0x89; /* Algorithm reference (BCD encoded). */
mse[i++] = 2;
mse[i++] = 0x11; /* RSA no padding (1 1 3 0). */
mse[i++] = 0x30;
log_assert (i <= DIM(mse));
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8,
mse, i);
}
else if (prkdf->key_reference_valid && IS_STARCOS_3 (app))
{
unsigned char mse[9];
i = 0;
if (prkdf->is_ecc)
{
log_info ("Note: ECC is not yet implemented for Starcos 3 cards\n");
err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
}
else
{
err = 0;
mse[i++] = 0x84; /* Key reference. */
mse[i++] = 1;
mse[i++] = prkdf->key_reference;
mse[i++] = 0x89; /* Algorithm reference (BCD encoded). */
mse[i++] = 3;
mse[i++] = 0x13; /* RSA PKCS#1 (standard) (1 3 2 3). */
mse[i++] = 0x23;
switch (hashalgo)
{
case GCRY_MD_SHA1: mse[i++] = 0x10; break;
case GCRY_MD_RMD160: mse[i++] = 0x20; break;
case GCRY_MD_SHA256: mse[i++] = 0x30; break;
case GCRY_MD_SHA384: mse[i++] = 0x40; break;
case GCRY_MD_SHA512: mse[i++] = 0x50; break;
case GCRY_MD_SHA224: mse[i++] = 0x60; break;
default: err = gpg_error (GPG_ERR_DIGEST_ALGO); break;
}
log_assert (i <= DIM(mse));
if (!err)
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB6,
mse, i);
}
}
else if (prkdf->key_reference_valid)
{
unsigned char mse[3];
mse[0] = 0x84; /* Select asym. key. */
mse[1] = 1;
mse[2] = prkdf->key_reference;
err = iso7816_manage_security_env (app_get_slot (app),
0x41, 0xB6,
mse, sizeof mse);
}
if (err)
{
log_error ("p15: MSE failed: %s\n", gpg_strerror (err));
goto leave;
}
if (prkdf->keyalgo == GCRY_PK_RSA && prkdf->keynbits >= 2048)
{
exmode = 1;
le_value = prkdf->keynbits / 8;
}
else
{
exmode = 0;
le_value = 0;
}
if (app->app_local->card_product == CARD_PRODUCT_CVISION)
err = iso7816_decipher (app_get_slot (app),
exmode, indata, indatalen,
le_value, 0, outdata, outdatalen);
else
err = iso7816_compute_ds (app_get_slot (app),
exmode, indata, indatalen,
le_value, outdata, outdatalen);
leave:
xfree (indata_buffer);
return err;
}
/* Handler for the PKAUTH command.
This is basically the same as the PKSIGN command but we first check
that the requested key is suitable for authentication; that is, it
must match the criteria used for the attribute $AUTHKEYID. See
do_sign for calling conventions; there is no HASHALGO, though. */
static gpg_error_t
do_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
prkdf_object_t prkdf;
int algo;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover
|| prkdf->gpgusage.auth))
{
log_error ("p15: key %s may not be used for authentication\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
algo = indatalen == 36? MD_USER_TLS_MD5SHA1 : GCRY_MD_SHA1;
return do_sign (app, ctrl, keyidstr, algo, pincb, pincb_arg,
indata, indatalen, outdata, outdatalen);
}
/* Handler for the PKDECRYPT command. Decrypt the data in INDATA and
* return the allocated result in OUTDATA. If a PIN is required the
* PINCB will be used to ask for the PIN; it should return the PIN in
* an allocated buffer and put it into PIN. */
static gpg_error_t
do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
prkdf_object_t prkdf; /* The private key object. */
aodf_object_t aodf; /* The associated authentication object. */
int exmode, le_value, padind;
int i;
(void)ctrl;
(void)r_info;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
if (!indatalen || !indata || !outdatalen || !outdata)
return gpg_error (GPG_ERR_INV_ARG);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err)
return err;
if (!(prkdf->usageflags.decrypt
|| prkdf->usageflags.unwrap
|| prkdf->usageflags.derive
|| prkdf->gpgusage.encr ))
{
log_error ("p15: key %s may not be used for decryption\n", keyidstr);
return gpg_error (GPG_ERR_WRONG_KEY_USAGE);
}
/* Find the authentication object to this private key object. */
if (!prkdf->authid)
{
log_info ("p15: no authentication object defined for %s\n", keyidstr);
aodf = NULL;
}
else
{
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->objidlen == prkdf->authidlen
&& !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen))
break;
if (!aodf)
log_info ("p15: no authentication for %s needed\n", keyidstr);
}
/* We need some more info about the key - get the keygrip to
* populate these fields. */
err = keygrip_from_prkdf (app, prkdf);
if (err)
{
log_error ("p15: keygrip_from_prkdf failed: %s\n", gpg_strerror (err));
return err;
}
/* Verify the PIN. */
err = prepare_verify_pin (app, keyidstr, prkdf, aodf);
if (!err)
err = verify_pin (app, pincb, pincb_arg, prkdf, aodf);
if (err)
return err;
if (prkdf->is_ecc && IS_CARDOS_5(app))
{
err = iso7816_manage_security_env (app_get_slot (app), 0xF3, 0x01,
NULL, 0);
if (err)
{
log_error ("p15: MSE failed: %s\n", gpg_strerror (err));
return err;
}
}
/* The next is guess work for CardOS. */
if (app->app_local->card_product == CARD_PRODUCT_DTRUST3)
{
/* From analyzing an USB trace of a Windows signing application
* we see that the SE is simply reset to 0x14. It seems to be
* sufficient to do this for decryption; signing still works
* with the standard code despite that our trace showed that
* there the SE is restored to 0x09. Note that the special
* D-Trust AID is in any case select by prepare_verify_pin.
*
* Hey, D-Trust please hand over the specs so that you can
* actually sell your cards and we can properly implement it;
* other vendors understand this and do not demand ridiculous
* paper work or complicated procedures to get samples. */
err = iso7816_manage_security_env (app_get_slot (app),
0xF3, 0x14, NULL, 0);
}
else if (app->app_local->card_product == CARD_PRODUCT_DTRUST4)
{
if (prkdf->is_ecc)
{
err = iso7816_manage_security_env (app_get_slot (app),
0xF3, 0x39, NULL, 0);
}
else
{
/* SE-ID 0x31 is for PKCS#1 padded cryptograms. For OAEP encryption
* schemes, different values apply. */
err = iso7816_manage_security_env (app_get_slot (app),
0xF3, 0x31, NULL, 0);
}
}
else if (prkdf->key_reference_valid && IS_STARCOS_3 (app))
{
unsigned char mse[9];
i = 0;
if (prkdf->is_ecc)
{
log_info ("Note: ECC is not yet implemented for Starcos 3 cards\n");
err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
}
else
{
mse[i++] = 0x84; /* Key reference. */
mse[i++] = 1;
mse[i++] = prkdf->key_reference;
mse[i++] = 0x89; /* Algorithm reference (BCD encoded). */
mse[i++] = 2;
mse[i++] = 0x11; /* RSA no padding (1 1 3 0). */
mse[i++] = 0x30;
log_assert (i <= DIM(mse));
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8,
mse, i);
}
}
else if (prkdf->key_reference_valid)
{
unsigned char mse[9];
/* Note: This works with CardOS but the D-Trust card has the
* problem that the next created signature would be broken. */
i = 0;
if (!prkdf->is_ecc)
{
mse[i++] = 0x80; /* Algorithm reference. */
mse[i++] = 1;
mse[i++] = 0x0a; /* RSA, no padding. */
}
mse[i++] = 0x84; /* Key reference. */
mse[i++] = 1;
mse[i++] = prkdf->key_reference;
if (prkdf->is_ecc && IS_CARDOS_5(app))
{
mse[i++] = 0x95; /* ???. */
mse[i++] = 1;
mse[i++] = 0x40;
}
log_assert (i <= DIM(mse));
err = iso7816_manage_security_env (app_get_slot (app), 0x41, 0xB8,
mse, i);
}
/* Check for MSE error. */
if (err)
{
log_error ("p15: MSE failed: %s\n", gpg_strerror (err));
return err;
}
exmode = le_value = 0;
padind = 0;
if (prkdf->keyalgo == GCRY_PK_RSA && prkdf->keynbits >= 2048)
{
exmode = 1; /* Extended length w/o a limit. */
le_value = prkdf->keynbits / 8;
}
if (app->app_local->card_product == CARD_PRODUCT_DTRUST3
|| app->app_local->card_product == CARD_PRODUCT_DTRUST4)
padind = 0x81;
if (prkdf->is_ecc && IS_CARDOS_5(app))
{
if ((indatalen & 1) && *(const char *)indata == 0x04)
{
/* Strip indicator byte. */
indatalen--;
indata = (const char *)indata + 1;
}
err = iso7816_pso_csv (app_get_slot (app), exmode,
indata, indatalen,
le_value,
outdata, outdatalen);
}
else
{
err = iso7816_decipher (app_get_slot (app), exmode,
indata, indatalen,
le_value, padind,
outdata, outdatalen);
}
return err;
}
/* Perform a simple verify operation for the PIN specified by
* KEYIDSTR. Note that we require a key reference which is then used
* to select the authentication object. Return GPG_ERR_NO_PIN if a
* PIN is not required for using the private key KEYIDSTR. */
static gpg_error_t
do_check_pin (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
prkdf_object_t prkdf; /* The private key object. */
aodf_object_t aodf; /* The associated authentication object. */
(void)ctrl;
if (!keyidstr || !*keyidstr)
return gpg_error (GPG_ERR_INV_VALUE);
err = prkdf_object_from_keyidstr (app, keyidstr, &prkdf);
if (err
&& gpg_err_code (err) != GPG_ERR_INV_ID
&& gpg_err_code (err) != GPG_ERR_NOT_FOUND)
return err;
if (err) /* Not found or invalid - assume it is the label. */
{
prkdf = NULL;
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->label && !ascii_strcasecmp (aodf->label, keyidstr))
break;
if (!aodf)
- return err; /* Re-use the original error code. */
+ return err; /* Reuse the original error code. */
}
else /* Find the authentication object to this private key object. */
{
if (!prkdf->authid)
{
log_error ("p15: no authentication object defined for %s\n",
keyidstr);
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
}
for (aodf = app->app_local->auth_object_info; aodf; aodf = aodf->next)
if (aodf->objidlen == prkdf->authidlen
&& !memcmp (aodf->objid, prkdf->authid, prkdf->authidlen))
break;
if (!aodf) /* None found. */
return gpg_error (GPG_ERR_NO_PIN);
}
err = prepare_verify_pin (app, keyidstr, prkdf, aodf);
if (!err)
err = verify_pin (app, pincb, pincb_arg, prkdf, aodf);
return err;
}
/* Process the various keygrip based info requests. */
static gpg_error_t
do_with_keygrip (app_t app, ctrl_t ctrl, int action,
const char *want_keygripstr, int capability)
{
gpg_error_t err;
char *serialno = NULL;
int as_data = 0;
prkdf_object_t prkdf;
/* First a quick check for valid parameters. */
switch (action)
{
case KEYGRIP_ACTION_LOOKUP:
if (!want_keygripstr)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
break;
case KEYGRIP_ACTION_SEND_DATA:
as_data = 1;
break;
case KEYGRIP_ACTION_WRITE_STATUS:
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* Allocate the s/n string if needed. */
if (action != KEYGRIP_ACTION_LOOKUP)
{
serialno = app_get_serialno (app);
if (!serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
for (prkdf = app->app_local->private_key_info;
prkdf; prkdf = prkdf->next)
{
if (keygrip_from_prkdf (app, prkdf))
continue;
if (action == KEYGRIP_ACTION_LOOKUP)
{
if (!strcmp (prkdf->keygrip, want_keygripstr))
{
err = 0; /* Found */
goto leave;
}
}
else if (!want_keygripstr || !strcmp (prkdf->keygrip, want_keygripstr))
{
char *keyref;
char usage[5];
if (capability == GCRY_PK_USAGE_SIGN)
{
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover
|| prkdf->usageflags.non_repudiation))
continue;
}
else if (capability == GCRY_PK_USAGE_ENCR)
{
if (!(prkdf->usageflags.decrypt || prkdf->usageflags.unwrap
|| prkdf->usageflags.derive))
continue;
}
else if (capability == GCRY_PK_USAGE_AUTH)
{
if (!(prkdf->usageflags.sign || prkdf->usageflags.sign_recover))
continue;
}
keyref = keyref_from_prkdf (app, prkdf);
if (!keyref)
{
err = gpg_error_from_syserror ();
goto leave;
}
set_usage_string (usage, prkdf);
send_keyinfo (ctrl, as_data, prkdf->keygrip, serialno, keyref, usage);
xfree (keyref);
if (want_keygripstr)
{
err = 0; /* Found */
goto leave;
}
}
}
/* Return an error so that the dispatcher keeps on looping over the
* other applications. For clarity we use a different error code
* when listing all keys. Note that in lookup mode WANT_KEYGRIPSTR
* is not NULL. */
if (!want_keygripstr)
err = gpg_error (GPG_ERR_TRUE);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
leave:
xfree (serialno);
return err;
}
/* Assume that EF(DIR) has been selected. Read its content and figure
out the home EF of pkcs#15. Return that home DF or 0 if not found
and the value at the address of BELPIC indicates whether it was
found by the belpic aid. */
static unsigned short
read_home_df (int slot, int *r_belpic)
{
gpg_error_t err;
unsigned char *buffer;
const unsigned char *p, *pp;
size_t buflen, n, nn;
unsigned short result = 0;
*r_belpic = 0;
err = iso7816_read_binary (slot, 0, 0, &buffer, &buflen);
if (err)
{
log_error ("p15: error reading EF(DIR): %s\n", gpg_strerror (err));
return 0;
}
/* FIXME: We need to scan all records. */
p = find_tlv (buffer, buflen, 0x61, &n);
if (p && n)
{
pp = find_tlv (p, n, 0x4f, &nn);
if (pp && ((nn == sizeof pkcs15_aid && !memcmp (pp, pkcs15_aid, nn))
|| (*r_belpic = (nn == sizeof pkcs15be_aid
&& !memcmp (pp, pkcs15be_aid, nn)))))
{
pp = find_tlv (p, n, 0x50, &nn);
if (pp && opt.verbose)
log_info ("p15: application label from EF(DIR) is '%.*s'\n",
(int)nn, pp);
pp = find_tlv (p, n, 0x51, &nn);
if (pp && nn == 4 && *pp == 0x3f && !pp[1])
{
result = ((pp[2] << 8) | pp[3]);
if (opt.verbose)
log_info ("p15: application directory is 0x%04hX\n", result);
}
}
}
xfree (buffer);
return result;
}
/*
Select the PKCS#15 application on the card in SLOT.
*/
gpg_error_t
app_select_p15 (app_t app)
{
int slot = app_get_slot (app);
int rc;
unsigned short def_home_df = 0;
card_type_t card_type = CARD_TYPE_UNKNOWN;
int direct = 0;
int is_belpic = 0;
unsigned char *fci = NULL;
size_t fcilen;
rc = iso7816_select_application_ext (slot, pkcs15_aid, sizeof pkcs15_aid, 1,
&fci, &fcilen);
if (rc)
{
/* D-TRUST Card 4.x uses a different AID. */
rc = iso7816_select_application_ext (slot, pkcs15dtrust4_aid,
sizeof pkcs15dtrust4_aid, 1,
&fci, &fcilen);
}
if (rc)
{ /* Not found: Try to locate it from 2F00. We use direct path
selection here because it seems that the Belgian eID card
does only allow for that. Many other cards supports this
selection method too. Note, that we don't use
select_application above for the Belgian card - the call
works but it seems that it does not switch to the correct DF.
Using the 2f02 just works. */
unsigned short path[1] = { 0x2f00 };
rc = iso7816_select_path (slot, path, 1, 0);
if (!rc)
{
direct = 1;
def_home_df = read_home_df (slot, &is_belpic);
if (def_home_df)
{
path[0] = def_home_df;
rc = iso7816_select_path (slot, path, 1, 0);
}
}
}
if (rc)
{ /* Still not found: Try the default DF. */
def_home_df = DEFAULT_HOME_DF;
rc = iso7816_select_file (slot, def_home_df, 1);
}
if (!rc)
{
/* Determine the type of the card. The general case is to look
it up from the ATR table. For the Belgian eID card we know
it instantly from the AID. */
if (is_belpic)
{
card_type = CARD_TYPE_BELPIC;
}
else
{
unsigned char *atr;
size_t atrlen;
int i;
atr = apdu_get_atr (app_get_slot (app), &atrlen);
if (!atr)
rc = gpg_error (GPG_ERR_INV_CARD);
else
{
for (i=0; card_atr_list[i].atrlen; i++)
if (card_atr_list[i].atrlen == atrlen
&& !memcmp (card_atr_list[i].atr, atr, atrlen))
{
card_type = card_atr_list[i].type;
break;
}
xfree (atr);
}
}
}
if (!rc)
{
app->apptype = APPTYPE_P15;
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
rc = gpg_error_from_syserror ();
goto leave;
}
/* Set the home DF from the FCI returned by the select. */
if (!def_home_df && fci)
{
const unsigned char *s;
size_t n;
s = find_tlv (fci, fcilen, 0x83, &n);
if (s && n == 2)
def_home_df = buf16_to_ushort (s);
else
{
if (fcilen)
log_printhex (fci, fcilen, "fci:");
log_info ("p15: select did not return the DF - using default\n");
def_home_df = DEFAULT_HOME_DF;
}
}
app->app_local->home_df = def_home_df;
/* Store the card type. FIXME: We might want to put this into
the common APP structure. */
app->app_local->card_type = card_type;
app->app_local->card_product = CARD_PRODUCT_UNKNOWN;
/* Store whether we may and should use direct path selection. */
switch (card_type)
{
case CARD_TYPE_CARDOS_50:
case CARD_TYPE_CARDOS_53:
case CARD_TYPE_CARDOS_54:
direct = 1;
break;
case CARD_TYPE_AET:
case CARD_TYPE_STARCOS_32:
app->app_local->no_extended_mode = 1;
break;
default:
/* Use whatever has been determined above. */
break;
}
app->app_local->direct_path_selection = direct;
/* Read basic information and thus check whether this is a real
card. */
rc = read_p15_info (app);
if (rc)
goto leave;
/* Special serial number munging. We need to check for a German
prototype card right here because we need to access to
EF(TokenInfo). We mark such a serial number by the using a
prefix of FF0100. */
if (APP_CARD(app)->serialnolen == 12
&& !memcmp (APP_CARD(app)->serialno,
"\xD2\x76\0\0\0\0\0\0\0\0\0\0", 12))
{
/* This is a German card with a silly serial number. Try to get
the serial number from the EF(TokenInfo). . */
unsigned char *p;
/* FIXME: actually get it from EF(TokenInfo). */
p = xtrymalloc (3 + APP_CARD(app)->serialnolen);
if (!p)
rc = gpg_error (gpg_err_code_from_errno (errno));
else
{
memcpy (p, "\xff\x01", 3);
memcpy (p+3, APP_CARD(app)->serialno, APP_CARD(app)->serialnolen);
APP_CARD(app)->serialnolen += 3;
xfree (APP_CARD(app)->serialno);
APP_CARD(app)->serialno = p;
}
}
app->fnc.deinit = do_deinit;
app->fnc.prep_reselect = NULL;
app->fnc.reselect = NULL;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.getattr = do_getattr;
app->fnc.setattr = NULL;
app->fnc.genkey = NULL;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = NULL;
app->fnc.check_pin = do_check_pin;
app->fnc.with_keygrip = do_with_keygrip;
leave:
if (rc)
do_deinit (app);
}
xfree (fci);
return rc;
}
diff --git a/scd/app-piv.c b/scd/app-piv.c
index d7f9acca3..8b9c9a608 100644
--- a/scd/app-piv.c
+++ b/scd/app-piv.c
@@ -1,3798 +1,3798 @@
/* app-piv.c - The OpenPGP card application.
* Copyright (C) 2019, 2020, 2024 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* Some notes:
* - Specs for PIV are at http://dx.doi.org/10.6028/NIST.SP.800-73-4
* - https://developers.yubico.com/PIV/Introduction/PIV_attestation.html
*
* - Access control matrix:
* | Action | 9B | PIN | PUK | |
* |--------------+-----+-----+-----+------------------------------|
* | Generate key | yes | | | |
* | Change 9B | yes | | | |
* | Change retry | yes | yes | | Yubikey only |
* | Import key | yes | | | |
* | Import cert | yes | | | |
* | Change CHUID | yes | | | |
* | Reset card | | | | PIN and PUK in blocked state |
* | Verify PIN | | yes | | |
* | Sign data | | yes | | |
* | Decrypt data | | yes | | |
* | Change PIN | | yes | | |
* | Change PUK | | | yes | |
* | Unblock PIN | | | yes | New PIN required |
* |---------------------------------------------------------------|
* (9B indicates the 24 byte PIV Card Application Administration Key)
*
* - When generating a key we store the created public key in the
* corresponding data object, so that gpg and gpgsm are able to get
* the public key, create a certificate and store that then in that
* data object. That is not standard compliant but due to the use
* of other tags, it should not harm. See do_genkey for the actual
* used tag structure.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <time.h>
#include "scdaemon.h"
#include "../common/util.h"
#include "../common/i18n.h"
#include "iso7816.h"
#include "../common/tlv.h"
#include "../common/host2net.h"
#include "apdu.h" /* We use apdu_send_direct. */
#define PIV_ALGORITHM_3DES_ECB_0 0x00
#define PIV_ALGORITHM_2DES_ECB 0x01
#define PIV_ALGORITHM_2DES_CBC 0x02
#define PIV_ALGORITHM_3DES_ECB 0x03
#define PIV_ALGORITHM_3DES_CBC 0x04
#define PIV_ALGORITHM_RSA 0x07
#define PIV_ALGORITHM_AES128_ECB 0x08
#define PIV_ALGORITHM_AES128_CBC 0x09
#define PIV_ALGORITHM_AES192_ECB 0x0A
#define PIV_ALGORITHM_AES192_CBC 0x0B
#define PIV_ALGORITHM_AES256_ECB 0x0C
#define PIV_ALGORITHM_AES256_CBC 0x0D
#define PIV_ALGORITHM_ECC_P256 0x11
#define PIV_ALGORITHM_ECC_P384 0x14
/* The AID for PIV. */
static char const piv_aid[] = { 0xA0, 0x00, 0x00, 0x03, 0x08, /* RID=NIST */
0x00, 0x00, 0x10, 0x00 /* PIX=PIV */ };
/* A table describing the DOs of a PIV card. */
struct data_object_s
{
unsigned int tag;
unsigned int mandatory:1;
unsigned int acr_contact:2; /* 0=always, 1=VCI, 2=PIN, 3=PINorOCC */
unsigned int acr_contactless:2; /* 0=always, 1=VCI, 2=VCIandPIN,
3=VCIand(PINorOCC) */
unsigned int dont_cache:1; /* Data item will not be cached. */
unsigned int flush_on_error:1; /* Flush cached item on error. */
unsigned int keypair:1; /* Has a public key for a keypair. */
const char keyref[3]; /* The key reference. */
const char *oidsuffix; /* Suffix of the OID. */
const char *usage; /* Usage string for a keypair or NULL. */
const char *desc; /* Description of the DO. */
};
typedef struct data_object_s *data_object_t;
static struct data_object_s data_objects[] = {
{ 0x5FC107, 1, 0,1, 0,0, 0, "", "1.219.0", NULL,
"Card Capability Container"},
{ 0x5FC102, 1, 0,0, 0,0, 0, "", "2.48.0", NULL,
"Cardholder Unique Id" },
{ 0x5FC105, 1, 0,1, 0,0, 1, "9A", "2.1.1", "a",
"Cert PIV Authentication" },
{ 0x5FC103, 1, 2,2, 0,0, 0, "", "2.96.16", NULL,
"Cardholder Fingerprints" },
{ 0x5FC106, 1, 0,1, 0,0, 0, "", "2.144.0", NULL,
"Security Object" },
{ 0x5FC108, 1, 2,2, 0,0, 0, "", "2.96.48", NULL,
"Cardholder Facial Image" },
{ 0x5FC101, 1, 0,0, 0,0, 1, "9E", "2.5.0", "a",
"Cert Card Authentication"},
{ 0x5FC10A, 0, 0,1, 0,0, 1, "9C", "2.1.0", "sc",
"Cert Digital Signature" },
{ 0x5FC10B, 0, 0,1, 0,0, 1, "9D", "2.1.2", "e",
"Cert Key Management" },
{ 0x5FC109, 0, 3,3, 0,0, 0, "", "2.48.1", NULL,
"Printed Information" },
{ 0x7E, 0, 0,0, 0,0, 0, "", "2.96.80", NULL,
"Discovery Object" },
{ 0x5FC10C, 0, 0,1, 0,0, 0, "", "2.96.96", NULL,
"Key History Object" },
{ 0x5FC10D, 0, 0,1, 0,0, 1, "82", "2.16.1", "e",
"Retired Cert Key Mgm 1" },
{ 0x5FC10E, 0, 0,1, 0,0, 1, "83", "2.16.2", "e",
"Retired Cert Key Mgm 2" },
{ 0x5FC10F, 0, 0,1, 0,0, 1, "84", "2.16.3", "e",
"Retired Cert Key Mgm 3" },
{ 0x5FC110, 0, 0,1, 0,0, 1, "85", "2.16.4", "e",
"Retired Cert Key Mgm 4" },
{ 0x5FC111, 0, 0,1, 0,0, 1, "86", "2.16.5", "e",
"Retired Cert Key Mgm 5" },
{ 0x5FC112, 0, 0,1, 0,0, 1, "87", "2.16.6", "e",
"Retired Cert Key Mgm 6" },
{ 0x5FC113, 0, 0,1, 0,0, 1, "88", "2.16.7", "e",
"Retired Cert Key Mgm 7" },
{ 0x5FC114, 0, 0,1, 0,0, 1, "89", "2.16.8", "e",
"Retired Cert Key Mgm 8" },
{ 0x5FC115, 0, 0,1, 0,0, 1, "8A", "2.16.9", "e",
"Retired Cert Key Mgm 9" },
{ 0x5FC116, 0, 0,1, 0,0, 1, "8B", "2.16.10", "e",
"Retired Cert Key Mgm 10" },
{ 0x5FC117, 0, 0,1, 0,0, 1, "8C", "2.16.11", "e",
"Retired Cert Key Mgm 11" },
{ 0x5FC118, 0, 0,1, 0,0, 1, "8D", "2.16.12", "e",
"Retired Cert Key Mgm 12" },
{ 0x5FC119, 0, 0,1, 0,0, 1, "8E", "2.16.13", "e",
"Retired Cert Key Mgm 13" },
{ 0x5FC11A, 0, 0,1, 0,0, 1, "8F", "2.16.14", "e",
"Retired Cert Key Mgm 14" },
{ 0x5FC11B, 0, 0,1, 0,0, 1, "90", "2.16.15", "e",
"Retired Cert Key Mgm 15" },
{ 0x5FC11C, 0, 0,1, 0,0, 1, "91", "2.16.16", "e",
"Retired Cert Key Mgm 16" },
{ 0x5FC11D, 0, 0,1, 0,0, 1, "92", "2.16.17", "e",
"Retired Cert Key Mgm 17" },
{ 0x5FC11E, 0, 0,1, 0,0, 1, "93", "2.16.18", "e",
"Retired Cert Key Mgm 18" },
{ 0x5FC11F, 0, 0,1, 0,0, 1, "94", "2.16.19", "e",
"Retired Cert Key Mgm 19" },
{ 0x5FC120, 0, 0,1, 0,0, 1, "95", "2.16.20", "e",
"Retired Cert Key Mgm 20" },
{ 0x5FC121, 0, 2,2, 0,0, 0, "", "2.16.21", NULL,
"Cardholder Iris Images" },
{ 0x7F61, 0, 0,0, 0,0, 0, "", "2.16.22", NULL,
"BIT Group Template" },
{ 0x5FC122, 0, 0,0, 0,0, 0, "", "2.16.23", NULL,
"SM Cert Signer" },
{ 0x5FC123, 0, 3,3, 0,0, 0, "", "2.16.24", NULL,
"Pairing Code Ref Data" },
{ 0 }
/* Other key reference values without a data object:
* "00" Global PIN (not cleared by application switching)
* "04" PIV Secure Messaging Key
* "80" PIV Application PIN
* "81" PIN Unblocking Key
* "96" Primary Finger OCC
* "97" Secondary Finger OCC
* "98" Pairing Code
* "9B" PIV Card Application Administration Key
*
* Yubikey specific data objects:
* "F9" Attestation key (preloaded can be replaced)
*/
};
/* One cache item for DOs. */
struct cache_s {
struct cache_s *next;
int tag;
size_t length;
unsigned char data[1];
};
/* Object with application specific data. */
struct app_local_s {
/* A linked list with cached DOs. */
struct cache_s *cache;
/* Various flags. */
struct
{
unsigned int yubikey:1; /* This is on a Yubikey. */
} flags;
/* Keep track on whether we cache a certain PIN so that we get it
* from the cache only if we know we cached it. This inhibits the
* use of the same cache entry for a card plugged in and out without
* gpg-agent having noticed that due to a bug. */
struct
{
unsigned int maybe_00:1;
unsigned int maybe_80:1;
unsigned int maybe_81:1;
unsigned int maybe_96:1;
unsigned int maybe_97:1;
unsigned int maybe_98:1;
unsigned int maybe_9B:1;
} pincache;
};
/***** Local prototypes *****/
static gpg_error_t get_keygrip_by_tag (app_t app, unsigned int tag,
char **r_keygripstr, int *got_cert);
static gpg_error_t genkey_parse_rsa (const unsigned char *data, size_t datalen,
gcry_sexp_t *r_sexp);
static gpg_error_t genkey_parse_ecc (const unsigned char *data, size_t datalen,
int mechanism, gcry_sexp_t *r_sexp);
/* Deconstructor. */
static void
do_deinit (app_t app)
{
if (app && app->app_local)
{
struct cache_s *c, *c2;
for (c = app->app_local->cache; c; c = c2)
{
c2 = c->next;
xfree (c);
}
xfree (app->app_local);
app->app_local = NULL;
}
}
/* Wrapper around iso7816_get_data which first tries to get the data
* from the cache. With GET_IMMEDIATE passed as true, the cache is
* bypassed. The tag-53 container is also removed. */
static gpg_error_t
get_cached_data (app_t app, int tag,
unsigned char **result, size_t *resultlen,
int get_immediate)
{
gpg_error_t err;
int i;
unsigned char *p;
const unsigned char *s;
size_t len, n;
struct cache_s *c;
*result = NULL;
*resultlen = 0;
if (!get_immediate)
{
for (c=app->app_local->cache; c; c = c->next)
if (c->tag == tag)
{
if(c->length)
{
p = xtrymalloc (c->length);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, c->data, c->length);
*result = p;
}
*resultlen = c->length;
return 0;
}
}
err = iso7816_get_data_odd (app_get_slot (app), 0, tag, &p, &len);
if (err)
return err;
/* Unless the Discovery Object or the BIT Group Template is
* requested, remove the outer container.
* (SP800-73.4 Part 2, section 3.1.2) */
if (tag == 0x7E || tag == 0x7F61)
;
else if (len && *p == 0x53 && (s = find_tlv (p, len, 0x53, &n)))
{
memmove (p, s, n);
len = n;
}
if (len)
*result = p;
*resultlen = len;
/* Check whether we should cache this object. */
if (get_immediate)
return 0;
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].tag == tag)
{
if (data_objects[i].dont_cache)
return 0;
break;
}
/* Okay, cache it. */
for (c=app->app_local->cache; c; c = c->next)
log_assert (c->tag != tag);
c = xtrymalloc (sizeof *c + len);
if (c)
{
if (len)
memcpy (c->data, p, len);
else
xfree (p);
c->length = len;
c->tag = tag;
c->next = app->app_local->cache;
app->app_local->cache = c;
}
return 0;
}
/* Remove data object described by TAG from the cache. If TAG is 0
- * all cache iterms are flushed. */
+ * all cache items are flushed. */
static void
flush_cached_data (app_t app, int tag)
{
struct cache_s *c, *cprev;
for (c=app->app_local->cache, cprev=NULL; c; cprev=c, c = c->next)
if (c->tag == tag || !tag)
{
if (cprev)
cprev->next = c->next;
else
app->app_local->cache = c->next;
xfree (c);
for (c=app->app_local->cache; c ; c = c->next)
{
log_assert (c->tag != tag); /* Oops: duplicated entry. */
}
return;
}
}
/* Get the DO identified by TAG from the card in SLOT and return a
* buffer with its content in RESULT and NBYTES. The return value is
* NULL if not found or a pointer which must be used to release the
* buffer holding value. */
static void *
get_one_do (app_t app, int tag, unsigned char **result, size_t *nbytes,
int *r_err)
{
gpg_error_t err;
int i;
unsigned char *buffer;
size_t buflen;
unsigned char *value;
size_t valuelen;
gpg_error_t dummyerr;
if (!r_err)
r_err = &dummyerr;
*result = NULL;
*nbytes = 0;
*r_err = 0;
for (i=0; data_objects[i].tag && data_objects[i].tag != tag; i++)
;
value = NULL;
err = gpg_error (GPG_ERR_ENOENT);
if (!value) /* Not in a constructed DO, try simple. */
{
err = get_cached_data (app, tag, &buffer, &buflen,
data_objects[i].dont_cache);
if (!err)
{
value = buffer;
valuelen = buflen;
}
}
if (!err)
{
*nbytes = valuelen;
*result = value;
return buffer;
}
*r_err = err;
return NULL;
}
static void
dump_all_do (int slot)
{
gpg_error_t err;
int i;
unsigned char *buffer;
size_t buflen;
for (i=0; data_objects[i].tag; i++)
{
/* We don't try extended length APDU because such large DO would
be pretty useless in a log file. */
err = iso7816_get_data_odd (slot, 0, data_objects[i].tag,
&buffer, &buflen);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_ENOENT
&& !data_objects[i].mandatory)
;
else
log_info ("DO '%s' not available: %s\n",
data_objects[i].desc, gpg_strerror (err));
}
else
{
if (data_objects[i].tag == 0x5FC109)
log_info ("DO '%s': '%.*s'\n", data_objects[i].desc,
(int)buflen, buffer);
else
{
log_info ("DO '%s': ", data_objects[i].desc);
if (buflen > 16 && opt.verbose < 2)
{
log_printhex (buffer, 16, NULL);
log_printf ("[...]\n");
}
else
log_printhex (buffer, buflen, "");
}
}
xfree (buffer); buffer = NULL;
}
}
/* Create a TLV tag and value and store it at BUFFER. Return the
* length of tag and length. A LENGTH greater than 65535 is
* truncated. TAG must be less or equal to 2^16. If BUFFER is NULL,
* only the required length is computed. */
static size_t
add_tlv (unsigned char *buffer, unsigned int tag, size_t length)
{
if (length > 0xffff)
length = 0xffff;
if (buffer)
{
unsigned char *p = buffer;
if (tag > 0xff)
*p++ = tag >> 8;
*p++ = tag;
if (length < 128)
*p++ = length;
else if (length < 256)
{
*p++ = 0x81;
*p++ = length;
}
else
{
*p++ = 0x82;
*p++ = length >> 8;
*p++ = length;
}
return p - buffer;
}
else
{
size_t n = 0;
if (tag > 0xff)
n++;
n++;
if (length < 128)
n++;
else if (length < 256)
n += 2;
else
n += 3;
return n;
}
}
/* Function to build a list of TLV and return the result in a malloced
* buffer. The varargs are tuples of (int,size_t,void) each with the
* tag, the length and the actual data. A (0,0,NULL) tuple terminates
* the list. Up to 10 tuples are supported. If SECMEM is true the
* returned buffer is allocated in secure memory. */
static gpg_error_t
concat_tlv_list (int secure, unsigned char **r_result, size_t *r_resultlen, ...)
{
gpg_error_t err;
va_list arg_ptr;
struct {
int tag;
unsigned int len;
unsigned int contlen;
const void *data;
} argv[10];
int i, j, argc;
unsigned char *data = NULL;
size_t datalen;
unsigned char *p;
size_t n;
*r_result = NULL;
*r_resultlen = 0;
/* Collect all args. Check that length is <= 2^16 to match the
* behaviour of add_tlv. */
va_start (arg_ptr, r_resultlen);
argc = 0;
while (((argv[argc].tag = va_arg (arg_ptr, int))))
{
argv[argc].len = va_arg (arg_ptr, size_t);
argv[argc].contlen = 0;
argv[argc].data = va_arg (arg_ptr, const void *);
if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
{
va_end (arg_ptr);
err = gpg_error (GPG_ERR_EINVAL);
goto leave;
}
argc++;
}
va_end (arg_ptr);
/* Compute the required buffer length and allocate the buffer. */
datalen = 0;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. Compute its length. Note that we
* currently allow only one constructed tag in the list. */
for (n=0, j = i + 1; j < argc; j++)
{
log_assert (!(!argv[j].len && !argv[j].data));
n += add_tlv (NULL, argv[j].tag, argv[j].len);
n += argv[j].len;
}
argv[i].contlen = n;
datalen += add_tlv (NULL, argv[i].tag, n);
}
else
{
datalen += add_tlv (NULL, argv[i].tag, argv[i].len);
datalen += argv[i].len;
}
}
data = secure? xtrymalloc_secure (datalen) : xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy that data to the buffer. */
p = data;
for (i=0; i < argc; i++)
{
if (!argv[i].len && !argv[i].data)
{
/* Constructed tag. */
p += add_tlv (p, argv[i].tag, argv[i].contlen);
}
else
{
p += add_tlv (p, argv[i].tag, argv[i].len);
memcpy (p, argv[i].data, argv[i].len);
p += argv[i].len;
}
}
log_assert ( data + datalen == p );
*r_result = data;
data = NULL;
*r_resultlen = datalen;
err = 0;
leave:
xfree (data);
return err;
}
/* Wrapper around iso7816_put_data_odd which also sets the tag into
* the '5C' data object. The varargs are tuples of (int,size_t,void)
* with the tag, the length and the actual data. A (0,0,NULL) tuple
* terminates the list. Up to 10 tuples are supported. */
static gpg_error_t
put_data (int slot, unsigned int tag, ...)
{
gpg_error_t err;
va_list arg_ptr;
struct {
int tag;
size_t len;
const void *data;
} argv[10];
int i, argc;
unsigned char data5c[5];
size_t data5clen;
unsigned char *data = NULL;
size_t datalen;
unsigned char *p;
size_t n;
/* Collect all args. Check that length is <= 2^16 to match the
* behaviour of add_tlv. */
va_start (arg_ptr, tag);
argc = 0;
while (((argv[argc].tag = va_arg (arg_ptr, int))))
{
argv[argc].len = va_arg (arg_ptr, size_t);
argv[argc].data = va_arg (arg_ptr, const void *);
if (argc >= DIM (argv)-1 || argv[argc].len > 0xffff)
{
va_end (arg_ptr);
return GPG_ERR_EINVAL;
}
argc++;
}
va_end (arg_ptr);
/* Build the TLV with the tag to be updated. */
data5c[0] = 0x5c; /* Tag list */
if (tag <= 0xff)
{
data5c[1] = 1;
data5c[2] = tag;
data5clen = 3;
}
else if (tag <= 0xffff)
{
data5c[1] = 2;
data5c[2] = (tag >> 8);
data5c[3] = tag;
data5clen = 4;
}
else
{
data5c[1] = 3;
data5c[2] = (tag >> 16);
data5c[3] = (tag >> 8);
data5c[4] = tag;
data5clen = 5;
}
/* Compute the required buffer length and allocate the buffer. */
n = 0;
for (i=0; i < argc; i++)
{
n += add_tlv (NULL, argv[i].tag, argv[i].len);
n += argv[i].len;
}
datalen = data5clen + add_tlv (NULL, 0x53, n) + n;
data = xtrymalloc (datalen);
if (!data)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Copy that data to the buffer. */
p = data;
memcpy (p, data5c, data5clen);
p += data5clen;
p += add_tlv (p, 0x53, n);
for (i=0; i < argc; i++)
{
p += add_tlv (p, argv[i].tag, argv[i].len);
memcpy (p, argv[i].data, argv[i].len);
p += argv[i].len;
}
log_assert ( data + datalen == p );
err = iso7816_put_data_odd (slot, -1 /* use command chaining */,
0x3fff, data, datalen);
leave:
xfree (data);
return err;
}
/* Parse the key reference KEYREFSTR which is expected to hold a key
* reference for a CHV object. Return the one octet keyref or -1 for
* an invalid reference. */
static int
parse_chv_keyref (const char *keyrefstr)
{
if (!keyrefstr)
return -1;
else if (!ascii_strcasecmp (keyrefstr, "PIV.00"))
return 0x00;
else if (!ascii_strcasecmp (keyrefstr, "PIV.80"))
return 0x80;
else if (!ascii_strcasecmp (keyrefstr, "PIV.81"))
return 0x81;
else
return -1;
}
/* The verify command can be used to retrieve the security status of
* the card. Given the PIN name (e.g. "PIV.80" for the application
* pin, a ISO7817_VERIFY_* code is returned or a non-negative number
* of verification attempts left. */
static int
get_chv_status (app_t app, const char *keyrefstr)
{
int keyref;
keyref = parse_chv_keyref (keyrefstr);
if (!keyrefstr)
return ISO7816_VERIFY_ERROR;
return iso7816_verify_status (app_get_slot (app), keyref);
}
/* Implementation of the GETATTR command. This is similar to the
* LEARN command but returns only one value via status lines. */
static gpg_error_t
do_getattr (app_t app, ctrl_t ctrl, const char *name)
{
static struct {
const char *name;
int tag;
int special;
} table[] = {
{ "SERIALNO", 0x0000, -1 },
{ "$AUTHKEYID", 0x0000, -2 }, /* Default ssh key. */
{ "$ENCRKEYID", 0x0000, -6 }, /* Default encryption key. */
{ "$SIGNKEYID", 0x0000, -7 }, /* Default signing key. */
{ "$DISPSERIALNO",0x0000, -3 },
{ "CHV-STATUS", 0x0000, -4 },
{ "CHV-USAGE", 0x007E, -5 }
};
gpg_error_t err = 0;
int idx;
void *relptr;
unsigned char *value;
size_t valuelen;
const unsigned char *s;
size_t n;
for (idx=0; (idx < DIM (table)
&& ascii_strcasecmp (table[idx].name, name)); idx++)
;
if (!(idx < DIM (table)))
err = gpg_error (GPG_ERR_INV_NAME);
else if (table[idx].special == -1)
{
char *serial = app_get_serialno (app);
if (serial)
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
}
else if (table[idx].special == -2)
{
char const tmp[] = "PIV.9A"; /* Cert PIV Authenticate. */
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
else if (table[idx].special == -3)
{
char *tmp = app_get_dispserialno (app, 1);
if (tmp)
{
send_status_info (ctrl, table[idx].name,
tmp, strlen (tmp),
NULL, (size_t)0);
xfree (tmp);
}
else
err = gpg_error (GPG_ERR_INV_NAME); /* No Abbreviated S/N. */
}
else if (table[idx].special == -4) /* CHV-STATUS */
{
int tmp[4];
tmp[0] = get_chv_status (app, "PIV.00");
tmp[1] = get_chv_status (app, "PIV.80");
tmp[2] = get_chv_status (app, "PIV.81");
err = send_status_printf (ctrl, table[idx].name, "%d %d %d",
tmp[0], tmp[1], tmp[2]);
}
else if (table[idx].special == -5) /* CHV-USAGE (aka PIN Usage Policy) */
{
/* We return 2 hex bytes or nothing in case the discovery object
* is not supported. */
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
if (relptr)
{
s = find_tlv (value, valuelen, 0x7E, &n);
if (s && n && (s = find_tlv (s, n, 0x5F2F, &n)) && n >=2 )
err = send_status_printf (ctrl, table[idx].name, "%02X %02X",
s[0], s[1]);
xfree (relptr);
}
}
else if (table[idx].special == -6)
{
char const tmp[] = "PIV.9D"; /* Key Management. */
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
else if (table[idx].special == -7)
{
char const tmp[] = "PIV.9C"; /* Digital Signature. */
send_status_info (ctrl, table[idx].name, tmp, strlen (tmp), NULL, 0);
}
else
{
relptr = get_one_do (app, table[idx].tag, &value, &valuelen, &err);
if (relptr)
{
send_status_info (ctrl, table[idx].name, value, valuelen, NULL, 0);
xfree (relptr);
}
}
return err;
}
/* Authenticate the card using the Card Application Administration
* Key. (VALUE,VALUELEN) has that 24 byte key. */
static gpg_error_t
auth_adm_key (app_t app, const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
unsigned char tmpl[4+24];
size_t tmpllen;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
char witness[8];
size_t n;
gcry_cipher_hd_t cipher = NULL;
/* Prepare decryption. */
err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipher, value, valuelen);
if (err)
goto leave;
/* Request a witness. */
tmpl[0] = 0x7c;
tmpl[1] = 0x02;
tmpl[2] = 0x80;
tmpl[3] = 0; /* (Empty witness requests a witness.) */
tmpllen = 4;
err = iso7816_general_authenticate (app_get_slot (app), 0,
PIV_ALGORITHM_3DES_ECB_0, 0x9B,
tmpl, tmpllen, 0,
&outdata, &outdatalen);
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_BAD_AUTH);
if (err)
goto leave;
if (!(outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x80, &n))
&& n == 8))
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: improper witness received\n");
goto leave;
}
err = gcry_cipher_decrypt (cipher, witness, 8, s, 8);
if (err)
goto leave;
/* Return decrypted witness and send our challenge. */
tmpl[0] = 0x7c;
tmpl[1] = 22;
tmpl[2] = 0x80;
tmpl[3] = 8;
memcpy (tmpl+4, witness, 8);
tmpl[12] = 0x81;
tmpl[13] = 8;
gcry_create_nonce (tmpl+14, 8);
tmpl[22] = 0x82;
tmpl[23] = 0;
tmpllen = 24;
xfree (outdata);
err = iso7816_general_authenticate (app_get_slot (app), 0,
PIV_ALGORITHM_3DES_ECB_0, 0x9B,
tmpl, tmpllen, 0,
&outdata, &outdatalen);
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_BAD_AUTH);
if (err)
goto leave;
if (!(outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n))
&& n == 8))
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: improper challenge received\n");
goto leave;
}
/* (We reuse the witness buffer.) */
err = gcry_cipher_decrypt (cipher, witness, 8, s, 8);
if (err)
goto leave;
if (memcmp (witness, tmpl+14, 8))
{
err = gpg_error (GPG_ERR_BAD_AUTH);
goto leave;
}
leave:
xfree (outdata);
gcry_cipher_close (cipher);
return err;
}
/* Set a new admin key. */
static gpg_error_t
set_adm_key (app_t app, const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
unsigned char apdu[8+24];
unsigned int sw;
/* Check whether it is a weak key and that it is of proper length. */
{
gcry_cipher_hd_t cipher;
err = gcry_cipher_open (&cipher, GCRY_CIPHER_3DES, GCRY_CIPHER_MODE_ECB, 0);
if (!err)
{
err = gcry_cipher_setkey (cipher, value, valuelen);
gcry_cipher_close (cipher);
}
if (err)
goto leave;
}
if (app->app_local->flags.yubikey)
{
/* This is a Yubikey. */
if (valuelen != 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
/* We use a proprietary Yubikey command. */
apdu[0] = 0;
apdu[1] = 0xff;
apdu[2] = 0xff;
apdu[3] = 0xff; /* touch policy: 0xff=never, 0xfe = always. */
apdu[4] = 3 + 24;
apdu[5] = PIV_ALGORITHM_3DES_ECB;
apdu[6] = 0x9b;
apdu[7] = 24;
memcpy (apdu+8, value, 24);
err = iso7816_apdu_direct (app_get_slot (app), apdu, 8+24, 0,
&sw, NULL, NULL);
wipememory (apdu+8, 24);
if (err)
log_error ("piv: setting admin key failed; sw=%04x\n", sw);
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
}
else
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
leave:
return err;
}
/* Handle the SETATTR operation. All arguments are already basically
* checked. */
static gpg_error_t
do_setattr (app_t app, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
static struct {
const char *name;
unsigned short tag;
unsigned short flush_tag; /* The tag which needs to be flushed or 0. */
int special; /* Special mode to use for thus NAME. */
} table[] = {
/* Authenticate using the PIV Card Application Administration Key
* (0x9B). Note that Yubico calls this key the "management key"
* which we don't do because that term is too similar to "Cert
* Management Key" (0x9D). */
{ "AUTH-ADM-KEY", 0x0000, 0x0000, 1 },
{ "SET-ADM-KEY", 0x0000, 0x0000, 2 }
};
int idx;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
for (idx=0; (idx < DIM (table)
&& ascii_strcasecmp (table[idx].name, name)); idx++)
;
if (!(idx < DIM (table)))
return gpg_error (GPG_ERR_INV_NAME);
/* Flush the cache before writing it, so that the next get operation
* will reread the data from the card and thus get synced in case of
* errors (e.g. data truncated by the card). */
if (table[idx].tag)
flush_cached_data (app, table[idx].flush_tag? table[idx].flush_tag
/* */ : table[idx].tag);
switch (table[idx].special)
{
case 1:
err = auth_adm_key (app, value, valuelen);
break;
case 2:
err = set_adm_key (app, value, valuelen);
break;
default:
err = gpg_error (GPG_ERR_BUG);
break;
}
return err;
}
/* Send the KEYPAIRINFO back. DOBJ describes the data object carrying
* the key. This is used by the LEARN command. */
static gpg_error_t
send_keypair_and_cert_info (app_t app, ctrl_t ctrl, data_object_t dobj,
int only_keypair)
{
gpg_error_t err = 0;
char *keygripstr = NULL;
int got_cert;
char idbuf[50];
const char *usage;
err = get_keygrip_by_tag (app, dobj->tag, &keygripstr, &got_cert);
if (err)
goto leave;
usage = dobj->usage? dobj->usage : "";
snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref);
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
idbuf, strlen (idbuf),
usage, strlen (usage),
NULL, (size_t)0);
if (!only_keypair && got_cert)
{
/* All certificates are of type 100 (Regular X.509 Cert). */
send_status_info (ctrl, "CERTINFO",
"100", 3,
idbuf, strlen (idbuf),
NULL, (size_t)0);
}
leave:
xfree (keygripstr);
return err;
}
/* Handle the LEARN command. */
static gpg_error_t
do_learn_status (app_t app, ctrl_t ctrl, unsigned int flags)
{
int i;
(void)flags;
do_getattr (app, ctrl, "CHV-USAGE");
do_getattr (app, ctrl, "CHV-STATUS");
for (i=0; data_objects[i].tag; i++)
if (data_objects[i].keypair)
send_keypair_and_cert_info (app, ctrl, data_objects + i,
!!(flags & APP_LEARN_FLAG_KEYPAIRINFO));
return 0;
}
/* Core of do_readcert which fetches the certificate based on the
* given tag and returns it in a freshly allocated buffer stored at
* R_CERT and the length of the certificate stored at R_CERTLEN. If
* on success a non-zero value is stored at R_MECHANISM, the returned
* data is not a certificate but a public key (in the format used by the
* container '7f49'). */
static gpg_error_t
readcert_by_tag (app_t app, unsigned int tag,
unsigned char **r_cert, size_t *r_certlen, int *r_mechanism)
{
gpg_error_t err;
unsigned char *buffer;
size_t buflen;
void *relptr;
const unsigned char *s, *s2;
size_t n, n2;
*r_cert = NULL;
*r_certlen = 0;
*r_mechanism = 0;
relptr = get_one_do (app, tag, &buffer, &buflen, NULL);
if (!relptr || !buflen)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
s = find_tlv (buffer, buflen, 0x71, &n);
if (!s)
{
/* No certificate; check whether a public key has been stored
* using our own scheme. */
s = find_tlv (buffer, buflen, 0x7f49, &n);
if (!s || !n)
{
log_error ("piv: No public key in 0x%X\n", tag);
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
s2 = find_tlv (buffer, buflen, 0x80, &n2);
if (!s2 || n2 != 1 || !*s2)
{
log_error ("piv: No mechanism for public key in 0x%X\n", tag);
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
*r_mechanism = *s2;
}
else
{
if (n != 1)
{
log_error ("piv: invalid CertInfo in 0x%X\n", tag);
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto leave;
}
if (*s == 0x01)
{
log_error ("piv: gzip compression not yet supported (tag 0x%X)\n",
tag);
err = gpg_error (GPG_ERR_UNSUPPORTED_ENCODING);
goto leave;
}
if (*s)
{
log_error ("piv: invalid CertInfo 0x%02x in 0x%X\n", *s, tag);
err = gpg_error (GPG_ERR_INV_CERT_OBJ);
goto leave;
}
/* Note: We don't check that the LRC octet has a length of zero
* as required by the specs. */
/* Get the cert from the container. */
s = find_tlv (buffer, buflen, 0x70, &n);
if (!s || !n)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
}
/* The next is common for certificate and public key. */
if (!(*r_cert = xtrymalloc (n)))
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (*r_cert, s, n);
*r_certlen = n;
err = 0;
leave:
xfree (relptr);
return err;
}
/* Get the keygrip in hex format of a key from the certificate stored
* at TAG. Caller must free the string at R_KEYGRIPSTR. */
static gpg_error_t
get_keygrip_by_tag (app_t app, unsigned int tag,
char **r_keygripstr, int *r_got_cert)
{
gpg_error_t err;
unsigned char *certbuf = NULL;
size_t certbuflen;
int mechanism;
gcry_sexp_t s_pkey = NULL;
ksba_cert_t cert = NULL;
unsigned char grip[KEYGRIP_LEN];
*r_got_cert = 0;
*r_keygripstr = xtrymalloc (2*KEYGRIP_LEN+1);
if (!r_keygripstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* We need to get the public key from the certificate. */
err = readcert_by_tag (app, tag, &certbuf, &certbuflen, &mechanism);
if (err)
goto leave;
if (mechanism) /* Compute keygrip from public key. */
{
if (mechanism == PIV_ALGORITHM_RSA)
err = genkey_parse_rsa (certbuf, certbuflen, &s_pkey);
else if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
err = genkey_parse_ecc (certbuf, certbuflen, mechanism, &s_pkey);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
if (err)
goto leave;
if (!gcry_pk_get_keygrip (s_pkey, grip))
{
log_error ("piv: error computing keygrip\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
bin2hex (grip, sizeof grip, *r_keygripstr);
}
else /* Compute keygrip from certificate. */
{
*r_got_cert = 0;
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
if (err)
goto leave;
err = app_help_get_keygrip_string (cert, *r_keygripstr, NULL, NULL);
}
leave:
gcry_sexp_release (s_pkey);
ksba_cert_release (cert);
xfree (certbuf);
if (err)
{
xfree (*r_keygripstr);
*r_keygripstr = NULL;
}
return err;
}
/* Locate the data object from the given KEYREF. The KEYREF may also
* be the corresponding OID of the key object. Returns the data
* object or NULL if not found. */
static data_object_t
find_dobj_by_keyref (app_t app, const char *keyref)
{
int i;
(void)app;
if (!ascii_strncasecmp (keyref, "PIV.", 4)) /* Standard keyref */
{
keyref += 4;
for (i=0; data_objects[i].tag; i++)
if (*data_objects[i].keyref
&& !ascii_strcasecmp (keyref, data_objects[i].keyref))
{
return data_objects + i;
}
}
else if (!strncmp (keyref, "2.16.840.1.101.3.7.", 19)) /* OID */
{
keyref += 19;
for (i=0; data_objects[i].tag; i++)
if (*data_objects[i].keyref
&& !strcmp (keyref, data_objects[i].oidsuffix))
{
return data_objects + i;
}
}
else if (strlen (keyref) == 40) /* A keygrip */
{
char *keygripstr = NULL;
int tag, dummy_got_cert;
for (i=0; (tag=data_objects[i].tag); i++)
{
if (!data_objects[i].keypair)
continue;
xfree (keygripstr);
if (get_keygrip_by_tag (app, tag, &keygripstr, &dummy_got_cert))
continue;
if (!strcmp (keygripstr, keyref))
{
xfree (keygripstr);
return data_objects + i;
}
}
xfree (keygripstr);
}
return NULL;
}
/* Return the keyref from DOBJ as an integer. If it does not exist,
* return -1. */
static int
keyref_from_dobj (data_object_t dobj)
{
if (!dobj || !hexdigitp (dobj->keyref) || !hexdigitp (dobj->keyref+1))
return -1;
return xtoi_2 (dobj->keyref);
}
/* Read a certificate from the card and returned in a freshly
* allocated buffer stored at R_CERT and the length of the certificate
* stored at R_CERTLEN. CERTID is either the OID of the cert's
* container or of the form "PIV.<two_hexdigit_keyref>" */
static gpg_error_t
do_readcert (app_t app, const char *certid,
unsigned char **r_cert, size_t *r_certlen)
{
gpg_error_t err;
data_object_t dobj;
int mechanism;
*r_cert = NULL;
*r_certlen = 0;
/* Hack to read a Yubikey attestation certificate. */
if (app->app_local->flags.yubikey
&& strlen (certid) == 11
&& !ascii_strncasecmp (certid, "PIV.ATST.", 9)
&& hexdigitp (certid+9) && hexdigitp (certid+10))
{
unsigned char apdu[4];
unsigned char *result;
size_t resultlen;
apdu[0] = 0;
apdu[1] = 0xf9; /* Yubikey: Get attestation cert. */
apdu[2] = xtoi_2 (certid+9);
apdu[3] = 0;
err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 1,
NULL, &result, &resultlen);
if (!err)
{
*r_cert = result;
*r_certlen = resultlen;
}
return err;
}
dobj = find_dobj_by_keyref (app, certid);
if (!dobj)
return gpg_error (GPG_ERR_INV_ID);
err = readcert_by_tag (app, dobj->tag, r_cert, r_certlen, &mechanism);
if (!err && mechanism)
{
/* Well, no certificate but a public key - we don't want it. */
xfree (*r_cert);
*r_cert = NULL;
*r_certlen = 0;
err = gpg_error (GPG_ERR_NOT_FOUND);
}
return err;
}
/* Return a public key in a freshly allocated buffer. This will only
* work for a freshly generated key as long as no reset of the
* application has been performed. This is because we return a cached
* result from key generation. If no cached result is available, the
* error GPG_ERR_UNSUPPORTED_OPERATION is returned so that the higher
* layer can then get the key by reading the matching certificate.
* On success a canonical encoded s-expression with the public key is
* stored at (R_PK,R_PKLEN); the caller must release that buffer. On
* error R_PK and R_PKLEN are not changed and an error code is
* returned.
*/
static gpg_error_t
do_readkey (app_t app, ctrl_t ctrl, const char *keyrefstr, unsigned int flags,
unsigned char **r_pk, size_t *r_pklen)
{
gpg_error_t err;
data_object_t dobj;
int keyref;
unsigned char *cert = NULL;
size_t certlen;
int mechanism;
gcry_sexp_t s_pkey = NULL;
unsigned char *pk = NULL;
size_t pklen;
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = readcert_by_tag (app, dobj->tag, &cert, &certlen, &mechanism);
if (err)
goto leave;
if (!mechanism)
{
/* We got a certificate. Extract the pubkey from it. */
err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen);
if (err)
{
log_error ("failed to parse the certificate: %s\n",
gpg_strerror (err));
goto leave;
}
}
else
{
/* Convert the public key into the expected s-expression. */
if (mechanism == PIV_ALGORITHM_RSA)
err = genkey_parse_rsa (cert, certlen, &s_pkey);
else if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
err = genkey_parse_ecc (cert, certlen, mechanism, &s_pkey);
else
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
if (err)
goto leave;
err = make_canon_sexp (s_pkey, &pk, &pklen);
if (err)
goto leave;
}
if ((flags & APP_READKEY_FLAG_INFO))
{
char keygripstr[KEYGRIP_LEN*2+1];
char idbuf[50];
const char *usage;
char *algostr;
err = app_help_get_keygrip_string_pk (pk, pklen, keygripstr,
NULL, NULL, &algostr);
if (err)
{
log_error ("app_help_get_keygrip_string_pk failed: %s\n",
gpg_strerror (err));
goto leave;
}
usage = dobj->usage? dobj->usage : "-";
snprintf (idbuf, sizeof idbuf, "PIV.%s", dobj->keyref);
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
idbuf, strlen (idbuf),
usage, strlen (usage),
"-", (size_t)1,
algostr, strlen (algostr),
NULL, (size_t)0);
xfree (algostr);
}
if (r_pk && r_pklen)
{
*r_pk = pk;
pk = NULL;
*r_pklen = pklen;
}
leave:
gcry_sexp_release (s_pkey);
xfree (pk);
xfree (cert);
return err;
}
/* Given a data object DOBJ return the corresponding PIV algorithm and
* store it at R_ALGO. The algorithm is taken from the corresponding
* certificate or from a cache. */
static gpg_error_t
get_key_algorithm_by_dobj (app_t app, data_object_t dobj, int *r_mechanism)
{
gpg_error_t err;
unsigned char *certbuf = NULL;
size_t certbuflen;
int mechanism;
ksba_cert_t cert = NULL;
ksba_sexp_t k_pkey = NULL;
gcry_sexp_t s_pkey = NULL;
gcry_sexp_t l1 = NULL;
char *algoname = NULL;
int algo;
size_t n;
const char *curve_name;
*r_mechanism = 0;
err = readcert_by_tag (app, dobj->tag, &certbuf, &certbuflen, &mechanism);
if (err)
goto leave;
if (mechanism)
{
/* A public key was found. That makes it easy. */
switch (mechanism)
{
case PIV_ALGORITHM_RSA:
case PIV_ALGORITHM_ECC_P256:
case PIV_ALGORITHM_ECC_P384:
*r_mechanism = mechanism;
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("piv: unknown mechanism %d in public key at %s\n",
mechanism, dobj->keyref);
break;
}
goto leave;
}
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert, certbuf, certbuflen);
if (err)
{
log_error ("piv: failed to parse the certificate %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
xfree (certbuf);
certbuf = NULL;
k_pkey = ksba_cert_get_public_key (cert);
if (!k_pkey)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
n = gcry_sexp_canon_len (k_pkey, 0, NULL, NULL);
err = gcry_sexp_new (&s_pkey, k_pkey, n, 0);
if (err)
goto leave;
l1 = gcry_sexp_find_token (s_pkey, "public-key", 0);
if (!l1)
{
err = gpg_error (GPG_ERR_NO_PUBKEY);
goto leave;
}
{
gcry_sexp_t l_tmp = gcry_sexp_cadr (l1);
gcry_sexp_release (l1);
l1 = l_tmp;
}
algoname = gcry_sexp_nth_string (l1, 0);
if (!algoname)
{
err = gpg_error_from_syserror ();
goto leave;
}
algo = gcry_pk_map_name (algoname);
switch (algo)
{
case GCRY_PK_RSA:
algo = PIV_ALGORITHM_RSA;
break;
case GCRY_PK_ECC:
case GCRY_PK_ECDSA:
case GCRY_PK_ECDH:
curve_name = gcry_pk_get_curve (s_pkey, 0, NULL);
if (curve_name && !strcmp (curve_name, "NIST P-256"))
algo = PIV_ALGORITHM_ECC_P256;
else if (curve_name && !strcmp (curve_name, "NIST P-384"))
algo = PIV_ALGORITHM_ECC_P384;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
log_error ("piv: certificate %s, curve '%s': %s\n",
dobj->keyref, curve_name, gpg_strerror (err));
goto leave;
}
break;
default:
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
log_error ("piv: certificate %s, pubkey algo '%s': %s\n",
dobj->keyref, algoname, gpg_strerror (err));
goto leave;
}
*r_mechanism = algo;
leave:
gcry_free (algoname);
gcry_sexp_release (l1);
gcry_sexp_release (s_pkey);
ksba_free (k_pkey);
xfree (certbuf);
return err;
}
/* Helper to cache the pin PINNO. If PIN is NULL the cache is cleared. */
static void
cache_pin (app_t app, ctrl_t ctrl, int pinno,
const char *pin, unsigned int pinlen)
{
char pinref[20];
if (opt.pcsc_shared)
return;
if (pinno < 0)
return;
switch (app->card->cardtype)
{
case CARDTYPE_YUBIKEY: break;
default: return;
}
snprintf (pinref, sizeof pinref, "%02x", pinno);
pincache_put (ctrl, app_get_slot (app), "piv", pinref, pin, pinlen);
switch (pinno)
{
case 0x00: app->app_local->pincache.maybe_00 = !!pin; break;
case 0x80: app->app_local->pincache.maybe_80 = !!pin; break;
case 0x81: app->app_local->pincache.maybe_81 = !!pin; break;
case 0x96: app->app_local->pincache.maybe_96 = !!pin; break;
case 0x97: app->app_local->pincache.maybe_97 = !!pin; break;
case 0x98: app->app_local->pincache.maybe_98 = !!pin; break;
case 0x9B: app->app_local->pincache.maybe_9B = !!pin; break;
}
}
/* If the PIN cache is available and really has a valid PIN return
* that pin at R_PIN. Returns true if that is the case; otherwise
* false. */
static int
pin_from_cache (app_t app, ctrl_t ctrl, int pinno, char **r_pin)
{
char pinref[20];
int maybe_cached;
*r_pin = NULL;
if (pinno < 0)
return 0;
switch (app->card->cardtype)
{
case CARDTYPE_YUBIKEY: break;
default: return 0;
}
switch (pinno)
{
case 0x00: maybe_cached = app->app_local->pincache.maybe_00; break;
case 0x80: maybe_cached = app->app_local->pincache.maybe_80; break;
case 0x81: maybe_cached = app->app_local->pincache.maybe_81; break;
case 0x96: maybe_cached = app->app_local->pincache.maybe_96; break;
case 0x97: maybe_cached = app->app_local->pincache.maybe_97; break;
case 0x98: maybe_cached = app->app_local->pincache.maybe_98; break;
case 0x9B: maybe_cached = app->app_local->pincache.maybe_9B; break;
default: maybe_cached = 0;
}
if (!maybe_cached)
return 0;
snprintf (pinref, sizeof pinref, "%02x", pinno);
if (pincache_get (ctrl, app_get_slot (app), "piv", pinref, r_pin))
return 0;
return 1;
}
/* Return an allocated string to be used as prompt. Returns NULL on
* malloc error. */
static char *
make_prompt (app_t app, int remaining, const char *firstline)
{
char *serial, *tmpbuf, *result;
serial = app_get_dispserialno (app, 0);
if (!serial)
return NULL;
/* TRANSLATORS: Put a \x1f right before a colon. This can be
* used by pinentry to nicely align the names and values. Keep
* the %s at the start and end of the string. */
result = xtryasprintf (_("%s"
"Number\x1f: %s%%0A"
"Holder\x1f: %s"
"%s"),
"\x1e",
serial,
"Unknown", /* Fixme */
"");
xfree (serial);
/* Append a "remaining attempts" info if needed. */
if (remaining != -1 && remaining < 3)
{
char *rembuf;
/* TRANSLATORS: This is the number of remaining attempts to
* enter a PIN. Use %%0A (double-percent,0A) for a linefeed. */
rembuf = xtryasprintf (_("Remaining attempts: %d"), remaining);
if (rembuf)
{
tmpbuf = strconcat (firstline, "%0A%0A", result,
"%0A%0A", rembuf, NULL);
xfree (rembuf);
}
else
tmpbuf = NULL;
xfree (result);
result = tmpbuf;
}
else
{
tmpbuf = strconcat (firstline, "%0A%0A", result, NULL);
xfree (result);
result = tmpbuf;
}
return result;
}
/* Helper for verify_chv to ask for the PIN and to prepare/pad it. On
* success the result is stored at (R_PIN,R_PINLEN). */
static gpg_error_t
ask_and_prepare_chv (app_t app, ctrl_t ctrl,
int keyref, int ask_new, int remaining, int no_cache,
gpg_error_t (*pincb)(void*,const char *,char **),
void *pincb_arg, char **r_pin, unsigned int *r_pinlen,
unsigned int *r_unpaddedpinlen)
{
gpg_error_t err;
const char *label;
char *prompt;
char *pinvalue = NULL;
unsigned int pinlen;
char *pinbuffer = NULL;
int minlen, maxlen, padding, onlydigits;
*r_pin = NULL;
*r_pinlen = 0;
if (r_unpaddedpinlen)
*r_unpaddedpinlen = 0;
if (ask_new)
remaining = -1;
if (remaining != -1)
log_debug ("piv: CHV %02X has %d attempts left\n", keyref, remaining);
switch (keyref)
{
case 0x00:
minlen = 6;
maxlen = 8;
padding = 1;
onlydigits = 1;
label = (ask_new? _("|N|Please enter the new Global-PIN")
/**/ : _("||Please enter the Global-PIN of your PIV card"));
break;
case 0x80:
minlen = 6;
maxlen = 8;
padding = 1;
onlydigits = 1;
label = (ask_new? _("|N|Please enter the new PIN")
/**/ : _("||Please enter the PIN of your PIV card"));
break;
case 0x81:
minlen = 8;
maxlen = 8;
padding = 0;
onlydigits = 0;
label = (ask_new? _("|N|Please enter the new Unblocking Key")
/**/ :_("||Please enter the Unblocking Key of your PIV card"));
break;
case 0x96:
case 0x97:
case 0x98:
case 0x9B:
return gpg_error (GPG_ERR_NOT_IMPLEMENTED);
default:
return gpg_error (GPG_ERR_INV_ID);
}
/* Ask for the PIN. */
if (!no_cache && remaining >= 3
&& pin_from_cache (app, ctrl, keyref, &pinvalue))
err = 0;
else
{
prompt = make_prompt (app, remaining, label);
err = pincb (pincb_arg, prompt, &pinvalue);
xfree (prompt);
prompt = NULL;
}
if (err)
{
log_info (_("PIN callback returned error: %s\n"), gpg_strerror (err));
return err;
}
pinlen = pinvalue? strlen (pinvalue) : 0;
if (pinlen < minlen)
{
log_error (_("PIN is too short; minimum length is %d\n"), minlen);
if (pinvalue)
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (pinlen > maxlen)
{
log_error (_("PIN is too long; maximum length is %d\n"), maxlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
if (onlydigits && strspn (pinvalue, "0123456789") != pinlen)
{
log_error (_("PIN has invalid characters; only digits are allowed\n"));
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return gpg_error (GPG_ERR_BAD_PIN);
}
pinbuffer = xtrymalloc_secure (maxlen);
if (!pinbuffer)
{
err = gpg_error_from_syserror ();
wipememory (pinvalue, pinlen);
xfree (pinvalue);
return err;
}
memcpy (pinbuffer, pinvalue, pinlen);
wipememory (pinvalue, pinlen);
xfree (pinvalue);
if (r_unpaddedpinlen)
*r_unpaddedpinlen = pinlen;
if (padding)
{
memset (pinbuffer + pinlen, 0xff, maxlen - pinlen);
pinlen = maxlen;
}
*r_pin = pinbuffer;
*r_pinlen = pinlen;
return 0;
}
/* Verify the card holder verification identified by KEYREF. This is
* either the Application PIN or the Global PIN. If FORCE is true a
* verification is always done. */
static gpg_error_t
verify_chv (app_t app, ctrl_t ctrl, int keyref, int force,
gpg_error_t (*pincb)(void*,const char *,char **), void *pincb_arg)
{
gpg_error_t err;
int remaining;
char *pin = NULL;
unsigned int pinlen, unpaddedpinlen;
/* First check whether a verify is at all needed. */
remaining = iso7816_verify_status (app_get_slot (app), keyref);
if (remaining == ISO7816_VERIFY_NOT_NEEDED)
{
if (!force) /* No need to verification. */
return 0; /* All fine. */
remaining = -1;
}
else if (remaining < 0) /* We don't care about other errors. */
remaining = -1;
err = ask_and_prepare_chv (app, ctrl, keyref, 0, remaining, force,
pincb, pincb_arg,
&pin, &pinlen, &unpaddedpinlen);
if (err)
return err;
err = iso7816_verify (app_get_slot (app), keyref, pin, pinlen);
if (err)
{
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
cache_pin (app, ctrl, keyref, NULL, 0);
}
else
cache_pin (app, ctrl, keyref, pin, unpaddedpinlen);
wipememory (pin, pinlen);
xfree (pin);
return err;
}
/* Handle the PASSWD command. Valid values for PWIDSTR are
* key references related to PINs; in particular:
* PIV.00 - The Global PIN
* PIV.80 - The Application PIN
* PIV.81 - The PIN Unblocking key
* The supported flags are:
* APP_CHANGE_FLAG_CLEAR Clear the PIN verification state.
* APP_CHANGE_FLAG_RESET Reset a PIN using the PUK. Only
* allowed with PIV.80.
*/
static gpg_error_t
do_change_chv (app_t app, ctrl_t ctrl, const char *pwidstr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
int keyref, targetkeyref;
unsigned char apdu[4];
unsigned int sw;
int remaining;
char *oldpin = NULL;
unsigned int oldpinlen;
char *newpin = NULL;
unsigned int newpinlen;
(void)ctrl;
/* Check for unknown flags. */
if ((flags & ~(APP_CHANGE_FLAG_CLEAR|APP_CHANGE_FLAG_RESET)))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
goto leave;
}
/* Parse the keyref. */
targetkeyref = keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
cache_pin (app, ctrl, keyref, NULL, 0);
/* First see whether the special --clear mode has been requested. */
if ((flags & APP_CHANGE_FLAG_CLEAR))
{
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0xff;
apdu[3] = keyref;
err = iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0,
NULL, NULL, NULL);
goto leave;
}
/* Prepare reset mode. */
if ((flags & APP_CHANGE_FLAG_RESET))
{
if (keyref == 0x81)
{
err = gpg_error (GPG_ERR_INV_ID); /* Can't reset the PUK. */
goto leave;
}
/* Set the keyref to the PUK and keep the TARGETKEYREF. */
keyref = 0x81;
}
/* Get the remaining tries count. This is done by using the check
* for verified state feature. */
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = keyref;
if (!iso7816_apdu_direct (app_get_slot (app), apdu, 4, 0, &sw, NULL, NULL))
remaining = -1; /* Already verified, thus full number of tries. */
else if ((sw & 0xfff0) == 0x63C0)
remaining = (sw & 0x000f); /* PIN has REMAINING tries left. */
else
remaining = -1;
/* Ask for the old pin or puk. */
err = ask_and_prepare_chv (app, ctrl, keyref, 0, remaining, 0,
pincb, pincb_arg,
&oldpin, &oldpinlen, NULL);
if (err)
return err;
/* Verify the old pin so that we don't prompt for the new pin if the
* old is wrong. This is not possible for the PUK, though. */
if (keyref != 0x81)
{
err = iso7816_verify (app_get_slot (app), keyref, oldpin, oldpinlen);
if (err)
{
log_error ("CHV %02X verification failed: %s\n",
keyref, gpg_strerror (err));
goto leave;
}
}
/* Ask for the new pin. */
err = ask_and_prepare_chv (app, ctrl, targetkeyref, 1, -1, 0,
pincb, pincb_arg,
&newpin, &newpinlen, NULL);
if (err)
return err;
if ((flags & APP_CHANGE_FLAG_RESET))
{
char *buf = xtrymalloc_secure (oldpinlen + newpinlen);
if (!buf)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (buf, oldpin, oldpinlen);
memcpy (buf+oldpinlen, newpin, newpinlen);
err = iso7816_reset_retry_counter_with_rc (app_get_slot (app),
targetkeyref,
buf, oldpinlen+newpinlen);
xfree (buf);
if (err)
log_error ("resetting CHV %02X using CHV %02X failed: %s\n",
targetkeyref, keyref, gpg_strerror (err));
}
else
{
err = iso7816_change_reference_data (app_get_slot (app), keyref,
oldpin, oldpinlen,
newpin, newpinlen);
if (err)
log_error ("CHV %02X changing PIN failed: %s\n",
keyref, gpg_strerror (err));
}
leave:
xfree (oldpin);
xfree (newpin);
return err;
}
/* Perform a simple verify operation for the PIN specified by PWIDSTR.
* For valid values see do_change_chv. */
static gpg_error_t
do_check_chv (app_t app, ctrl_t ctrl, const char *pwidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
int keyref;
(void)ctrl;
keyref = parse_chv_keyref (pwidstr);
if (keyref == -1)
return gpg_error (GPG_ERR_INV_ID);
return verify_chv (app, ctrl, keyref, 0, pincb, pincb_arg);
}
/* Compute a digital signature using the GENERAL AUTHENTICATE command
* on INDATA which is expected to be the raw message digest. The
* KEYIDSTR has the key reference or its OID (e.g. "PIV.9A"). The
* result is stored at (R_OUTDATA,R_OUTDATALEN); on error (NULL,0) is
* stored there and an error code returned. For ECDSA the result is
* the simple concatenation of R and S without any DER encoding. R
* and S are left extended with zeroes to make sure they have an equal
* length. If HASHALGO is not zero, the function prepends the hash's
* OID to the indata or checks that it is consistent.
*/
static gpg_error_t
do_sign (app_t app, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata_arg, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen)
{
const unsigned char *indata = indata_arg;
gpg_error_t err;
data_object_t dobj;
unsigned char oidbuf[64];
size_t oidbuflen;
unsigned char *outdata = NULL;
size_t outdatalen = 0;
const unsigned char *s;
size_t n;
int keyref, mechanism;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
unsigned char *apdudata = NULL;
size_t apdudatalen;
int force_verify;
(void)ctrl;
if (!keyidstr || !*keyidstr)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
dobj = find_dobj_by_keyref (app, keyidstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
/* According to table 4b of SP800-73-4 the signing key always
* requires a verify. */
switch (keyref)
{
case 0x9c: force_verify = 1; break;
default: force_verify = 0; break;
}
err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
if (err)
goto leave;
/* For ECC we need to remove the ASN.1 prefix from INDATA. For RSA
* we need to add the padding and possible also the ASN.1 prefix. */
if (mechanism == PIV_ALGORITHM_ECC_P256
|| mechanism == PIV_ALGORITHM_ECC_P384)
{
int need_algo, need_digestlen;
if (mechanism == PIV_ALGORITHM_ECC_P256)
{
need_algo = GCRY_MD_SHA256;
need_digestlen = 32;
}
else
{
need_algo = GCRY_MD_SHA384;
need_digestlen = 48;
}
if (hashalgo && hashalgo != need_algo)
{
err = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
log_error ("piv: hash algo %d does not match mechanism %d\n",
need_algo, mechanism);
goto leave;
}
if (indatalen > need_digestlen)
{
oidbuflen = sizeof oidbuf;
err = gcry_md_get_asnoid (need_algo, &oidbuf, &oidbuflen);
if (err)
{
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: no OID for hash algo %d\n", need_algo);
goto leave;
}
if (indatalen != oidbuflen + need_digestlen
|| memcmp (indata, oidbuf, oidbuflen))
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with mechanism %d\n",
mechanism);
goto leave;
}
indata += oidbuflen;
indatalen -= oidbuflen;
}
}
else if (mechanism == PIV_ALGORITHM_RSA
&& indatalen == 2048/8 && indata[indatalen-1] == 0xBC)
{
/* If the provided data length matches the supported RSA
* framelen and the last octet of the data is 0xBC, we assume
* this is PSS formatted data and we use it verbatim; PIV cards
* accept PSS as well as PKCS#1. */
}
else if (mechanism == PIV_ALGORITHM_RSA)
{
/* PIV requires 2048 bit RSA. */
unsigned int framelen = 2048 / 8;
unsigned char *frame;
int i;
oidbuflen = sizeof oidbuf;
if (!hashalgo)
{
/* We assume that indata already has the required
* digestinfo; thus merely prepend the padding below. */
}
else if ((err = gcry_md_get_asnoid (hashalgo, &oidbuf, &oidbuflen)))
{
log_debug ("piv: no OID for hash algo %d\n", hashalgo);
goto leave;
}
else
{
unsigned int digestlen = gcry_md_get_algo_dlen (hashalgo);
if (indatalen == digestlen)
{
/* Plain hash in INDATA; prepend the digestinfo. */
indata_buffer = xtrymalloc (oidbuflen + indatalen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer, oidbuf, oidbuflen);
memcpy (indata_buffer+oidbuflen, indata, indatalen);
indata = indata_buffer;
indatalen = oidbuflen + indatalen;
}
else if (indatalen == oidbuflen + digestlen
&& !memcmp (indata, oidbuf, oidbuflen))
; /* Correct prefix. */
else
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: bad input for signing with RSA and hash %d\n",
hashalgo);
goto leave;
}
}
/* Now prepend the pkcs#v1.5 padding. We require at least 8
* byte of padding and 3 extra bytes for the prefix and the
* delimiting nul. */
if (!indatalen || indatalen + 8 + 4 > framelen)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: input does not fit into a %u bit PKCS#v1.5 frame\n",
8*framelen);
goto leave;
}
frame = xtrymalloc (framelen);
if (!frame)
{
err = gpg_error_from_syserror ();
goto leave;
}
n = 0;
frame[n++] = 0;
frame[n++] = 1; /* Block type. */
i = framelen - indatalen - 3 ;
memset (frame+n, 0xff, i);
n += i;
frame[n++] = 0; /* Delimiter. */
memcpy (frame+n, indata, indatalen);
n += indatalen;
log_assert (n == framelen);
/* And now put it into the indata_buffer. */
xfree (indata_buffer);
indata_buffer = frame;
indata = indata_buffer;
indatalen = framelen;
}
else
{
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: unknown PIV mechanism %d while signing\n", mechanism);
goto leave;
}
/* Now verify the Application PIN. */
err = verify_chv (app, ctrl, 0x80, force_verify, pincb, pincb_arg);
if (err)
goto leave;
/* Build the Dynamic Authentication Template. */
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x7c, (size_t)0, NULL, /* Constructed. */
(int)0x82, (size_t)0, "",
(int)0x81, (size_t)indatalen, indata,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
/* Note: the -1 requests command chaining. */
err = iso7816_general_authenticate (app_get_slot (app), -1,
mechanism, keyref,
apdudata, (int)apdudatalen, 0,
&outdata, &outdatalen);
if (err)
goto leave;
/* Parse the response. */
if (outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n)))
{
if (mechanism == PIV_ALGORITHM_RSA)
{
memmove (outdata, outdata + (s - outdata), n);
outdatalen = n;
}
else /* ECC */
{
const unsigned char *rval, *sval;
size_t rlen, rlenx, slen, slenx, resultlen;
char *result;
/* The result of an ECDSA signature is
* SEQUENCE { r INTEGER, s INTEGER }
* We re-pack that by concatenating R and S and making sure
* that both have the same length. We simplify parsing by
* using find_tlv and not a proper DER parser. */
s = find_tlv (s, n, 0x30, &n);
if (!s)
goto bad_der;
rval = find_tlv (s, n, 0x02, &rlen);
if (!rval)
goto bad_der;
log_assert (n >= (rval-s)+rlen);
sval = find_tlv (rval+rlen, n-((rval-s)+rlen), 0x02, &slen);
if (!sval)
goto bad_der;
rlenx = slenx = 0;
if (rlen > slen)
slenx = rlen - slen;
else if (slen > rlen)
rlenx = slen - rlen;
resultlen = rlen + rlenx + slen + slenx;
result = xtrycalloc (1, resultlen);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (result + rlenx, rval, rlen);
memcpy (result + rlenx + rlen + slenx, sval, slen);
xfree (outdata);
outdata = result;
outdatalen = resultlen;
}
}
else
{
bad_der:
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: response does not contain a proper result\n");
goto leave;
}
leave:
if (err)
{
xfree (outdata);
*r_outdata = NULL;
*r_outdatalen = 0;
}
else
{
*r_outdata = outdata;
*r_outdatalen = outdatalen;
}
xfree (apdudata);
xfree (indata_buffer);
return err;
}
/* AUTH for PIV cards is actually the same as SIGN. The difference
* between AUTH and SIGN is that AUTH expects that pkcs#1.5 padding
* for RSA has already been done (digestInfo part w/o the padding)
* whereas SIGN may accept a plain digest and does the padding if
* needed. This is also the reason why SIGN takes a hashalgo. For
* both it is also acceptable to receive fully prepared PSS data. */
static gpg_error_t
do_auth (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen)
{
return do_sign (app, ctrl, keyidstr, 0, pincb, pincb_arg, indata, indatalen,
r_outdata, r_outdatalen);
}
/* Decrypt the data in (INDATA,INDATALEN) and on success store the
* mallocated result at (R_OUTDATA,R_OUTDATALEN). */
static gpg_error_t
do_decipher (app_t app, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata_arg, size_t indatalen,
unsigned char **r_outdata, size_t *r_outdatalen,
unsigned int *r_info)
{
const unsigned char *indata = indata_arg;
gpg_error_t err;
data_object_t dobj;
unsigned char *outdata = NULL;
size_t outdatalen;
const unsigned char *s;
size_t n;
int keyref, mechanism;
unsigned int framelen;
unsigned char *indata_buffer = NULL; /* Malloced helper. */
unsigned char *apdudata = NULL;
size_t apdudatalen;
(void)ctrl;
if (!keyidstr || !*keyidstr)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto leave;
}
dobj = find_dobj_by_keyref (app, keyidstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (keyref == 0x9A || keyref == 0x9C || keyref == 0x9E)
{
/* Signing only reference. We only allow '9D' and the retired
* cert key management DOs. */
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = get_key_algorithm_by_dobj (app, dobj, &mechanism);
if (err)
goto leave;
switch (mechanism)
{
case PIV_ALGORITHM_ECC_P256:
framelen = 1+32+32;
break;
case PIV_ALGORITHM_ECC_P384:
framelen = 1+48+48;
break;
case PIV_ALGORITHM_RSA:
framelen = 2048 / 8;
break;
default:
err = gpg_error (GPG_ERR_INTERNAL);
log_debug ("piv: unknown PIV mechanism %d while decrypting\n", mechanism);
goto leave;
}
/* Check that the ciphertext has the right length; due to internal
* convey mechanism using MPIs leading zero bytes might have been
* lost. Adjust for this. Unfortunately the ciphertext might have
* also been prefixed with a leading zero to make it a positive
* number; that may be a too long frame and we need to adjust for
* this too. Note that for ECC those fixes are not reqquired
* because the first octet is always '04' to indicate an
* uncompressed point. */
if (indatalen > framelen)
{
if (mechanism == PIV_ALGORITHM_RSA
&& indatalen == framelen + 1 && !*indata)
{
indata_buffer = xtrycalloc (1, framelen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer, indata+1, framelen);
indata = indata_buffer;
indatalen = framelen;
}
else
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("piv: input of %zu octets too large for mechanism %d\n",
indatalen, mechanism);
goto leave;
}
}
if (indatalen < framelen)
{
indata_buffer = xtrycalloc (1, framelen);
if (!indata_buffer)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (indata_buffer+(framelen-indatalen), indata, indatalen);
indata = indata_buffer;
indatalen = framelen;
}
/* Now verify the Application PIN. */
err = verify_chv (app, ctrl, 0x80, 0, pincb, pincb_arg);
if (err)
return err;
/* Build the Dynamic Authentication Template. */
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x7c, (size_t)0, NULL, /* Constructed. */
(int)0x82, (size_t)0, "",
mechanism == PIV_ALGORITHM_RSA?
(int)0x81 : (int)0x85, (size_t)indatalen, indata,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
/* Note: the -1 requests command chaining. */
err = iso7816_general_authenticate (app_get_slot (app), -1,
mechanism, keyref,
apdudata, (int)apdudatalen, 0,
&outdata, &outdatalen);
if (err)
goto leave;
/* Parse the response. */
if (outdatalen && *outdata == 0x7c
&& (s = find_tlv (outdata, outdatalen, 0x82, &n)))
{
memmove (outdata, outdata + (s - outdata), n);
outdatalen = n;
}
else
{
err = gpg_error (GPG_ERR_CARD);
log_error ("piv: response does not contain a proper result\n");
goto leave;
}
leave:
if (err)
{
xfree (outdata);
*r_outdata = NULL;
*r_outdatalen = 0;
}
else
{
*r_outdata = outdata;
*r_outdatalen = outdatalen;
}
*r_info = 0;
xfree (apdudata);
xfree (indata_buffer);
return err;
}
/* Check whether a key for DOBJ already exists. We detect this by
* reading the certificate described by DOBJ. If FORCE is TRUE a
* diagnositic will be printed but no error returned if the key
* already exists. The flag GENERATING is used to select a
* diagnositic. */
static gpg_error_t
does_key_exist (app_t app, data_object_t dobj, int generating, int force)
{
void *relptr;
unsigned char *buffer;
size_t buflen;
int found;
relptr = get_one_do (app, dobj->tag, &buffer, &buflen, NULL);
found = (relptr && buflen);
xfree (relptr);
if (found && !force)
{
log_error (_("key already exists\n"));
return gpg_error (GPG_ERR_EEXIST);
}
if (found)
log_info (_("existing key will be replaced\n"));
else if (generating)
log_info (_("generating new key\n"));
else
log_info (_("writing new key\n"));
return 0;
}
/* Helper for do_writekey; here the RSA part. BUF, BUFLEN, and DEPTH
* are the current parser state of the S-expression with the key. */
static gpg_error_t
writekey_rsa (app_t app, data_object_t dobj, int keyref,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
const unsigned char *rsa_n = NULL;
const unsigned char *rsa_e = NULL;
const unsigned char *rsa_p = NULL;
const unsigned char *rsa_q = NULL;
unsigned char *rsa_dpm1 = NULL;
unsigned char *rsa_dqm1 = NULL;
unsigned char *rsa_qinv = NULL;
size_t rsa_n_len, rsa_e_len, rsa_p_len, rsa_q_len;
size_t rsa_dpm1_len, rsa_dqm1_len, rsa_qinv_len;
unsigned char *apdudata = NULL;
size_t apdudatalen;
unsigned char tmpl[1];
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'n': mpi = &rsa_n; mpi_len = &rsa_n_len; break;
case 'e': mpi = &rsa_e; mpi_len = &rsa_e_len; break;
case 'p': mpi = &rsa_p; mpi_len = &rsa_p_len; break;
case 'q': mpi = &rsa_q; mpi_len = &rsa_q_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters. */
if (!rsa_n || !rsa_e || !rsa_p || !rsa_q)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
/* Fixme: Shall we check whether n == pq ? */
if (opt.verbose)
log_info ("RSA private key size is %u bytes\n", (unsigned int)rsa_n_len);
/* Compute the dp, dq and u components. */
{
gcry_mpi_t mpi_e, mpi_p, mpi_q;
gcry_mpi_t mpi_dpm1 = gcry_mpi_snew (0);
gcry_mpi_t mpi_dqm1 = gcry_mpi_snew (0);
gcry_mpi_t mpi_qinv = gcry_mpi_snew (0);
gcry_mpi_t mpi_tmp = gcry_mpi_snew (0);
gcry_mpi_scan (&mpi_e, GCRYMPI_FMT_USG, rsa_e, rsa_e_len, NULL);
gcry_mpi_scan (&mpi_p, GCRYMPI_FMT_USG, rsa_p, rsa_p_len, NULL);
gcry_mpi_scan (&mpi_q, GCRYMPI_FMT_USG, rsa_q, rsa_q_len, NULL);
gcry_mpi_sub_ui (mpi_tmp, mpi_p, 1);
gcry_mpi_invm (mpi_dpm1, mpi_e, mpi_tmp);
gcry_mpi_sub_ui (mpi_tmp, mpi_q, 1);
gcry_mpi_invm (mpi_dqm1, mpi_e, mpi_tmp);
gcry_mpi_invm (mpi_qinv, mpi_q, mpi_p);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dpm1, &rsa_dpm1_len, mpi_dpm1);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_dqm1, &rsa_dqm1_len, mpi_dqm1);
gcry_mpi_aprint (GCRYMPI_FMT_USG, &rsa_qinv, &rsa_qinv_len, mpi_qinv);
gcry_mpi_release (mpi_e);
gcry_mpi_release (mpi_p);
gcry_mpi_release (mpi_q);
gcry_mpi_release (mpi_dpm1);
gcry_mpi_release (mpi_dqm1);
gcry_mpi_release (mpi_qinv);
gcry_mpi_release (mpi_tmp);
}
err = concat_tlv_list (1, &apdudata, &apdudatalen,
(int)0x01, (size_t)rsa_p_len, rsa_p,
(int)0x02, (size_t)rsa_q_len, rsa_q,
(int)0x03, (size_t)rsa_dpm1_len, rsa_dpm1,
(int)0x04, (size_t)rsa_dqm1_len, rsa_dqm1,
(int)0x05, (size_t)rsa_qinv_len, rsa_qinv,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
err = iso7816_send_apdu (app_get_slot (app),
-1, /* Use command chaining. */
0, /* Class */
0xfe, /* Ins: Yubikey Import Asym. Key. */
PIV_ALGORITHM_RSA, /* P1 */
keyref, /* P2 */
apdudatalen,/* Lc */
apdudata, /* data */
NULL, NULL, NULL);
if (err)
goto leave;
/* Write the public key to the cert object. */
xfree (apdudata);
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x81, (size_t)rsa_n_len, rsa_n,
(int)0x82, (size_t)rsa_e_len, rsa_e,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
tmpl[0] = PIV_ALGORITHM_RSA;
err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)apdudatalen, apdudata,
(int)0, (size_t)0, NULL);
leave:
xfree (rsa_dpm1);
xfree (rsa_dqm1);
xfree (rsa_qinv);
xfree (apdudata);
return err;
}
/* Helper for do_writekey; here the ECC part. BUF, BUFLEN, and DEPTH
* are the current parser state of the S-expression with the key. */
static gpg_error_t
writekey_ecc (app_t app, data_object_t dobj, int keyref,
const unsigned char *buf, size_t buflen, int depth)
{
gpg_error_t err;
const unsigned char *tok;
size_t toklen;
int last_depth1, last_depth2;
int mechanism = 0;
const unsigned char *ecc_q = NULL;
const unsigned char *ecc_d = NULL;
size_t ecc_q_len, ecc_d_len;
unsigned char *apdudata = NULL;
size_t apdudatalen;
unsigned char tmpl[1];
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
{
err = gpg_error (GPG_ERR_UNKNOWN_SEXP);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && toklen == 5 && !memcmp (tok, "curve", 5))
{
char *name;
const char *xname;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
name = xtrymalloc (toklen+1);
if (!name)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (name, tok, toklen);
name[toklen] = 0;
/* Canonicalize the curve name. We use the openpgp
* functions here because Libgcrypt has no generic curve
* alias lookup feature and the PIV supported curves are
* also supported by OpenPGP. */
xname = openpgp_oid_to_curve (openpgp_curve_to_oid (name, NULL, NULL),
0);
xfree (name);
if (xname && !strcmp (xname, "nistp256"))
mechanism = PIV_ALGORITHM_ECC_P256;
else if (xname && !strcmp (xname, "nistp384"))
mechanism = PIV_ALGORITHM_ECC_P384;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
goto leave;
}
}
else if (tok && toklen == 1)
{
const unsigned char **mpi;
size_t *mpi_len;
switch (*tok)
{
case 'q': mpi = &ecc_q; mpi_len = &ecc_q_len; break;
case 'd': mpi = &ecc_d; mpi_len = &ecc_d_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if (mpi && *mpi)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (tok && mpi)
{
/* Strip off leading zero bytes and save. */
for (;toklen && !*tok; toklen--, tok++)
;
*mpi = tok;
*mpi_len = toklen;
}
}
/* Skip until end of list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
goto leave;
}
/* Check that we have all parameters. */
if (!mechanism || !ecc_q || !ecc_d)
{
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if (opt.verbose)
log_info ("ECC private key size is %u bytes\n", (unsigned int)ecc_d_len);
err = concat_tlv_list (1, &apdudata, &apdudatalen,
(int)0x06, (size_t)ecc_d_len, ecc_d,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
err = iso7816_send_apdu (app_get_slot (app),
-1, /* Use command chaining. */
0, /* Class */
0xfe, /* Ins: Yubikey Import Asym. Key. */
mechanism, /* P1 */
keyref, /* P2 */
apdudatalen,/* Lc */
apdudata, /* data */
NULL, NULL, NULL);
if (err)
goto leave;
/* Write the public key to the cert object. */
xfree (apdudata);
err = concat_tlv_list (0, &apdudata, &apdudatalen,
(int)0x86, (size_t)ecc_q_len, ecc_q,
(int)0, (size_t)0, NULL);
if (err)
goto leave;
tmpl[0] = mechanism;
err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)apdudatalen, apdudata,
(int)0, (size_t)0, NULL);
leave:
xfree (apdudata);
return err;
}
/* Write a key to a slot. This command requires proprietary
- * extensions of the PIV specification and is thus only implemnted for
+ * extensions of the PIV specification and is thus only implemented for
* supported card types. The input is a canonical encoded
* S-expression with the secret key in KEYDATA and its length (for
* assertion) in KEYDATALEN. KEYREFSTR needs to be the usual 2
* hexdigit slot number prefixed with "PIV." PINCB and PINCB_ARG are
* not used for PIV cards.
*
* Supported FLAGS are:
* APP_WRITEKEY_FLAG_FORCE Overwrite existing key.
*/
static gpg_error_t
do_writekey (app_t app, ctrl_t ctrl,
const char *keyrefstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
int force = !!(flags & APP_WRITEKEY_FLAG_FORCE);
data_object_t dobj;
int keyref;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth;
(void)ctrl;
(void)pincb;
(void)pincb_arg;
if (!app->app_local->flags.yubikey)
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Check keyref and test whether a key already exists. */
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = does_key_exist (app, dobj, 0, force);
if (err)
goto leave;
/* Parse the S-expression with the key. */
buf = keydata;
buflen = keydatalen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if (!tok || toklen != 11 || memcmp ("private-key", tok, toklen))
{
if (!tok)
;
else if (toklen == 21 && !memcmp ("protected-private-key", tok, toklen))
log_info ("protected-private-key passed to writekey\n");
else if (toklen == 20 && !memcmp ("shadowed-private-key", tok, toklen))
log_info ("shadowed-private-key passed to writekey\n");
err = gpg_error (GPG_ERR_BAD_SECKEY);
goto leave;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
goto leave;
/* First clear an existing key. We do this by writing an empty 7f49
* tag. This will return GPG_ERR_NO_PUBKEY on a later read. */
flush_cached_data (app, dobj->tag);
err = put_data (app_get_slot (app), dobj->tag,
(int)0x7f49, (size_t)0, "",
(int)0, (size_t)0, NULL);
if (err)
{
log_error ("piv: failed to clear the cert DO %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
/* Divert to the algo specific implementation. */
if (tok && toklen == 3 && memcmp ("rsa", tok, toklen) == 0)
err = writekey_rsa (app, dobj, keyref, buf, buflen, depth);
else if (tok && toklen == 3 && memcmp ("ecc", tok, toklen) == 0)
err = writekey_ecc (app, dobj, keyref, buf, buflen, depth);
else
err = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
if (err)
{
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
log_error (_("failed to store the key: %s\n"), gpg_strerror (err));
}
leave:
return err;
}
/* Parse an RSA response object, consisting of the content of tag
* 0x7f49, into a gcrypt s-expression object and store that R_SEXP.
* On error NULL is stored at R_SEXP. */
static gpg_error_t
genkey_parse_rsa (const unsigned char *data, size_t datalen,
gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *m, *e;
unsigned char *mbuf = NULL;
unsigned char *ebuf = NULL;
size_t mlen, elen;
*r_sexp = NULL;
m = find_tlv (data, datalen, 0x0081, &mlen);
if (!m)
{
log_error (_("response does not contain the RSA modulus\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
e = find_tlv (data, datalen, 0x0082, &elen);
if (!e)
{
log_error (_("response does not contain the RSA public exponent\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
for (; mlen && !*m; mlen--, m++) /* Strip leading zeroes */
;
for (; elen && !*e; elen--, e++) /* Strip leading zeroes */
;
mbuf = xtrymalloc (mlen + 1);
if (!mbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (mlen && (*m & 0x80))
{
*mbuf = 0;
memcpy (mbuf+1, m, mlen);
mlen++;
}
else
memcpy (mbuf, m, mlen);
ebuf = xtrymalloc (elen + 1);
if (!ebuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
/* Prepend numbers with a 0 if needed. */
if (elen && (*e & 0x80))
{
*ebuf = 0;
memcpy (ebuf+1, e, elen);
elen++;
}
else
memcpy (ebuf, e, elen);
err = gcry_sexp_build (r_sexp, NULL, "(public-key(rsa(n%b)(e%b)))",
(int)mlen, mbuf, (int)elen, ebuf);
leave:
xfree (mbuf);
xfree (ebuf);
return err;
}
/* Parse an ECC response object, consisting of the content of tag
* 0x7f49, into a gcrypt s-expression object and store that R_SEXP.
* On error NULL is stored at R_SEXP. MECHANISM specifies the
* curve. */
static gpg_error_t
genkey_parse_ecc (const unsigned char *data, size_t datalen, int mechanism,
gcry_sexp_t *r_sexp)
{
gpg_error_t err;
const unsigned char *ecc_q;
size_t ecc_qlen;
const char *curve;
*r_sexp = NULL;
ecc_q = find_tlv (data, datalen, 0x0086, &ecc_qlen);
if (!ecc_q)
{
log_error (_("response does not contain the EC public key\n"));
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (mechanism == PIV_ALGORITHM_ECC_P256)
curve = "nistp256";
else if (mechanism == PIV_ALGORITHM_ECC_P384)
curve = "nistp384";
else
{
err = gpg_error (GPG_ERR_BUG); /* Call with wrong parameters. */
goto leave;
}
err = gcry_sexp_build (r_sexp, NULL, "(public-key(ecc(curve%s)(q%b)))",
curve, (int)ecc_qlen, ecc_q);
leave:
return err;
}
/* Create a new keypair for KEYREF. If KEYTYPE is NULL a default
* keytype is selected, else it may be one of the strings:
* "rsa2048", "nistp256, or "nistp384".
*
* Supported FLAGS are:
* APP_GENKEY_FLAG_FORCE Overwrite existing key.
*
* Note that CREATETIME is not used for PIV cards.
*
* Because there seems to be no way to read the public key we need to
* retrieve it from a certificate. The GnuPG system however requires
* the use of app_readkey to fetch the public key from the card to
* create the certificate; to support this we temporary store the
* generated public key in the local context for use by app_readkey.
*/
static gpg_error_t
do_genkey (app_t app, ctrl_t ctrl, const char *keyrefstr, const char *keytype,
unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
data_object_t dobj;
unsigned char *buffer = NULL;
size_t buflen;
int force = !!(flags & APP_GENKEY_FLAG_FORCE);
int mechanism;
time_t start_at;
int keyref;
unsigned char tmpl[5];
size_t tmpllen;
const unsigned char *keydata;
size_t keydatalen;
(void)ctrl;
(void)createtime;
(void)pincb;
(void)pincb_arg;
if (!keytype)
keytype = "rsa2048";
if (!strcmp (keytype, "rsa2048"))
mechanism = PIV_ALGORITHM_RSA;
else if (!strcmp (keytype, "nistp256"))
mechanism = PIV_ALGORITHM_ECC_P256;
else if (!strcmp (keytype, "nistp384"))
mechanism = PIV_ALGORITHM_ECC_P384;
else
return gpg_error (GPG_ERR_UNKNOWN_CURVE);
/* We flush the cache to increase the I/O traffic before a key
* generation. This _might_ help the card to gather more entropy
* and is anyway a prerequisite for does_key_exist. */
flush_cached_data (app, 0);
/* Check whether a key already exists. */
dobj = find_dobj_by_keyref (app, keyrefstr);
if ((keyref = keyref_from_dobj (dobj)) == -1)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
err = does_key_exist (app, dobj, 1, force);
if (err)
goto leave;
/* Create the key. */
log_info (_("please wait while key is being generated ...\n"));
start_at = time (NULL);
tmpl[0] = 0xac;
tmpl[1] = 3;
tmpl[2] = 0x80;
tmpl[3] = 1;
tmpl[4] = mechanism;
tmpllen = 5;
err = iso7816_generate_keypair (app_get_slot (app), 0, 0, keyref,
tmpl, tmpllen, 0, &buffer, &buflen);
if (err)
{
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
log_error (_("generating key failed\n"));
return err;
}
{
int nsecs = (int)(time (NULL) - start_at);
log_info (ngettext("key generation completed (%d second)\n",
"key generation completed (%d seconds)\n",
nsecs), nsecs);
}
/* Parse the result and store it as an s-expression in a dedicated
* cache for later retrieval by app_readkey. */
keydata = find_tlv (buffer, buflen, 0x7F49, &keydatalen);
if (!keydata || !keydatalen)
{
err = gpg_error (GPG_ERR_CARD);
log_error (_("response does not contain the public key data\n"));
goto leave;
}
tmpl[0] = mechanism;
flush_cached_data (app, dobj->tag);
err = put_data (app_get_slot (app), dobj->tag,
(int)0x80, (size_t)1, tmpl,
(int)0x7f49, (size_t)keydatalen, keydata,
(int)0, (size_t)0, NULL);
if (err)
{
log_error ("piv: failed to write key to the cert DO %s: %s\n",
dobj->keyref, gpg_strerror (err));
goto leave;
}
leave:
xfree (buffer);
return err;
}
/* Map some names to an OID. */
static const unsigned char *
map_curve_name_to_oid (const unsigned char *name, size_t *namelenp)
{
if (*namelenp == 8 && !memcmp (name, "nistp256", 8))
{
*namelenp = 19;
return "1.2.840.10045.3.1.7";
}
if (*namelenp == 8 && !memcmp (name, "nistp384", 8))
{
*namelenp = 12;
return "1.3.132.0.34";
}
if (*namelenp == 8 && !memcmp (name, "nistp521", 8))
{
*namelenp = 12;
return "1.3.132.0.35";
}
return name;
}
/* Communication object for my_cmp_public_key. */
struct my_cmp_public_key_parm_s {
int curve_seen;
};
/* Compare function used with cmp_canon_sexp. */
static int
my_cmp_public_key (void *opaque, int depth,
const unsigned char *aval, size_t alen,
const unsigned char *bval, size_t blen)
{
struct my_cmp_public_key_parm_s *parm = opaque;
(void)depth;
if (parm->curve_seen)
{
/* Last token was "curve" - canonicalize its argument. */
parm->curve_seen = 0;
aval = map_curve_name_to_oid (aval, &alen);
bval = map_curve_name_to_oid (bval, &blen);
}
else if (alen == 5 && !memcmp (aval, "curve", 5))
parm->curve_seen = 1;
else
parm->curve_seen = 0;
if (alen > blen)
return 1;
else if (alen < blen)
return -1;
else
return memcmp (aval, bval, alen);
}
/* Write the certificate (CERT,CERTLEN) to the card at CERTREFSTR.
* CERTREFSTR is either the OID of the certificate's container data
* object or of the form "PIV.<two_hexdigit_keyref>". */
static gpg_error_t
do_writecert (app_t app, ctrl_t ctrl,
const char *certrefstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *cert, size_t certlen)
{
gpg_error_t err;
data_object_t dobj;
unsigned char *pk = NULL;
unsigned char *orig_pk = NULL;
size_t pklen, orig_pklen;
struct my_cmp_public_key_parm_s cmp_parm = { 0 };
(void)ctrl;
(void)pincb; /* Not used; instead authentication is needed. */
(void)pincb_arg;
if (!certlen)
return gpg_error (GPG_ERR_INV_CERT_OBJ);
dobj = find_dobj_by_keyref (app, certrefstr);
if (!dobj || !*dobj->keyref)
return gpg_error (GPG_ERR_INV_ID);
flush_cached_data (app, dobj->tag);
/* Check that the public key parameters from the certificate match
* an already stored key. Note that we do not allow writing a
* certificate if no key has yet been created (GPG_ERR_NOT_FOUND) or
* if there is a problem reading the public key from the certificate
* GPG_ERR_NO_PUBKEY). We enforce this because otherwise the only
* way to detect whether a key exists is by trying to use that
* key. */
err = do_readkey (app, ctrl, certrefstr, 0, &orig_pk, &orig_pklen);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_NO_SECKEY); /* Use a better error code. */
goto leave;
}
/* Compare pubkeys. */
err = app_help_pubkey_from_cert (cert, certlen, &pk, &pklen);
if (err)
goto leave; /* No public key in new certificate. */
if (cmp_canon_sexp (orig_pk, orig_pklen, pk, pklen,
my_cmp_public_key, &cmp_parm))
{
err = gpg_error (GPG_ERR_CONFLICT);
goto leave;
}
flush_cached_data (app, dobj->tag);
err = put_data (app_get_slot (app), dobj->tag,
(int)0x70, (size_t)certlen, cert,/* Certificate */
(int)0x71, (size_t)1, "", /* No compress */
(int)0xfe, (size_t)0, "", /* Empty LRC. */
(int)0, (size_t)0, NULL);
/* A PIN is not required, thus use a better error code. */
if (gpg_err_code (err) == GPG_ERR_BAD_PIN)
err = gpg_error (GPG_ERR_NO_AUTH);
if (err)
log_error ("piv: failed to write cert to %s: %s\n",
dobj->keyref, gpg_strerror (err));
leave:
xfree (pk);
xfree (orig_pk);
return err;
}
/* Process the various keygrip based info requests. */
static gpg_error_t
do_with_keygrip (app_t app, ctrl_t ctrl, int action,
const char *want_keygripstr, int capability)
{
gpg_error_t err;
char *keygripstr = NULL;
char *serialno = NULL;
char idbuf[20];
int data = 0;
int i, tag, dummy_got_cert;
/* First a quick check for valid parameters. */
switch (action)
{
case KEYGRIP_ACTION_LOOKUP:
if (!want_keygripstr)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
break;
case KEYGRIP_ACTION_SEND_DATA:
data = 1;
break;
case KEYGRIP_ACTION_WRITE_STATUS:
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* Allocate the s/n string if needed. */
if (action != KEYGRIP_ACTION_LOOKUP)
{
serialno = app_get_serialno (app);
if (!serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
for (i = 0; (tag = data_objects[i].tag); i++)
{
if (!data_objects[i].keypair)
continue;
xfree (keygripstr);
if (get_keygrip_by_tag (app, tag, &keygripstr, &dummy_got_cert))
continue;
if (action == KEYGRIP_ACTION_LOOKUP)
{
if (!strcmp (keygripstr, want_keygripstr))
{
err = 0; /* Found */
goto leave;
}
}
else if (!want_keygripstr || !strcmp (keygripstr, want_keygripstr))
{
const char *usage;
if (data_objects[i].usage)
usage = data_objects[i].usage;
else
usage = "-";
if (capability == GCRY_PK_USAGE_SIGN)
{
if (strcmp (data_objects[i].keyref, "9C"))
continue;
}
if (capability == GCRY_PK_USAGE_ENCR)
{
if (strcmp (data_objects[i].usage, "e"))
continue;
}
if (capability == GCRY_PK_USAGE_AUTH)
{
if (strcmp (data_objects[i].keyref, "9A"))
continue;
}
snprintf (idbuf, sizeof idbuf, "PIV.%s", data_objects[i].keyref);
send_keyinfo (ctrl, data, keygripstr, serialno, idbuf, usage);
if (want_keygripstr)
{
err = 0; /* Found */
goto leave;
}
}
}
/* Return an error so that the dispatcher keeps on looping over the
* other applications. For clarity we use a different error code
* when listing all keys. Note that in lookup mode WANT_KEYGRIPSTR
* is not NULL. */
if (!want_keygripstr)
err = gpg_error (GPG_ERR_TRUE);
else
err = gpg_error (GPG_ERR_NOT_FOUND);
leave:
xfree (keygripstr);
xfree (serialno);
return err;
}
/* Prepare a reselect of another application. This is used by cards
* which support on-the-fly switching between applications. The
* function is called to give us a chance to save state for a future
* reselect of us again. */
static gpg_error_t
do_prep_reselect (app_t app, ctrl_t ctrl)
{
gpg_error_t err;
(void)app;
(void)ctrl;
err = 0;
return err;
}
/* Reselect the application. This is used by cards which support
* on-the-fly switching between applications. */
static gpg_error_t
do_reselect (app_t app, ctrl_t ctrl)
{
gpg_error_t err;
(void)ctrl;
/* An extra check which should not be necessary because the caller
* should have made sure that a re-select is only called for
* appropriate cards. */
if (!app->app_local->flags.yubikey)
return gpg_error (GPG_ERR_NOT_SUPPORTED);
err = iso7816_select_application (app_get_slot (app),
piv_aid, sizeof piv_aid, 0x0001);
return err;
}
/* Check if AID is the correct one. */
static gpg_error_t
do_check_aid (app_t app, ctrl_t ctrl, const unsigned char *aid, size_t aidlen)
{
(void)app;
(void)ctrl;
if (aidlen >= sizeof piv_aid
&& memcmp (aid, piv_aid, sizeof piv_aid) == 0)
return 0;
return gpg_error (GPG_ERR_WRONG_CARD);
}
/* Select the PIV application on the card in SLOT. This function must
* be used before any other PIV application functions. */
gpg_error_t
app_select_piv (app_t app)
{
int slot = app_get_slot (app);
gpg_error_t err;
unsigned char *apt = NULL;
size_t aptlen;
const unsigned char *s;
size_t n;
void *relptr1 = NULL;
/* Note that we select using the AID without the 2 octet version
* number. This allows for better reporting of future specs. We
* need to use the use-zero-for-P2-flag. */
err = iso7816_select_application_ext (slot, piv_aid, sizeof piv_aid, 0x0001,
&apt, &aptlen);
if (err)
goto leave;
app->apptype = APPTYPE_PIV;
app->did_chv1 = 0;
app->did_chv2 = 0;
app->did_chv3 = 0;
app->app_local = NULL;
/* Check the Application Property Template. */
if (opt.verbose)
{
/* We use a separate log_info to avoid the "DBG:" prefix. */
log_info ("piv: APT=");
log_printhex (apt, aptlen, "");
}
s = find_tlv (apt, aptlen, 0x4F, &n);
/* Some cards (new Yubikey) return only the PIX, while others
* (old Yubikey, PivApplet) return the RID+PIX.
* Sample APTs:
* Yubikey 5.4.3: 6111 4f06 000010000100 7907 4f05 a000000308
* SCE7.0-G-F-P : 610f 4f06 001000010000 7905 a000000308
*/
if (app->card->cardtype == CARDTYPE_SCE7
&& s && apt && aptlen == 17
&& !memcmp (apt, ("\x61\x0f\x4f\x06\x00\x10\x00\x01"
"\x00\x00\x79\x05\xa0\x00\x00\x03\x08"), aptlen))
{
if (opt.verbose)
log_info ("piv: assuming G&D SCE7.0-G-F-P\n");
app->appversion = 0x0100; /* Let's assume this. */
goto apt_checked;
}
if (!s || !((n == 6 && !memcmp (s, piv_aid+5, 4))
|| (n == 11 && !memcmp (s, piv_aid, 9))))
{
/* The PIX does not match. */
log_error ("piv: missing or invalid DO 0x4F in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (s[n-2] != 1 || s[n-1] != 0)
{
log_error ("piv: unknown PIV version %u.%u\n", s[4], s[5]);
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
app->appversion = ((s[n-2] << 8) | s[n-1]);
s = find_tlv (apt, aptlen, 0x79, &n);
if (!s || n < 7)
{
log_error ("piv: missing or invalid DO 0x79 in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
s = find_tlv (s, n, 0x4F, &n);
/* Some cards may also return the full AID instead of just
* the 5-byte RID here. */
if (!s || !(n == 5 || n == 11) || memcmp (s, piv_aid, 5))
{
/* The RID does not match. */
log_error ("piv: missing or invalid DO 0x79.4F in APT\n");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
apt_checked:
app->app_local = xtrycalloc (1, sizeof *app->app_local);
if (!app->app_local)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (app->card->cardtype == CARDTYPE_YUBIKEY)
app->app_local->flags.yubikey = 1;
/* If we don't have a s/n construct it from the CHUID. */
if (!APP_CARD(app)->serialno)
{
unsigned char *chuid;
size_t chuidlen;
relptr1 = get_one_do (app, 0x5FC102, &chuid, &chuidlen, NULL);
if (!relptr1)
log_error ("piv: CHUID not found\n");
else
{
s = find_tlv (chuid, chuidlen, 0x34, &n);
if (!s || n != 16)
{
log_error ("piv: Card UUID %s in CHUID\n",
s? "invalid":"missing");
if (opt.debug && s)
log_printhex (s, n, "got");
}
else
{
APP_CARD(app)->serialno = xtrymalloc (n);
if (!APP_CARD(app)->serialno)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (APP_CARD(app)->serialno, s, n);
APP_CARD(app)->serialnolen = n;
err = app_munge_serialno (APP_CARD(app));
if (err)
goto leave;
}
}
}
/* FIXME: Parse the optional and conditional DOs in the APT. */
if (opt.verbose)
dump_all_do (slot);
app->fnc.deinit = do_deinit;
app->fnc.prep_reselect = do_prep_reselect;
app->fnc.reselect = do_reselect;
app->fnc.learn_status = do_learn_status;
app->fnc.readcert = do_readcert;
app->fnc.readkey = do_readkey;
app->fnc.getattr = do_getattr;
app->fnc.setattr = do_setattr;
app->fnc.writecert = do_writecert;
app->fnc.writekey = do_writekey;
app->fnc.genkey = do_genkey;
app->fnc.sign = do_sign;
app->fnc.auth = do_auth;
app->fnc.decipher = do_decipher;
app->fnc.change_pin = do_change_chv;
app->fnc.check_pin = do_check_chv;
app->fnc.with_keygrip = do_with_keygrip;
app->fnc.check_aid = do_check_aid;
leave:
xfree (relptr1);
xfree (apt);
if (err)
do_deinit (app);
return err;
}
diff --git a/scd/app.c b/scd/app.c
index 09a148416..9a5150fd1 100644
--- a/scd/app.c
+++ b/scd/app.c
@@ -1,2791 +1,2791 @@
/* app.c - Application selection.
* Copyright (C) 2003, 2004, 2005 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <npth.h>
#include "scdaemon.h"
#include "../common/exechelp.h"
#include "iso7816.h"
#include "apdu.h"
#include "../common/tlv.h"
#include "../common/membuf.h"
/* Forward declaration of internal function. */
static gpg_error_t
select_additional_application_internal (card_t card, apptype_t req_apptype);
static gpg_error_t
send_serialno_and_app_status (card_t card, int with_apps, ctrl_t ctrl);
static gpg_error_t run_reselect (ctrl_t ctrl, card_t c, app_t a, app_t a_prev);
/*
* Multiple readers, single writer (MRSW) lock.
*/
struct mrsw_lock
{
npth_mutex_t lock;
npth_cond_t cond;
int num_readers_active;
int num_writers_waiting;
int writer_active;
npth_cond_t notify_cond;
};
/* MRSW lock to protect the list of cards.
*
* This structure is used for serializing access to the list of cards
* (by CARD_TOP). While allowing multiple accesses by different
* connections as "r" access (for a CARD in the list), "w" access to
* update the list is only possible with a single thread.
*
* Each use of a CARD (in the list) does "r" access.
*
* For "w" access, the app_send_devinfo function may wait on any
* change of the list. For other cases of "w" access are opening new
* card or removal of card, updating the list of card.
*
* Note that for serializing access to each CARD (and its associated
* applications) itself, it is done separately by another mutex with
* lock_card/unlock_card.
*/
static struct mrsw_lock card_list_lock;
/* A list of card contexts. A card is a collection of applications
* (described by app_t) on the same physical token. */
static card_t card_top;
/* The list of application names and their select function. If no
* specific application is selected the first available application on
* a card is selected. */
struct app_priority_list_s
{
apptype_t apptype;
char const *name;
gpg_error_t (*select_func)(app_t);
};
static struct app_priority_list_s app_priority_list[] =
{{ APPTYPE_OPENPGP , "openpgp", app_select_openpgp },
{ APPTYPE_PIV , "piv", app_select_piv },
{ APPTYPE_NKS , "nks", app_select_nks },
{ APPTYPE_P15 , "p15", app_select_p15 },
{ APPTYPE_GELDKARTE, "geldkarte", app_select_geldkarte },
{ APPTYPE_DINSIG , "dinsig", app_select_dinsig },
{ APPTYPE_SC_HSM , "sc-hsm", app_select_sc_hsm },
{ APPTYPE_NONE , NULL, NULL }
/* APPTYPE_UNDEFINED is special and not listed here. */
};
/* Map a cardtype to a string. Never returns NULL. */
const char *
strcardtype (cardtype_t t)
{
switch (t)
{
case CARDTYPE_GENERIC: return "generic";
case CARDTYPE_GNUK: return "gnuk";
case CARDTYPE_YUBIKEY: return "yubikey";
case CARDTYPE_ZEITCONTROL: return "zeitcontrol";
case CARDTYPE_SCE7: return "smartcafe";
}
return "?";
}
/* Map an application type to a string. Never returns NULL. */
const char *
strapptype (apptype_t t)
{
int i;
for (i=0; app_priority_list[i].apptype; i++)
if (app_priority_list[i].apptype == t)
return app_priority_list[i].name;
return t == APPTYPE_UNDEFINED? "undefined" : t? "?" : "none";
}
const char *
xstrapptype (app_t app)
{
return app? strapptype (app->apptype) : "[no_app]";
}
/* Return the apptype for NAME. */
static apptype_t
apptype_from_name (const char *name)
{
int i;
if (!name)
return APPTYPE_NONE;
for (i=0; app_priority_list[i].apptype; i++)
if (!ascii_strcasecmp (app_priority_list[i].name, name))
return app_priority_list[i].apptype;
if (!ascii_strcasecmp ("undefined", name))
return APPTYPE_UNDEFINED;
return APPTYPE_NONE;
}
/* Return the apptype for KEYREF. This is the first part of the
* KEYREF up to the dot. */
static apptype_t
apptype_from_keyref (const char *keyref)
{
int i;
unsigned int n;
const char *s;
if (!keyref)
return APPTYPE_NONE;
s = strchr (keyref, '.');
if (!s || s == keyref || !s[1])
return APPTYPE_NONE; /* Not a valid keyref. */
n = s - keyref;
for (i=0; app_priority_list[i].apptype; i++)
if (strlen (app_priority_list[i].name) == n
&& !ascii_strncasecmp (app_priority_list[i].name, keyref, n))
return app_priority_list[i].apptype;
return APPTYPE_NONE;
}
/* Return true if both serilanumbers are the same. This function
* takes care of some peculiarities. */
static int
is_same_serialno (const unsigned char *sna, size_t snalen,
const unsigned char *snb, size_t snblen)
{
if ((!sna && !snb) || (!snalen && !snblen))
return 1;
if (!sna || !snb)
return 0; /* One of them is NULL. (Both NULL tested above). */
if (snalen != snblen)
return 0; /* (No special cases for this below). */
/* The special case for OpenPGP cards where we ignore the version
* bytes (vvvv). Example: D276000124010304000500009D8A0000
* ^^^^^^^^^^^^vvvvmmmmssssssssrrrr */
if (snalen == 16 && !memcmp (sna, "\xD2\x76\x00\x01\x24\x01", 6))
{
if (memcmp (snb, "\xD2\x76\x00\x01\x24\x01", 6))
return 0; /* No */
return !memcmp (sna + 8, snb + 8, 8);
}
return !memcmp (sna, snb, snalen);
}
/* Initialization function to change the default app_priority_list.
* LIST is a list of comma or space separated strings with application
* names. Unknown names will only result in warning message.
* Application not mentioned in LIST are used in their original order
* after the given once. */
void
app_update_priority_list (const char *arg)
{
struct app_priority_list_s save;
char **names;
int i, j, idx;
names = strtokenize (arg, ", ");
if (!names)
log_fatal ("strtokenize failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
idx = 0;
for (i=0; names[i]; i++)
{
ascii_strlwr (names[i]);
for (j=0; j < i; j++)
if (!strcmp (names[j], names[i]))
break;
if (j < i)
{
log_info ("warning: duplicate application '%s' in priority list\n",
names[i]);
continue;
}
for (j=idx; app_priority_list[j].name; j++)
if (!strcmp (names[i], app_priority_list[j].name))
break;
if (!app_priority_list[j].name)
{
log_info ("warning: unknown application '%s' in priority list\n",
names[i]);
continue;
}
save = app_priority_list[idx];
app_priority_list[idx] = app_priority_list[j];
app_priority_list[j] = save;
idx++;
}
log_assert (idx < DIM (app_priority_list));
xfree (names);
for (i=0; app_priority_list[i].name; i++)
log_info ("app priority %d: %s\n", i, app_priority_list[i].name);
}
static void
print_progress_line (void *opaque, const char *what, int pc, int cur, int tot)
{
ctrl_t ctrl = opaque;
char line[100];
if (ctrl)
{
snprintf (line, sizeof line, "%s %c %d %d", what, pc, cur, tot);
send_status_direct (ctrl, "PROGRESS", line);
}
}
/* Lock the CARD. This function shall be used right before calling
* any of the actual application functions to serialize access to the
* reader. We do this always even if the card is not actually used.
* This allows an actual connection to assume that it never shares a
* card (while performing one command). Returns 0 on success; only
* then the unlock_reader function must be called after returning from
* the handler. Right now we assume a that a reader has just one
* card; this may eventually need refinement. */
static gpg_error_t
lock_card (card_t card, ctrl_t ctrl)
{
if (npth_mutex_lock (&card->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("failed to acquire CARD lock for %p: %s\n",
card, gpg_strerror (err));
return err;
}
apdu_set_progress_cb (card->slot, print_progress_line, ctrl);
apdu_set_prompt_cb (card->slot, popup_prompt, ctrl);
return 0;
}
/* Release a lock on a card. See lock_reader(). */
static void
unlock_card (card_t card)
{
apdu_set_progress_cb (card->slot, NULL, NULL);
apdu_set_prompt_cb (card->slot, NULL, NULL);
if (npth_mutex_unlock (&card->lock))
{
gpg_error_t err = gpg_error_from_syserror ();
log_error ("failed to release CARD lock for %p: %s\n",
card, gpg_strerror (err));
}
}
static void
card_list_r_lock (void)
{
npth_mutex_lock (&card_list_lock.lock);
while (card_list_lock.num_writers_waiting
|| card_list_lock.writer_active)
npth_cond_wait (&card_list_lock.cond, &card_list_lock.lock);
card_list_lock.num_readers_active++;
npth_mutex_unlock (&card_list_lock.lock);
}
static void
card_list_r_unlock (void)
{
npth_mutex_lock (&card_list_lock.lock);
if (--card_list_lock.num_readers_active == 0)
npth_cond_broadcast (&card_list_lock.cond);
npth_mutex_unlock (&card_list_lock.lock);
}
static void
card_list_w_lock (void)
{
npth_mutex_lock (&card_list_lock.lock);
card_list_lock.num_writers_waiting++;
while (card_list_lock.num_readers_active
|| card_list_lock.writer_active)
npth_cond_wait (&card_list_lock.cond, &card_list_lock.lock);
card_list_lock.num_writers_waiting--;
card_list_lock.writer_active++;
npth_mutex_unlock (&card_list_lock.lock);
}
static void
card_list_w_unlock (void)
{
npth_mutex_lock (&card_list_lock.lock);
card_list_lock.writer_active--;
npth_cond_broadcast (&card_list_lock.cond);
npth_mutex_unlock (&card_list_lock.lock);
}
static void
card_list_signal (void)
{
npth_cond_broadcast (&card_list_lock.notify_cond);
}
static void
card_list_wait (void)
{
npth_mutex_lock (&card_list_lock.lock);
card_list_lock.writer_active--;
npth_cond_broadcast (&card_list_lock.cond);
npth_cond_wait (&card_list_lock.notify_cond, &card_list_lock.lock);
card_list_lock.num_writers_waiting++;
while (card_list_lock.num_readers_active
|| card_list_lock.writer_active)
npth_cond_wait (&card_list_lock.cond, &card_list_lock.lock);
card_list_lock.num_writers_waiting--;
card_list_lock.writer_active++;
npth_mutex_unlock (&card_list_lock.lock);
}
/* This function may be called to print information pertaining to the
* current state of this module to the log. */
void
app_dump_state (void)
{
card_t c;
app_t a;
card_list_r_lock ();
for (c = card_top; c; c = c->next)
{
log_info ("app_dump_state: card=%p slot=%d type=%s refcount=%u\n",
c, c->slot, strcardtype (c->cardtype), c->ref_count);
/* FIXME The use of log_info risks a race! */
for (a=c->app; a; a = a->next)
log_info ("app_dump_state: app=%p type='%s'\n",
a, strapptype (a->apptype));
}
card_list_r_unlock ();
}
/*
* Send information for all available cards.
*
* With KEEP_LOOPING=0, it only outputs once.
* With KEEP_LOOPING<0, it keeps looping, until it detects no device.
* With KEEP_LOOPING>0, it keeps looping forever.
*/
gpg_error_t
app_send_devinfo (ctrl_t ctrl, int keep_looping)
{
card_t c;
app_t a;
int no_device;
card_list_w_lock ();
while (1)
{
no_device = (card_top == NULL);
if (no_device && keep_looping < 0)
break;
send_status_direct (ctrl, "DEVINFO_START", "");
for (c = card_top; c; c = c->next)
{
char *serialno;
char card_info[80];
serialno = card_get_serialno (c);
snprintf (card_info, sizeof card_info, "DEVICE %s %s",
strcardtype (c->cardtype), serialno);
xfree (serialno);
for (a = c->app; a; a = a->next)
send_status_direct (ctrl, card_info, strapptype (a->apptype));
}
send_status_direct (ctrl, "DEVINFO_END", "");
if (keep_looping == 0)
break;
card_list_wait ();
}
card_list_w_unlock ();
return no_device ? gpg_error (GPG_ERR_NOT_FOUND): 0;
}
/* Check whether the application NAME is allowed. This does not mean
we have support for it though. */
static int
is_app_allowed (const char *name)
{
strlist_t l;
for (l=opt.disabled_applications; l; l = l->next)
if (!strcmp (l->d, name))
return 0; /* no */
return 1; /* yes */
}
/* This function is mainly used by the serialno command to check for
* an application conflict which may appear if the serialno command is
* used to request a specific application and the connection has
* already done a select_application. Return values are:
* 0 - No conflict
* GPG_ERR_FALSE - Another application is in use but it is possible
* to switch to the requested application.
* Other code - Switching is not possible.
*
* If SERIALNO_BIN is not NULL a conflict is only asserted if the
* serialno of the card matches.
*/
gpg_error_t
check_application_conflict (card_t card, const char *name,
const unsigned char *serialno_bin,
size_t serialno_bin_len)
{
apptype_t apptype;
if (!card || !name)
return 0;
if (!card->app)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED); /* Should not happen. */
if (serialno_bin && card->serialno)
{
if (!is_same_serialno (card->serialno, card->serialnolen,
serialno_bin, serialno_bin_len))
return 0; /* The card does not match the requested S/N. */
}
apptype = apptype_from_name (name);
if (card->app->apptype == apptype)
return 0;
if (card->app->apptype == APPTYPE_UNDEFINED)
return 0;
if (card->cardtype == CARDTYPE_YUBIKEY)
{
if (card->app->apptype == APPTYPE_OPENPGP)
{
/* Current app is OpenPGP. */
if (!ascii_strcasecmp (name, "piv"))
return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */
}
else if (card->app->apptype == APPTYPE_PIV)
{
/* Current app is PIV. */
if (!ascii_strcasecmp (name, "openpgp"))
return gpg_error (GPG_ERR_FALSE); /* Switching allowed. */
}
}
log_info ("application '%s' in use - can't switch\n",
strapptype (card->app->apptype));
return gpg_error (GPG_ERR_CONFLICT);
}
gpg_error_t
card_reset (card_t card)
{
gpg_error_t err = 0;
int sw;
sw = apdu_reset (card->slot);
if (sw)
err = gpg_error (GPG_ERR_CARD_RESET);
card->reset_requested = 1;
scd_kick_the_loop ();
gnupg_sleep (1);
return err;
}
/* Return the card type from (ATR,ATRLEN) or CARDTYPE_GENERIC in case
* of error or if the ATR was not found. If ATR is NULL, SLOT is used
* to retrieve the ATR from the reader. */
static cardtype_t
atr_to_cardtype (int slot, const unsigned char *atr, size_t atrlen)
{
#define X(a) ((unsigned char const *)(a))
static struct
{
size_t atrlen;
unsigned char const *atr;
cardtype_t type;
} atrlist[] = {
{ 19, X("\x3b\xf9\x96\x00\x00\x80\x31\xfe"
"\x45\x53\x43\x45\x37\x20\x0f\x00\x20\x46\x4e"),
CARDTYPE_SCE7 },
{ 0 }
};
#undef X
unsigned char *atrbuf = NULL;
cardtype_t cardtype = 0;
int i;
if (atr)
{
atrbuf = apdu_get_atr (slot, &atrlen);
if (!atrbuf)
return 0;
atr = atrbuf;
}
for (i=0; atrlist[i].atrlen; i++)
if (atrlist[i].atrlen == atrlen
&& !memcmp (atrlist[i].atr, atr, atrlen))
{
cardtype = atrlist[i].type;
break;
}
xfree (atrbuf);
return cardtype;
}
static gpg_error_t
app_new_register (int slot, ctrl_t ctrl, const char *name,
int periodical_check_needed)
{
gpg_error_t err = 0;
card_t card = NULL;
app_t app = NULL;
unsigned char *result = NULL;
size_t resultlen;
int want_undefined;
int i;
/* Need to allocate a new card object */
card = xtrycalloc (1, sizeof *card);
if (!card)
{
err = gpg_error_from_syserror ();
log_info ("error allocating context: %s\n", gpg_strerror (err));
return err;
}
card->slot = slot;
card->card_status = (unsigned int)-1;
if (npth_mutex_init (&card->lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("error initializing mutex: %s\n", gpg_strerror (err));
xfree (card);
return err;
}
err = lock_card (card, ctrl);
if (err)
{
xfree (card);
return err;
}
want_undefined = (name && !strcmp (name, "undefined"));
/* Try to read the GDO file first to get a default serial number.
We skip this if the undefined application has been requested. */
if (!want_undefined)
{
err = iso7816_select_file (slot, 0x3F00, 1);
if (gpg_err_code (err) == GPG_ERR_CARD)
{
/* Might be SW==0x7D00. Let's test whether it is a Yubikey
* by selecting its manager application and then reading the
* config. */
static char const yk_aid[] =
{ 0xA0, 0x00, 0x00, 0x05, 0x27, 0x47, 0x11, 0x17 }; /*MGR*/
static char const otp_aid[] =
{ 0xA0, 0x00, 0x00, 0x05, 0x27, 0x20, 0x01 }; /*OTP*/
unsigned char *buf;
size_t buflen;
const unsigned char *s0;
unsigned char formfactor;
size_t n;
if (!iso7816_select_application (slot, yk_aid, sizeof yk_aid,
0x0001)
&& !iso7816_apdu_direct (slot, "\x00\x1d\x00\x00\x00", 5, 0,
NULL, &buf, &buflen))
{
card->cardtype = CARDTYPE_YUBIKEY;
if (opt.verbose)
{
log_info ("Yubico: config=");
log_printhex (buf, buflen, "");
}
/* We skip the first byte which seems to be the total
* length of the config data. */
if (buflen > 1)
{
s0 = find_tlv (buf+1, buflen-1, 0x04, &n); /* Form factor */
formfactor = (s0 && n == 1)? *s0 : 0;
s0 = find_tlv (buf+1, buflen-1, 0x02, &n); /* Serial */
if (s0 && n <= 4)
{
card->serialno = xtrymalloc (3 + 1 + 4);
if (card->serialno)
{
card->serialnolen = 3 + 1 + 4;
card->serialno[0] = 0xff;
card->serialno[1] = 0x02;
card->serialno[2] = 0x0;
card->serialno[3] = formfactor;
memset (card->serialno + 4, 0, 4 - n);
memcpy (card->serialno + 4 + 4 - n, s0, n);
err = app_munge_serialno (card);
}
}
s0 = find_tlv (buf+1, buflen-1, 0x05, &n); /* version */
if (s0 && n == 3)
card->cardversion = ((s0[0]<<16)|(s0[1]<<8)|s0[2]);
else if (!s0)
{
/* No version - this is not a Yubikey 5. We now
* switch to the OTP app and take the first
* three bytes of the response as version
* number. */
xfree (buf);
buf = NULL;
if (!iso7816_select_application_ext (slot,
otp_aid, sizeof otp_aid,
1, &buf, &buflen)
&& buflen > 3)
card->cardversion = ((buf[0]<<16)|(buf[1]<<8)|buf[2]);
}
}
xfree (buf);
}
else
card->cardtype = atr_to_cardtype (slot, NULL, 0);
}
else /* Got 3F00 */
{
unsigned char *atr;
size_t atrlen;
/* This is heuristics to identify different implementations. */
/* FIXME: The first two checks are pretty OpenPGP card specific. */
atr = apdu_get_atr (slot, &atrlen);
if (atr)
{
if (atrlen == 21 && atr[2] == 0x11)
card->cardtype = CARDTYPE_GNUK;
else if (atrlen == 21 && atr[7] == 0x75)
card->cardtype = CARDTYPE_ZEITCONTROL;
else
card->cardtype = atr_to_cardtype (slot, atr, atrlen);
xfree (atr);
}
}
if (!err && card->cardtype != CARDTYPE_YUBIKEY)
err = iso7816_select_file (slot, 0x2F02, 0);
if (!err && card->cardtype != CARDTYPE_YUBIKEY)
err = iso7816_read_binary (slot, 0, 0, &result, &resultlen);
if (!err && card->cardtype != CARDTYPE_YUBIKEY)
{
size_t n;
const unsigned char *p;
p = find_tlv_unchecked (result, resultlen, 0x5A, &n);
if (p)
resultlen -= (p-result);
if (p && n > resultlen && n == 0x0d && resultlen+1 == n)
{
/* The object does not fit into the buffer. This is an
invalid encoding (or the buffer is too short. However, I
have some test cards with such an invalid encoding and
therefore I use this ugly workaround to return something
I can further experiment with. */
log_info ("enabling BMI testcard workaround\n");
n--;
}
if (p && n <= resultlen)
{
/* The GDO file is pretty short, thus we simply reuse it for
storing the serial number. */
memmove (result, p, n);
card->serialno = result;
card->serialnolen = n;
err = app_munge_serialno (card);
if (err)
goto leave;
}
else
xfree (result);
result = NULL;
}
}
/* Allocate a new app object. */
app = xtrycalloc (1, sizeof *app);
if (!app)
{
err = gpg_error_from_syserror ();
log_info ("error allocating app context: %s\n", gpg_strerror (err));
goto leave;
}
card->app = app;
app->card = card;
/* Figure out the application to use. */
if (want_undefined)
{
/* We switch to the "undefined" application only if explicitly
requested. */
app->apptype = APPTYPE_UNDEFINED;
/* Clear the error so that we don't run through the application
* selection chain. */
err = 0;
}
else
{
/* For certain error codes, there is no need to try more. */
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT
|| gpg_err_code (err) == GPG_ERR_ENODEV)
goto leave;
/* Set a default error so that we run through the application
* selection chain. */
err = gpg_error (GPG_ERR_NOT_FOUND);
}
/* Find the first available app if NAME is NULL or the matching
* NAME but only if that application is also enabled. */
for (i=0; err && app_priority_list[i].name; i++)
{
if (is_app_allowed (app_priority_list[i].name)
&& (!name || !strcmp (name, app_priority_list[i].name)))
err = app_priority_list[i].select_func (app);
}
if (err && name && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE)
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
leave:
if (err)
{
if (name)
log_info ("can't select application '%s': %s\n",
name, gpg_strerror (err));
else
log_info ("no supported card application found: %s\n",
gpg_strerror (err));
unlock_card (card);
xfree (app);
xfree (card);
return err;
}
card->periodical_check_needed = periodical_check_needed;
card->next = card_top;
card_top = card;
unlock_card (card);
return 0;
}
/* If called with NAME as NULL, select the best fitting application
* and return its card context; otherwise select the application with
* NAME and return its card context. Returns an error code and stores
* NULL at R_CARD if no application was found or no card is present. */
gpg_error_t
select_application (ctrl_t ctrl, const char *name,
int scan, const unsigned char *serialno_bin,
size_t serialno_bin_len)
{
gpg_error_t err = 0;
card_t card, card_prev = NULL;
card_list_w_lock ();
ctrl->card_ctx = NULL;
if (scan || !card_top)
{
struct dev_list *l;
int new_card = 0;
/* Scan the devices to find new device(s). */
err = apdu_dev_list_start (opt.reader_port, &l);
if (err)
{
card_list_w_unlock ();
return err;
}
while (1)
{
int slot;
int periodical_check_needed_this;
slot = apdu_open_reader (l);
if (slot < 0)
break;
periodical_check_needed_this = apdu_connect (slot);
if (periodical_check_needed_this < 0)
{
/* We close a reader with no card. */
err = gpg_error (GPG_ERR_ENODEV);
}
else
{
err = app_new_register (slot, ctrl, name,
periodical_check_needed_this);
new_card++;
}
if (err)
{
pincache_put (ctrl, slot, NULL, NULL, NULL, 0);
apdu_close_reader (slot);
}
}
apdu_dev_list_finish (l);
/* If new device(s), kick the scdaemon loop. */
if (new_card)
scd_kick_the_loop ();
}
for (card = card_top; card; card = card->next)
{
lock_card (card, ctrl);
if (serialno_bin == NULL)
break;
if (is_same_serialno (card->serialno, card->serialnolen,
serialno_bin, serialno_bin_len))
break;
unlock_card (card);
card_prev = card;
}
if (card)
{
err = check_application_conflict (card, name, NULL, 0);
if (!err)
ctrl->current_apptype = card->app ? card->app->apptype : APPTYPE_NONE;
else if (gpg_err_code (err) == GPG_ERR_FALSE)
{
apptype_t req_apptype = apptype_from_name (name);
if (!req_apptype)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
{
err = select_additional_application_internal (card, req_apptype);
if (!err)
ctrl->current_apptype = req_apptype;
}
}
if (!err)
{
card->ref_count++;
ctrl->card_ctx = card;
if (card_prev)
{
card_prev->next = card->next;
card->next = card_top;
card_top = card;
}
}
unlock_card (card);
}
else
err = gpg_error (GPG_ERR_ENODEV);
card_list_w_unlock ();
return err;
}
/* Switch the current card for the session CTRL and print a SERIALNO
* status line on success. (SERIALNO, SERIALNOLEN) is the binary s/n
* of the card to switch to. */
gpg_error_t
app_switch_current_card (ctrl_t ctrl,
const unsigned char *serialno, size_t serialnolen)
{
gpg_error_t err;
card_t card, cardtmp;
card_list_r_lock ();
cardtmp = ctrl->card_ctx;
if (!cardtmp)
{
err = gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
goto leave;
}
if (serialno && serialnolen)
{
for (card = card_top; card; card = card->next)
{
if (is_same_serialno (card->serialno, card->serialnolen,
serialno, serialnolen))
break;
}
if (!card)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Note: We do not lock CARD and CARDTMP here because we only
* swap the context of the current session and there is no
* chance of a context switch. This also works if the card
* stays the same. */
ctrl->card_ctx = card;
card->ref_count++;
card_unref_locked (cardtmp);
}
/* Print the status line. */
err = send_serialno_and_app_status (ctrl->card_ctx, 0, ctrl);
leave:
card_list_r_unlock ();
return err;
}
static gpg_error_t
select_additional_application_internal (card_t card, apptype_t req_apptype)
{
gpg_error_t err = 0;
app_t app;
int i;
/* Check that the requested app has not yet been put onto the list. */
for (app = card->app; app; app = app->next)
if (app->apptype == req_apptype)
{
/* We already got this one. Note that in this case we don't
* make it the current one but it doesn't matter because
* maybe_switch_app will do that anyway. */
err = 0;
app = NULL;
goto leave;
}
/* Allocate a new app object. */
app = xtrycalloc (1, sizeof *app);
if (!app)
{
err = gpg_error_from_syserror ();
log_info ("error allocating app context: %s\n", gpg_strerror (err));
goto leave;
}
app->card = card;
/* Find the app and run the select. */
for (i=0; app_priority_list[i].apptype; i++)
{
if (app_priority_list[i].apptype == req_apptype
&& is_app_allowed (app_priority_list[i].name))
{
err = app_priority_list[i].select_func (app);
break;
}
}
if (!app_priority_list[i].apptype
|| (err && gpg_err_code (err) != GPG_ERR_OBJ_TERM_STATE))
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
if (err)
goto leave;
/* Add this app. We make it the current one to avoid an extra
* reselect by maybe_switch_app after the select we just did. */
app->next = card->app;
card->app = app;
log_info ("added app '%s' to the card context and switched\n",
strapptype (app->apptype));
leave:
if (err)
xfree (app);
return err;
}
/* Add all possible additional applications to the card context but do
* not change the current one. This currently works only for Yubikeys. */
static gpg_error_t
select_all_additional_applications_internal (ctrl_t ctrl, card_t card)
{
gpg_error_t err = 0;
apptype_t candidates[3];
int i, j;
int any_new = 0;
if (card->cardtype == CARDTYPE_YUBIKEY)
{
candidates[0] = APPTYPE_OPENPGP;
candidates[1] = APPTYPE_PIV;
candidates[2] = APPTYPE_NONE;
}
else
{
candidates[0] = APPTYPE_NONE;
}
/* Find the app and run the select. */
for (i=0; app_priority_list[i].apptype; i++)
{
app_t app, app_r, app_prev;
for (j=0; candidates[j]; j++)
if (candidates[j] == app_priority_list[i].apptype
&& is_app_allowed (app_priority_list[i].name))
break;
if (!candidates[j])
continue;
for (app = card->app; app; app = app->next)
if (app->apptype == candidates[j])
break;
if (app)
continue; /* Already on the list of apps. */
app = xtrycalloc (1, sizeof *app);
if (!app)
{
err = gpg_error_from_syserror ();
log_info ("error allocating app context: %s\n", gpg_strerror (err));
goto leave;
}
app->card = card;
err = app_priority_list[i].select_func (app);
if (err)
{
log_error ("error selecting additional app '%s': %s - skipped\n",
strapptype (candidates[j]), gpg_strerror (err));
err = 0;
xfree (app);
}
else
{
/* Append to the list of apps. */
app_prev = card->app;
for (app_r=app_prev->next; app_r; app_prev=app_r, app_r=app_r->next)
;
app_prev->next = app;
log_info ("added app '%s' to the card context\n",
strapptype (app->apptype));
any_new = 1;
}
}
/* If we found a new application we need to reselect the original
* application so that we are in a well defined state. */
if (!err && any_new && card->app && card->app->fnc.reselect)
err = run_reselect (ctrl, card, card->app, NULL);
leave:
return err;
}
/* This function needs to be called with the NAME of the new
* application to be selected on CARD. On success the application is
* added to the list of the card's active applications as currently
* active application. On error no new application is allocated.
* Selecting an already selected application has no effect. */
gpg_error_t
select_additional_application (card_t card, ctrl_t ctrl, const char *name)
{
gpg_error_t err = 0;
apptype_t req_apptype;
if (!name)
req_apptype = 0;
else
{
req_apptype = apptype_from_name (name);
if (!req_apptype)
return gpg_error (GPG_ERR_NOT_FOUND);
}
if (req_apptype)
{
err = select_additional_application_internal (card, req_apptype);
if (!err)
{
ctrl->current_apptype = req_apptype;
if (DBG_APP)
log_debug ("current_apptype is set to %s\n", name);
}
}
else
{
err = select_all_additional_applications_internal (ctrl, card);
}
return err;
}
char *
get_supported_applications (void)
{
int idx;
size_t nbytes;
char *buffer, *p;
const char *s;
for (nbytes=1, idx=0; (s=app_priority_list[idx].name); idx++)
nbytes += strlen (s) + 1 + 1;
buffer = xtrymalloc (nbytes);
if (!buffer)
return NULL;
for (p=buffer, idx=0; (s=app_priority_list[idx].name); idx++)
if (is_app_allowed (s))
p = stpcpy (stpcpy (p, s), ":\n");
*p = 0;
return buffer;
}
/* Deallocate the application. */
static void
deallocate_card (card_t card)
{
card_t c, c_prev = NULL;
app_t a, anext;
for (c = card_top; c; c = c->next)
if (c == card)
{
if (c_prev == NULL)
card_top = c->next;
else
c_prev->next = c->next;
break;
}
else
c_prev = c;
if (card->ref_count)
log_error ("releasing still used card context (%d)\n", card->ref_count);
for (a = card->app; a; a = anext)
{
if (a->fnc.deinit)
{
a->fnc.deinit (a);
a->fnc.deinit = NULL;
}
anext = a->next;
xfree (a);
}
xfree (card->serialno);
unlock_card (card);
xfree (card);
}
static card_t
do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str,
int capability)
{
int locked = 0;
card_t c;
app_t a, a_prev;
for (c = card_top; c; c = c->next)
{
if (lock_card (c, ctrl))
{
c = NULL;
goto leave_the_loop;
}
locked = 1;
a_prev = NULL;
for (a = c->app; a; a = a->next)
{
if (!a->fnc.with_keygrip || a->need_reset)
continue;
/* Note that we need to do a re-select even for the current
* app because the last selected application (e.g. after
* init) might be a different one and we do not run
* maybe_switch_app here. Of course we we do this only iff
* we have an additional app. */
if (c->app->next)
{
if (run_reselect (ctrl, c, a, a_prev))
continue;
}
a_prev = a;
if (DBG_APP)
log_debug ("slot %d, app %s: calling with_keygrip(%s)\n",
c->slot, xstrapptype (a),
action == KEYGRIP_ACTION_SEND_DATA? "send_data":
action == KEYGRIP_ACTION_WRITE_STATUS? "status":
action == KEYGRIP_ACTION_LOOKUP? "lookup":"?");
if (!a->fnc.with_keygrip (a, ctrl, action, keygrip_str, capability))
goto leave_the_loop; /* ACTION_LOOKUP succeeded. */
}
/* Select the first app again. */
if (c->app->next)
run_reselect (ctrl, c, c->app, a_prev);
unlock_card (c);
locked = 0;
}
leave_the_loop:
/* Force switching of the app if the selected one is not the current
* one. Changing the current apptype is sufficient to do this. */
if (c && c->app && c->app->apptype != a->apptype)
ctrl->current_apptype = a->apptype;
if (locked && c)
{
unlock_card (c);
locked = 0;
}
return c;
}
/* Locking access to the card-list and CARD, returns CARD. */
card_t
card_get (ctrl_t ctrl, const char *keygrip)
{
card_t card;
card_list_r_lock ();
if (keygrip)
card = do_with_keygrip (ctrl, KEYGRIP_ACTION_LOOKUP, keygrip, 0);
else
card = ctrl->card_ctx;
if (!card)
{
card_list_r_unlock ();
return NULL;
}
lock_card (card, NULL);
return card;
}
/* Release the lock of CARD and the card-list. */
void
card_put (card_t card)
{
/* We don't deallocate CARD here. Instead, we keep it. This is
useful so that a card does not get reset even if only one session
is using the card - this way the PIN cache and other cached data
are preserved. */
unlock_card (card);
card_list_r_unlock ();
}
/* This is the same as card_unref but assumes that CARD is already
* locked. */
void
card_unref_locked (card_t card)
{
if (!card)
return;
if (!card->ref_count)
log_bug ("tried to release an already released card context\n");
--card->ref_count;
}
/* The serial number may need some cosmetics. Do it here. This
function shall only be called once after a new serial number has
been put into APP->serialno.
Prefixes we use:
FF 00 00 = For serial numbers starting with an FF
FF 01 00 = Some german p15 cards return an empty serial number so the
serial number from the EF(TokenInfo) is used instead.
FF 02 00 = Serial number from Yubikey config.
This is normally not seen because we modify this here
to an OpenPGP Card s/n.
FF 7F 00 = No serialno.
All other serial numbers not starting with FF are used as they are.
*/
gpg_error_t
app_munge_serialno (card_t card)
{
if (card->cardtype == CARDTYPE_YUBIKEY
&& card->serialnolen == 3 + 1 + 4
&& !memcmp (card->serialno, "\xff\x02\x00", 3))
{
/* An example for a serial number is
* FF020001008A77C1
* ~~~~~~--~~~~~~~~
* ! ! !--------- 4 byte s/n
* ! !----------- Form factor
* !----------------- Our prefix
* Yubico seems to use the decimalized version of their S/N
- * as the OpenPGP card S/N. Thus in theory we can contruct the
+ * as the OpenPGP card S/N. Thus in theory we can construct the
* number from this information so that we do not rely on having
* the OpenPGP app enabled.
*/
unsigned long sn;
sn = card->serialno[4] * 16777216;
sn += card->serialno[5] * 65536;
sn += card->serialno[6] * 256;
sn += card->serialno[7];
if (sn <= 99999999ul)
{
char *buf = xtrymalloc (16);
if (!buf)
return gpg_error_from_syserror ();
memcpy (buf, "\xD2\x76\x00\x01\x24\x01", 6);
buf[6] = 0; /* Application version which we don't know */
buf[7] = 0; /* thus we use 0.0 and don't use this directly. */
buf[8] = 0; /* Manufacturer: Yubico (0x0006). */
buf[9] = 6;
buf[13] = (sn % 10);
sn /= 10;
buf[13] |= (sn % 10) << 4;
sn /= 10;
buf[12] = (sn % 10);
sn /= 10;
buf[12] |= (sn % 10) << 4;
sn /= 10;
buf[11] = (sn % 10);
sn /= 10;
buf[11] |= (sn % 10) << 4;
sn /= 10;
buf[10] = (sn % 10);
sn /= 10;
buf[10] |= (sn % 10) << 4;
sn /= 10;
buf[14] = 0; /* Last two bytes are RFU. */
buf[15] = 0;
xfree (card->serialno);
card->serialno = buf;
card->serialnolen = 16;
}
}
else if (card->serialnolen && card->serialno[0] == 0xff)
{
/* The serial number starts with our special prefix. This
requires that we put our default prefix "FF0000" in front. */
unsigned char *p = xtrymalloc (card->serialnolen + 3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\0", 3);
memcpy (p+3, card->serialno, card->serialnolen);
card->serialnolen += 3;
xfree (card->serialno);
card->serialno = p;
}
else if (!card->serialnolen)
{
unsigned char *p = xtrymalloc (3);
if (!p)
return gpg_error_from_syserror ();
memcpy (p, "\xff\x7f", 3);
card->serialnolen = 3;
xfree (card->serialno);
card->serialno = p;
}
return 0;
}
/* Retrieve the serial number of the card. The serial number is
returned as a malloced string (hex encoded) in SERIAL. Caller must
free SERIAL unless the function returns an error. */
char *
card_get_serialno (card_t card)
{
char *serial;
if (!card)
return NULL;
if (!card->serialnolen)
serial = xtrystrdup ("FF7F00");
else
serial = bin2hex (card->serialno, card->serialnolen, NULL);
return serial;
}
/* Same as card_get_serialno but takes an APP object. */
char *
app_get_serialno (app_t app)
{
if (!app || !app->card)
{
gpg_err_set_errno (0);
return NULL;
}
return card_get_serialno (app->card);
}
/* Return an allocated string with the serial number in a format to be
* show to the user. With NOFALLBACK set to true return NULL if such an
* abbreviated S/N is not available, else return the full serial
* number as a hex string. May return NULL on malloc problem. */
char *
card_get_dispserialno (card_t card, int nofallback)
{
char *result, *p;
unsigned long sn;
if (card && card->serialno && card->serialnolen == 3+1+4
&& !memcmp (card->serialno, "\xff\x02\x00", 3))
{
/* This is a 4 byte S/N of a Yubikey which seems to be printed
* on the token in decimal. Maybe they will print larger S/N
* also in decimal but we can't be sure, thus do it only for
* these 32 bit numbers. */
sn = card->serialno[4] * 16777216;
sn += card->serialno[5] * 65536;
sn += card->serialno[6] * 256;
sn += card->serialno[7];
if ((card->cardversion >> 16) >= 5)
result = xtryasprintf ("%lu %03lu %03lu",
(sn/1000000ul),
(sn/1000ul % 1000ul),
(sn % 1000ul));
else
result = xtryasprintf ("%lu", sn);
}
else if (card && card->cardtype == CARDTYPE_YUBIKEY)
{
/* Get back the printed Yubikey number from the OpenPGP AID
* Example: D2760001240100000006120808620000
*/
result = card_get_serialno (card);
if (result && strlen (result) >= 28 && !strncmp (result+16, "0006", 4))
{
sn = atoi_4 (result+20) * 10000;
sn += atoi_4 (result+24);
if ((card->cardversion >> 16) >= 5)
p = xtryasprintf ("%lu %03lu %03lu",
(sn/1000000ul),
(sn/1000ul % 1000ul),
(sn % 1000ul));
else
p = xtryasprintf ("%lu", sn);
if (p)
{
xfree (result);
result = p;
}
}
else if (nofallback)
{
xfree (result);
result = NULL;
}
}
else if (card && card->app && card->app->apptype == APPTYPE_OPENPGP)
{
/* Extract number from standard OpenPGP AID. */
result = card_get_serialno (card);
if (result && strlen (result) > 16+12)
{
memcpy (result, result+16, 4);
result[4] = ' ';
memcpy (result+5, result+20, 8);
result[13] = 0;
}
else if (nofallback)
{
xfree (result);
result = NULL;
}
}
else if (nofallback)
result = NULL; /* No Abbreviated S/N. */
else
result = card_get_serialno (card);
return result;
}
/* Same as card_get_dispserialno but takes an APP object. */
char *
app_get_dispserialno (app_t app, int nofallback)
{
if (!app || !app->card)
{
gpg_err_set_errno (0);
return NULL;
}
return card_get_dispserialno (app->card, nofallback);
}
/* Helper to run the reselect function. */
static gpg_error_t
run_reselect (ctrl_t ctrl, card_t c, app_t a, app_t a_prev)
{
gpg_error_t err;
if (!a->fnc.reselect)
{
log_info ("slot %d, app %s: re-select not implemented\n",
c->slot, xstrapptype (a));
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
}
/* Give the current app a chance to save some state before another
* app is selected. We ignore errors here because that state saving
* (e.g. putting PINs into a cache) is a convenience feature and not
* required to always work. */
if (a_prev && a_prev->fnc.prep_reselect)
{
if (a_prev->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = a_prev->fnc.prep_reselect (a_prev, ctrl);
if (err)
log_error ("slot %d, app %s: preparing re-select from %s failed: %s\n",
c->slot, xstrapptype (a),
xstrapptype (a_prev), gpg_strerror (err));
}
if (a->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = a->fnc.reselect (a, ctrl);
if (err)
{
log_error ("slot %d, app %s: error re-selecting: %s\n",
c->slot, xstrapptype (a), gpg_strerror (err));
return err;
}
if (DBG_APP)
log_debug ("slot %d, app %s: re-selected\n", c->slot, xstrapptype (a));
return 0;
}
/*
* Check external interference before each use of the application on
* card. Returns -1 when detecting some external interference.
* Returns 0 if not.
*
* Note: This kind of detection can't be perfect. At most, it may be
* possibly useful kludge, in some limited situations.
*/
static int
check_external_interference (app_t app, ctrl_t ctrl)
{
/*
* Only when a user is using Yubikey with pcsc-shared configuration,
* we need this detection. Otherwise, the card/token is under full
* control of scdaemon, there's no problem at all. However, if the
* APDU command has been used we better also check whether the AID
* is still valid.
*/
if (app && app->card && app->card->maybe_check_aid)
app->card->maybe_check_aid = 0;
else if (!opt.pcsc_shared || app->card->cardtype != CARDTYPE_YUBIKEY)
return 0;
if (app->fnc.check_aid)
{
unsigned char *aid;
size_t aidlen;
gpg_error_t err;
int slot = app_get_slot (app);
err = iso7816_get_data (slot, 0, 0x004F, &aid, &aidlen);
if (err)
return -1;
err = app->fnc.check_aid (app, ctrl, aid, aidlen);
xfree (aid);
if (err)
return -1;
}
return 0;
}
/* Check that the card has been initialized and whether we need to
* switch to another application on the same card. Switching means
* that the new active app will be moved to the head of the list at
* CARD->app. This function must be called with the card lock held. */
static gpg_error_t
maybe_switch_app (ctrl_t ctrl, card_t card, const char *keyref)
{
gpg_error_t err;
app_t app;
app_t app_prev = NULL;
apptype_t apptype;
if (!card->app)
return gpg_error (GPG_ERR_CARD_NOT_INITIALIZED);
if (card->maybe_check_aid && card->app->fnc.reselect
&& check_external_interference (card->app, ctrl))
{
if (DBG_APP)
log_debug ("slot %d, app %s: forced re-select due to direct APDU use\n",
card->slot, xstrapptype (card->app));
err = card->app->fnc.reselect (card->app, ctrl);
if (err)
log_error ("slot %d, app %s: forced re-select failed: %s - ignored\n",
card->slot, xstrapptype (card->app), gpg_strerror (err));
err = 0;
}
if (!ctrl->current_apptype)
{
/* For whatever reasons the current apptype has not been set -
* fix that and use the current app. */
if (DBG_APP)
log_debug ("slot %d: no current app switching to %s\n",
card->slot, strapptype (card->app->apptype));
ctrl->current_apptype = card->app->apptype;
return 0;
}
for (app = card->app; app; app = app->next)
if (app->apptype == ctrl->current_apptype)
break;
if (!app)
{
/* The current app is not supported by this card. Set the first
* app of the card as current. */
if (DBG_APP)
log_debug ("slot %d: current app %s not available switching to %s\n",
card->slot, strapptype (ctrl->current_apptype),
strapptype (card->app->apptype));
ctrl->current_apptype = card->app->apptype;
return 0;
}
if (DBG_APP)
log_debug ("slot %d: have=%s want=%s keyref=%s\n",
card->slot, strapptype (card->app->apptype),
strapptype (ctrl->current_apptype),
keyref? keyref:"[none]");
app = NULL;
if (keyref)
{
/* Switch based on the requested KEYREF. */
apptype = apptype_from_keyref (keyref);
if (apptype)
{
for (app = card->app; app; app_prev = app, app = app->next)
if (app->apptype == apptype)
break;
if (!app_prev && ctrl->current_apptype == card->app->apptype)
if (check_external_interference (app, ctrl) == 0)
return 0; /* Already the first app - no need to switch. */
}
else if (strlen (keyref) == 40)
{
/* This looks like a keygrip. Iterate over all apps to find
* the corresponding app. */
for (app = card->app; app; app_prev = app, app = app->next)
if (app->fnc.with_keygrip
&& !app->need_reset
&& !app->fnc.with_keygrip (app, ctrl,
KEYGRIP_ACTION_LOOKUP, keyref, 0))
break;
if (!app_prev && ctrl->current_apptype == card->app->apptype)
if (check_external_interference (app, ctrl) == 0)
return 0; /* Already the first app - no need to switch. */
}
}
if (!app)
{
/* Switch based on the current application of this connection or
* if a keyref based switch didn't worked. */
if (ctrl->current_apptype == card->app->apptype)
return 0; /* No need to switch. */
app_prev = card->app;
for (app = app_prev->next; app; app_prev = app, app = app->next)
if (app->apptype == ctrl->current_apptype)
break;
}
if (!app)
return gpg_error (GPG_ERR_WRONG_CARD);
err = run_reselect (ctrl, card, app, app_prev);
if (err)
return err;
/* Swap APP with the head of the app list if needed. Note that APP
* is not the head of the list. */
if (app_prev)
{
app_prev->next = app->next;
app->next = card->app;
card->app = app;
}
if (opt.verbose)
log_info ("slot %d, app %s: %s\n",
card->slot, xstrapptype (app),
app_prev? "switched":"re-selected");
ctrl->current_apptype = app->apptype;
return 0;
}
/* Helper for app_write_learn_status. */
static gpg_error_t
write_learn_status_core (card_t card, app_t app, ctrl_t ctrl,
unsigned int flags)
{
gpg_error_t err;
/* We do not send CARD and APPTYPE if only keypairinfo is requested. */
if (!(flags & APP_LEARN_FLAG_KEYPAIRINFO))
{
if (card && card->cardtype)
send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype));
if (card && card->cardversion)
send_status_printf (ctrl, "CARDVERSION", "%X", card->cardversion);
if (app->apptype)
send_status_direct (ctrl, "APPTYPE", strapptype (app->apptype));
if (app->appversion)
send_status_printf (ctrl, "APPVERSION", "%X", app->appversion);
}
if (app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
{
err = app->fnc.learn_status (app, ctrl, flags);
if (err && (flags & APP_LEARN_FLAG_REREAD))
app->need_reset = 1;
}
return err;
}
/* Write out the application specific status lines for the LEARN
command. */
gpg_error_t
app_write_learn_status (card_t card, ctrl_t ctrl, unsigned int flags)
{
gpg_error_t err, err2, tmperr;
app_t app, last_app;
int any_reselect = 0;
/* Always make sure that the current app for this connection has
* been selected and is at the top of the list. */
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.learn_status)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
err = write_learn_status_core (card, card->app, ctrl, flags);
if (!err && card->app->fnc.reselect && (flags & APP_LEARN_FLAG_MULTI))
{
/* The current app has the reselect feature so that we can
* loop over all other apps which are capable of a reselect
* and finally reselect the first app again. Note that we
* did the learn for the currently selected card above. */
app = last_app = card->app;
for (app = app->next; app && !err; app = app->next)
if (app->fnc.reselect)
{
if (last_app && last_app->fnc.prep_reselect)
{
tmperr = last_app->fnc.prep_reselect (last_app, ctrl);
if (tmperr)
log_info ("slot %d, app %s:"
" preparing re-select from %s failed: %s\n",
card->slot, xstrapptype (app),
xstrapptype (last_app),
gpg_strerror (tmperr));
}
any_reselect = 1;
err = app->fnc.reselect (app, ctrl);
if (!err)
{
last_app = app;
err = write_learn_status_core (NULL, app, ctrl, flags);
}
}
app = card->app;
if (any_reselect)
{
if (last_app && last_app->fnc.prep_reselect)
{
tmperr = last_app->fnc.prep_reselect (last_app, ctrl);
if (tmperr)
log_info ("slot %d, app %s:"
" preparing re-select from %s failed: %s\n",
card->slot, xstrapptype (app),
xstrapptype (last_app), gpg_strerror (tmperr));
}
err2 = app->fnc.reselect (app, ctrl);
if (err2)
{
log_error ("error re-selecting '%s': %s\n",
strapptype(app->apptype), gpg_strerror (err2));
if (!err)
err = err2;
}
}
}
}
return err;
}
/* Read the certificate with id CERTID (as returned by learn_status in
the CERTINFO status lines) and return it in the freshly allocated
buffer put into CERT and the length of the certificate put into
CERTLEN. */
gpg_error_t
app_readcert (card_t card, ctrl_t ctrl, const char *certid,
unsigned char **cert, size_t *certlen)
{
gpg_error_t err;
if ((err = maybe_switch_app (ctrl, card, certid)))
;
else if (!card->app->fnc.readcert)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling readcert(%s)\n",
card->slot, xstrapptype (card->app), certid);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.readcert (card->app, certid, cert, certlen);
}
return err;
}
/* Read the key with ID KEYID. On success a canonical encoded
* S-expression with the public key will get stored at PK and its
* length (for assertions) at PKLEN; the caller must release that
* buffer. On error NULL will be stored at PK and PKLEN and an error
* code returned. If the key is not required NULL may be passed for
* PK; this makes sense if the APP_READKEY_FLAG_INFO has also been set.
*
* This function might not be supported by all applications. */
gpg_error_t
app_readkey (card_t card, ctrl_t ctrl, const char *keyid, unsigned int flags,
unsigned char **pk, size_t *pklen)
{
gpg_error_t err;
if (pk)
*pk = NULL;
if (pklen)
*pklen = 0;
if (!keyid)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, keyid)))
;
else if (!card->app->fnc.readkey)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling readkey(%s)\n",
card->slot, xstrapptype (card->app), keyid);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.readkey (card->app, ctrl, keyid, flags, pk, pklen);
}
return err;
}
/* Perform a GETATTR operation. */
gpg_error_t
app_getattr (card_t card, ctrl_t ctrl, const char *name)
{
gpg_error_t err;
if (!name || !*name)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (name && !strcmp (name, "CARDTYPE"))
{
send_status_direct (ctrl, "CARDTYPE", strcardtype (card->cardtype));
}
else if (name && !strcmp (name, "APPTYPE"))
{
send_status_direct (ctrl, "APPTYPE", strapptype (card->app->apptype));
}
else if (name && !strcmp (name, "SERIALNO"))
{
char *serial;
serial = app_get_serialno (card->app);
if (!serial)
err = gpg_error (GPG_ERR_INV_VALUE);
else
{
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
}
}
else if (!card->app->fnc.getattr)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling getattr(%s)\n",
card->slot, xstrapptype (card->app), name);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.getattr (card->app, ctrl, name);
}
return err;
}
/* Perform a SETATTR operation. */
gpg_error_t
app_setattr (card_t card, ctrl_t ctrl, const char *name,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *value, size_t valuelen)
{
gpg_error_t err;
if (!name || !*name || !value)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.setattr)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling setattr(%s)\n",
card->slot, xstrapptype (card->app), name);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.setattr (card->app, ctrl, name, pincb, pincb_arg,
value, valuelen);
}
return err;
}
/* Create the signature and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
app_sign (card_t card, ctrl_t ctrl, const char *keyidstr, int hashalgo,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
if (!indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.sign)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling sign(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.sign (card->app, ctrl, keyidstr, hashalgo,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen);
}
if (opt.verbose)
log_info ("operation sign result: %s\n", gpg_strerror (err));
return err;
}
/* Create the signature using the INTERNAL AUTHENTICATE command and
return the allocated result in OUTDATA. If a PIN is required the
PINCB will be used to ask for the PIN; it should return the PIN in
an allocated buffer and put it into PIN. */
gpg_error_t
app_auth (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen )
{
gpg_error_t err;
if (!outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.auth)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (card->app->apptype != APPTYPE_OPENPGP
&& (!indata || !indatalen))
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_APP)
log_debug ("slot %d app %s: calling auth(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.auth (card->app, ctrl, keyidstr,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen);
}
if (opt.verbose)
log_info ("operation auth result: %s\n", gpg_strerror (err));
return err;
}
/* Decrypt the data in INDATA and return the allocated result in OUTDATA.
If a PIN is required the PINCB will be used to ask for the PIN; it
should return the PIN in an allocated buffer and put it into PIN. */
gpg_error_t
app_decipher (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const void *indata, size_t indatalen,
unsigned char **outdata, size_t *outdatalen,
unsigned int *r_info)
{
gpg_error_t err;
*r_info = 0;
if (!indata || !indatalen || !outdata || !outdatalen || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.decipher)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling decipher(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.decipher (card->app, ctrl, keyidstr,
pincb, pincb_arg,
indata, indatalen,
outdata, outdatalen,
r_info);
}
if (opt.verbose)
log_info ("operation decipher result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITECERT operation. */
gpg_error_t
app_writecert (card_t card, ctrl_t ctrl,
const char *certidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *data, size_t datalen)
{
gpg_error_t err;
if (!certidstr || !*certidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, certidstr)))
;
else if (!card->app->fnc.writecert)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling writecert(%s)\n",
card->slot, xstrapptype (card->app), certidstr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.writecert (card->app, ctrl, certidstr,
pincb, pincb_arg, data, datalen);
}
if (opt.verbose)
log_info ("operation writecert result: %s\n", gpg_strerror (err));
return err;
}
/* Perform the WRITEKEY operation. */
gpg_error_t
app_writekey (card_t card, ctrl_t ctrl,
const char *keyidstr, unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg,
const unsigned char *keydata, size_t keydatalen)
{
gpg_error_t err;
if (!keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, keyidstr)))
;
else if (!card->app->fnc.writekey)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling writekey(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.writekey (card->app, ctrl, keyidstr, flags,
pincb, pincb_arg, keydata, keydatalen);
}
if (opt.verbose)
log_info ("operation writekey result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a GENKEY operation. */
gpg_error_t
app_genkey (card_t card, ctrl_t ctrl, const char *keynostr,
const char *keytype, unsigned int flags, time_t createtime,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!keynostr || !*keynostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, keynostr)))
;
else if (!card->app->fnc.genkey)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling genkey(%s)\n",
card->slot, xstrapptype (card->app), keynostr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.genkey (card->app, ctrl, keynostr, keytype, flags,
createtime, pincb, pincb_arg);
}
if (opt.verbose)
log_info ("operation genkey result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a GET CHALLENGE operation. This function is special as it
directly accesses the card without any application specific
wrapper. */
gpg_error_t
app_get_challenge (card_t card, ctrl_t ctrl,
size_t nbytes, unsigned char *buffer)
{
(void)ctrl;
if (!nbytes || !buffer)
return gpg_error (GPG_ERR_INV_VALUE);
return iso7816_get_challenge (card->slot, nbytes, buffer);
}
/* Perform a CHANGE REFERENCE DATA or RESET RETRY COUNTER operation. */
gpg_error_t
app_change_pin (card_t card, ctrl_t ctrl, const char *chvnostr,
unsigned int flags,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!chvnostr || !*chvnostr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.change_pin)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling change_pin(%s)\n",
card->slot, xstrapptype (card->app), chvnostr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.change_pin (card->app, ctrl,
chvnostr, flags, pincb, pincb_arg);
}
if (opt.verbose)
log_info ("operation change_pin result: %s\n", gpg_strerror (err));
return err;
}
/* Perform a VERIFY operation without doing anything else. This may
be used to initialize a the PIN cache for long lasting other
operations. Its use is highly application dependent. */
gpg_error_t
app_check_pin (card_t card, ctrl_t ctrl, const char *keyidstr,
gpg_error_t (*pincb)(void*, const char *, char **),
void *pincb_arg)
{
gpg_error_t err;
if (!keyidstr || !*keyidstr || !pincb)
return gpg_error (GPG_ERR_INV_VALUE);
if ((err = maybe_switch_app (ctrl, card, NULL)))
;
else if (!card->app->fnc.check_pin)
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
else
{
if (DBG_APP)
log_debug ("slot %d app %s: calling check_pin(%s)\n",
card->slot, xstrapptype (card->app), keyidstr);
if (card->app->need_reset)
err = gpg_error (GPG_ERR_CARD_RESET);
else
err = card->app->fnc.check_pin (card->app, ctrl, keyidstr,
pincb, pincb_arg);
}
if (opt.verbose)
log_info ("operation check_pin result: %s\n", gpg_strerror (err));
return err;
}
#ifndef HAVE_W32_SYSTEM
static void
setup_env (void *arg)
{
char *v = arg;
putenv (v);
}
#endif
static void
report_change (int slot, int old_status, int cur_status)
{
char *homestr, *envstr;
char *fname;
char templ[50];
estream_t fp;
snprintf (templ, sizeof templ, "reader_%d.status", slot);
fname = make_filename (gnupg_homedir (), templ, NULL );
fp = es_fopen (fname, "w");
if (fp)
{
es_fprintf (fp, "%s\n",
(cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
es_fclose (fp);
}
xfree (fname);
homestr = make_filename (gnupg_homedir (), NULL);
if (gpgrt_asprintf (&envstr, "GNUPGHOME=%s", homestr) < 0)
log_error ("out of core while building environment\n");
else
{
gpg_error_t err;
const char *args[9];
char numbuf1[30], numbuf2[30], numbuf3[30];
gnupg_spawn_actions_t act = NULL;
sprintf (numbuf1, "%d", slot);
sprintf (numbuf2, "0x%04X", old_status);
sprintf (numbuf3, "0x%04X", cur_status);
args[0] = "--reader-port";
args[1] = numbuf1;
args[2] = "--old-code";
args[3] = numbuf2;
args[4] = "--new-code";
args[5] = numbuf3;
args[6] = "--status";
args[7] = ((cur_status & 1)? "USABLE":
(cur_status & 4)? "ACTIVE":
(cur_status & 2)? "PRESENT": "NOCARD");
args[8] = NULL;
fname = make_filename (gnupg_homedir (), "scd-event", NULL);
err = gnupg_spawn_actions_new (&act);
if (!err)
{
#ifndef HAVE_W32_SYSTEM
gnupg_spawn_actions_set_atfork (act, setup_env, envstr);
#endif
err = gnupg_process_spawn (fname, args, GNUPG_PROCESS_DETACHED,
act, NULL);
gnupg_spawn_actions_release (act);
}
if (err && gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("failed to run event handler '%s': %s\n",
fname, gpg_strerror (err));
xfree (fname);
xfree (envstr);
}
xfree (homestr);
}
int
scd_update_reader_status_file (void)
{
card_t card, card_next;
int periodical_check_needed = 0;
int reported = 0;
card_list_w_lock ();
for (card = card_top; card; card = card_next)
{
int sw;
unsigned int status;
lock_card (card, NULL);
card_next = card->next;
if (card->reset_requested)
{
/* Here is the post-processing of RESET request. */
status = 0;
card->reset_requested = 0;
}
else
{
sw = apdu_get_status (card->slot, 0, &status);
if (sw == SW_HOST_NO_READER)
{
/* Most likely the _reader_ has been unplugged. */
status = 0;
}
else if (sw)
{
/* Get status failed. Ignore that. */
if (card->periodical_check_needed)
periodical_check_needed = 1;
unlock_card (card);
continue;
}
}
if (card->card_status != status)
{
report_change (card->slot, card->card_status, status);
send_client_notifications (card, status == 0);
reported++;
if (status == 0)
{
if (DBG_APP)
log_debug ("Removal of a card: %d\n", card->slot);
pincache_put (NULL, card->slot, NULL, NULL, NULL, 0);
apdu_close_reader (card->slot);
deallocate_card (card);
}
else
{
card->card_status = status;
if (card->periodical_check_needed)
periodical_check_needed = 1;
unlock_card (card);
}
}
else
{
if (card->periodical_check_needed)
periodical_check_needed = 1;
unlock_card (card);
}
}
if (reported)
card_list_signal ();
card_list_w_unlock ();
return periodical_check_needed;
}
/* This function must be called once to initialize this module. This
has to be done before a second thread is spawned. We can't do the
static initialization because Pth emulation code might not be able
to do a static init; in particular, it is not possible for W32. */
gpg_error_t
initialize_module_command (void)
{
gpg_error_t err;
card_list_lock.num_readers_active = 0;
card_list_lock.num_writers_waiting = 0;
card_list_lock.writer_active = 0;
if (npth_mutex_init (&card_list_lock.lock, NULL))
{
err = gpg_error_from_syserror ();
log_error ("app: error initializing mutex: %s\n", gpg_strerror (err));
return err;
}
err = npth_cond_init (&card_list_lock.cond, NULL);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("npth_cond_init failed: %s\n", gpg_strerror (err));
return err;
}
err = npth_cond_init (&card_list_lock.notify_cond, NULL);
if (err)
{
err = gpg_error_from_syserror ();
log_error ("npth_cond_init failed: %s\n", gpg_strerror (err));
return err;
}
return apdu_init ();
}
/* Sort helper for app_send_card_list. */
static int
compare_card_list_items (const void *arg_a, const void *arg_b)
{
const card_t a = *(const card_t *)arg_a;
const card_t b = *(const card_t *)arg_b;
return a->slot - b->slot;
}
/* Helper for send_card_and_app_list and app_switch_active_app. */
static gpg_error_t
send_serialno_and_app_status (card_t card, int with_apps, ctrl_t ctrl)
{
gpg_error_t err;
app_t a;
char *serial;
char *p;
membuf_t mb;
int any = 0;
serial = card_get_serialno (card);
if (!serial)
return 0; /* Oops. */
if (with_apps)
{
/* Note that in case the additional applications have not yet been
* added to the card context (which is commonly done by means of
* "SERIALNO --all", we do that here. */
err = select_all_additional_applications_internal (ctrl, card);
if (err)
{
xfree (serial);
return err;
}
init_membuf (&mb, 256);
put_membuf_str (&mb, serial);
for (a = card->app; a; a = a->next)
{
if (!a->fnc.with_keygrip || a->need_reset)
continue;
any = 1;
put_membuf (&mb, " ", 1);
put_membuf_str (&mb, xstrapptype (a));
}
if (!any && card->app)
{
/* No card app supports the with_keygrip function. Use the
* main app as fallback. */
put_membuf (&mb, " ", 1);
put_membuf_str (&mb, xstrapptype (card->app));
}
put_membuf (&mb, "", 1);
p = get_membuf (&mb, NULL);
if (!p)
{
err = gpg_error_from_syserror ();
xfree (serial);
return err;
}
send_status_direct (ctrl, "SERIALNO", p);
xfree (p);
}
else
send_status_direct (ctrl, "SERIALNO", serial);
xfree (serial);
return 0;
}
/* Common code for app_send_card_list and app_send_active_apps. */
static gpg_error_t
send_card_and_app_list (ctrl_t ctrl, card_t wantcard, int with_apps)
{
gpg_error_t err;
card_t c;
card_t *cardlist = NULL;
int n, ncardlist;
card_list_r_lock ();
for (n=0, c = card_top; c; c = c->next)
n++;
if (!n)
{
err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
goto leave;
}
cardlist = xtrycalloc (n, sizeof *cardlist);
if (!cardlist)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (ncardlist=0, c = card_top; c; c = c->next)
cardlist[ncardlist++] = c;
qsort (cardlist, ncardlist, sizeof *cardlist, compare_card_list_items);
for (n=0; n < ncardlist; n++)
{
if (wantcard && wantcard != cardlist[n])
continue;
err = send_serialno_and_app_status (cardlist[n], with_apps, ctrl);
if (err)
goto leave;
}
err = 0;
leave:
card_list_r_unlock ();
xfree (cardlist);
return err;
}
/* Send status lines with the serialno of all inserted cards. */
gpg_error_t
app_send_card_list (ctrl_t ctrl)
{
return send_card_and_app_list (ctrl, NULL, 0);
}
/* Send status lines with the serialno and appname of the current card
* or of all cards if CARD is NULL. */
gpg_error_t
app_send_active_apps (card_t card, ctrl_t ctrl)
{
return send_card_and_app_list (ctrl, card, 1);
}
/* Switch to APPNAME and print a respective status line with that app
* listed first. If APPNAME is NULL or the empty string no switching
* is done but the status line is printed anyway. */
gpg_error_t
app_switch_active_app (card_t card, ctrl_t ctrl, const char *appname)
{
gpg_error_t err;
apptype_t apptype;
/* Note that in case the additional applications have not yet been
* added to the card context (which is commonly done by means of
* "SERIALNO --all", we do that here. */
err = select_all_additional_applications_internal (ctrl, card);
if (err)
goto leave;
if (appname && *appname)
{
apptype = apptype_from_name (appname);
if (!apptype)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
ctrl->current_apptype = apptype;
err = maybe_switch_app (ctrl, card, NULL);
if (err)
goto leave;
}
/* Print the status line. */
err = send_serialno_and_app_status (card, 1, ctrl);
leave:
return err;
}
/* Execute an action for each app. ACTION can be one of:
*
* - KEYGRIP_ACTION_SEND_DATA
*
* If KEYGRIP_STR matches a public key of any active application
* send information as LF terminated data lines about the public
* key. The format of these lines is
* <keygrip> T <serialno> <idstr>
* If a match was found a pointer to the matching application is
* returned. With the KEYGRIP_STR given as NULL, lines for all
* keys (with CAPABILITY) will be send and the return value is
* GPG_ERR_TRUE.
*
* - KEYGRIP_ACTION_WRITE_STATUS
*
* Same as KEYGRIP_ACTION_SEND_DATA but uses status lines instead
* of data lines.
*
* - KEYGRIP_ACTION_LOOKUP
*
* Returns a pointer to the application matching KEYGRIP_STR but
* does not emit any status or data lines. If no key with that
* keygrip is available or KEYGRIP_STR is NULL, GPG_ERR_NOT_FOUND
* is returned.
*/
card_t
app_do_with_keygrip (ctrl_t ctrl, int action, const char *keygrip_str,
int capability)
{
card_t card;
card_list_r_lock ();
card = do_with_keygrip (ctrl, action, keygrip_str, capability);
card_list_r_unlock ();
return card;
}
diff --git a/scd/ccid-driver.c b/scd/ccid-driver.c
index 0fcd5a3d8..92387a1a8 100644
--- a/scd/ccid-driver.c
+++ b/scd/ccid-driver.c
@@ -1,4081 +1,4081 @@
/* ccid-driver.c - USB ChipCardInterfaceDevices driver
* Copyright (C) 2003, 2004, 2005, 2006, 2007
* 2008, 2009, 2013 Free Software Foundation, Inc.
* Written by Werner Koch.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*
* ALTERNATIVELY, this file may be distributed under the terms of the
* following license, in which case the provisions of this license are
* required INSTEAD OF the GNU General Public License. If you wish to
* allow use of your version of this file only under the terms of the
* GNU General Public License, and not to allow others to use your
* version of this file under the terms of the following license,
* indicate your decision by deleting this paragraph and the license
* below.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, and the entire permission notice in its entirety,
* including the disclaimer of warranties.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. The name of the author may not be used to endorse or promote
* products derived from this software without specific prior
* written permission.
*
* THIS SOFTWARE IS PROVIDED "AS IS" AND ANY EXPRESS OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT,
* INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
* STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
* OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* CCID (ChipCardInterfaceDevices) is a specification for accessing
smartcard via a reader connected to the USB.
This is a limited driver allowing to use some CCID drivers directly
without any other specila drivers. This is a fallback driver to be
used when nothing else works or the system should be kept minimal
for security reasons. It makes use of the libusb library to gain
portable access to USB.
This driver has been tested with the SCM SCR335 and SPR532
smartcard readers and requires that a reader implements APDU or
TPDU level exchange and does fully automatic initialization.
*/
#ifdef HAVE_CONFIG_H
# include <config.h>
#endif
#if defined(HAVE_LIBUSB) || defined(TEST)
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <time.h>
#include <unistd.h>
#ifdef HAVE_NPTH
# include <npth.h>
#endif /*HAVE_NPTH*/
#include <libusb.h>
#include "scdaemon.h"
#include "iso7816.h"
#define CCID_DRIVER_INCLUDE_USB_IDS 1
#include "ccid-driver.h"
#define DRVNAME "ccid-driver: "
/* Max length of buffer with out CCID message header of 10-byte
Sending: 547 for RSA-4096 key import
APDU size = 540 (24+4+256+256)
command + lc + le = 4 + 3 + 0
Sending: write data object of cardholder certificate
APDU size = 2048
command + lc + le = 4 + 3 + 0
Receiving: 2048 for cardholder certificate
*/
#define CCID_MAX_BUF (2048+7+10)
/* CCID command timeout. */
#define CCID_CMD_TIMEOUT (5*1000)
/* Number of supported devices. See MAX_READER in apdu.c. */
#define CCID_MAX_DEVICE 16
/* Depending on how this source is used we either define our error
* output to go to stderr or to the GnuPG based logging functions. We
* use the latter when GNUPG_MAJOR_VERSION is defined. */
#if defined(GNUPG_MAJOR_VERSION)
# include "scdaemon.h"
# define DEBUGOUT(t) do { if (debug_level) \
log_debug (DRVNAME t); } while (0)
# define DEBUGOUT_1(t,a) do { if (debug_level) \
log_debug (DRVNAME t,(a)); } while (0)
# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b)); } while (0)
# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b),(c));} while (0)
# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
log_debug (DRVNAME t,(a),(b),(c),(d));} while (0)
# define DEBUGOUT_CONT(t) do { if (debug_level) \
log_printf (t); } while (0)
# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
log_printf (t,(a)); } while (0)
# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
log_printf (t,(a),(b)); } while (0)
# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
log_printf (t,(a),(b),(c)); } while (0)
# define DEBUGOUT_LF() do { if (debug_level) \
log_printf ("\n"); } while (0)
#else /* Other usage of this source - don't use gnupg specifics. */
# define DEBUGOUT(t) do { if (debug_level) \
fprintf (stderr, DRVNAME t); } while (0)
# define DEBUGOUT_1(t,a) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a)); } while (0)
# define DEBUGOUT_2(t,a,b) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b)); } while (0)
# define DEBUGOUT_3(t,a,b,c) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b), (c)); } while (0)
# define DEBUGOUT_4(t,a,b,c,d) do { if (debug_level) \
fprintf (stderr, DRVNAME t, (a), (b), (c), (d));} while(0)
# define DEBUGOUT_CONT(t) do { if (debug_level) \
fprintf (stderr, t); } while (0)
# define DEBUGOUT_CONT_1(t,a) do { if (debug_level) \
fprintf (stderr, t, (a)); } while (0)
# define DEBUGOUT_CONT_2(t,a,b) do { if (debug_level) \
fprintf (stderr, t, (a), (b)); } while (0)
# define DEBUGOUT_CONT_3(t,a,b,c) do { if (debug_level) \
fprintf (stderr, t, (a), (b), (c)); } while (0)
# define DEBUGOUT_LF() do { if (debug_level) \
putc ('\n', stderr); } while (0)
#endif /* This source is not used by scdaemon. */
#undef USE_LIBUSB_DEBUG_CB
#if LIBUSB_API_VERSION >= 0x01000107
# define USE_LIBUSB_DEBUG_CB 1
#endif
#ifndef EAGAIN
#define EAGAIN EWOULDBLOCK
#endif
enum {
RDR_to_PC_NotifySlotChange= 0x50,
RDR_to_PC_HardwareError = 0x51,
PC_to_RDR_SetParameters = 0x61,
PC_to_RDR_IccPowerOn = 0x62,
PC_to_RDR_IccPowerOff = 0x63,
PC_to_RDR_GetSlotStatus = 0x65,
PC_to_RDR_Secure = 0x69,
PC_to_RDR_T0APDU = 0x6a,
PC_to_RDR_Escape = 0x6b,
PC_to_RDR_GetParameters = 0x6c,
PC_to_RDR_ResetParameters = 0x6d,
PC_to_RDR_IccClock = 0x6e,
PC_to_RDR_XfrBlock = 0x6f,
PC_to_RDR_Mechanical = 0x71,
PC_to_RDR_Abort = 0x72,
PC_to_RDR_SetDataRate = 0x73,
RDR_to_PC_DataBlock = 0x80,
RDR_to_PC_SlotStatus = 0x81,
RDR_to_PC_Parameters = 0x82,
RDR_to_PC_Escape = 0x83,
RDR_to_PC_DataRate = 0x84
};
/* Two macro to detect whether a CCID command has failed and to get
the error code. These macros assume that we can access the
mandatory first 10 bytes of a CCID message in BUF. */
#define CCID_COMMAND_FAILED(buf) ((buf)[7] & 0x40)
#define CCID_ERROR_CODE(buf) (((unsigned char *)(buf))[8])
/* Store information on the driver's state. A pointer to such a
structure is used as handle for most functions. */
struct ccid_driver_s
{
libusb_device_handle *idev;
unsigned int bai;
unsigned short id_vendor;
unsigned short id_product;
int ifc_no;
int ep_bulk_out;
int ep_bulk_in;
int ep_intr;
int seqno;
unsigned char t1_ns;
unsigned char t1_nr;
unsigned char nonnull_nad;
int max_ifsd;
int max_ccid_msglen;
int ifsc;
unsigned char apdu_level:2; /* Reader supports short APDU level
exchange. With a value of 2 short
and extended level is supported.*/
unsigned int auto_voltage:1;
unsigned int auto_param:1;
unsigned int auto_pps:1;
unsigned int auto_ifsd:1;
unsigned int has_pinpad:2;
unsigned int enodev_seen:1;
int powered_off;
time_t last_progress; /* Last time we sent progress line. */
/* The progress callback and its first arg as supplied to
ccid_set_progress_cb. */
void (*progress_cb)(void *, const char *, int, int, int);
void *progress_cb_arg;
void (*prompt_cb)(void *, int);
void *prompt_cb_arg;
unsigned char intr_buf[64];
struct libusb_transfer *transfer;
};
/* Object to keep infos about found ccid devices. */
struct ccid_dev_table {
int n; /* Index to ccid_usb_dev_list */
int interface_number;
int setting_number;
unsigned char *ifcdesc_extra;
int ep_bulk_out;
int ep_bulk_in;
int ep_intr;
size_t ifcdesc_extra_len;
};
static int initialized_usb; /* Tracks whether USB has been initialized. */
static int debug_level; /* Flag to control the debug output.
0 = No debugging
1 = USB I/O info
2 = Level 1 + T=1 protocol tracing
3 = Level 2 + USB/I/O tracing of SlotStatus.
*/
static int ccid_usb_thread_is_alive;
static libusb_device **ccid_usb_dev_list;
static struct ccid_dev_table ccid_dev_table[CCID_MAX_DEVICE];
static unsigned int compute_edc (const unsigned char *data, size_t datalen,
int use_crc);
static int bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
int no_debug);
static int bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
size_t *nread, int expected_type, int seqno, int timeout,
int no_debug);
static int abort_cmd (ccid_driver_t handle, int seqno, int init);
static int send_escape_cmd (ccid_driver_t handle, const unsigned char *data,
size_t datalen, unsigned char *result,
size_t resultmax, size_t *resultlen);
static void
my_npth_unprotect (void)
{
#ifdef USE_NPTH
npth_unprotect ();
#endif
}
static void
my_npth_protect (void)
{
#ifdef USE_NPTH
npth_protect ();
#endif
}
static int
map_libusb_error (int usberr)
{
switch (usberr)
{
case 0: return 0;
case LIBUSB_ERROR_IO: return CCID_DRIVER_ERR_USB_IO;
case LIBUSB_ERROR_ACCESS: return CCID_DRIVER_ERR_USB_ACCESS;
case LIBUSB_ERROR_NO_DEVICE:return CCID_DRIVER_ERR_USB_NO_DEVICE;
case LIBUSB_ERROR_BUSY: return CCID_DRIVER_ERR_USB_BUSY;
case LIBUSB_ERROR_TIMEOUT: return CCID_DRIVER_ERR_USB_TIMEOUT;
case LIBUSB_ERROR_OVERFLOW: return CCID_DRIVER_ERR_USB_OVERFLOW;
}
return CCID_DRIVER_ERR_USB_OTHER;
}
/* Convert a little endian stored 4 byte value into an unsigned
integer. */
static unsigned int
convert_le_u32 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8) | (buf[2] << 16) | ((unsigned int)buf[3] << 24);
}
/* Convert a little endian stored 2 byte value into an unsigned
integer. */
static unsigned int
convert_le_u16 (const unsigned char *buf)
{
return buf[0] | (buf[1] << 8);
}
static void
set_msg_len (unsigned char *msg, unsigned int length)
{
msg[1] = length;
msg[2] = length >> 8;
msg[3] = length >> 16;
msg[4] = length >> 24;
}
static void
print_progress (ccid_driver_t handle)
{
time_t ct = time (NULL);
/* We don't want to print progress lines too often. */
if (ct == handle->last_progress)
return;
if (handle->progress_cb)
handle->progress_cb (handle->progress_cb_arg, "card_busy", 'w', 0, 0);
handle->last_progress = ct;
}
/* Pint an error message for a failed CCID command including a textual
error code. MSG shall be the CCID message at a minimum of 10 bytes. */
static void
print_command_failed (const unsigned char *msg)
{
const char *t;
char buffer[100];
int ec;
if (!debug_level)
return;
ec = CCID_ERROR_CODE (msg);
switch (ec)
{
case 0x00: t = "Command not supported"; break;
case 0xE0: t = "Slot busy"; break;
case 0xEF: t = "PIN cancelled"; break;
case 0xF0: t = "PIN timeout"; break;
case 0xF2: t = "Automatic sequence ongoing"; break;
case 0xF3: t = "Deactivated Protocol"; break;
case 0xF4: t = "Procedure byte conflict"; break;
case 0xF5: t = "ICC class not supported"; break;
case 0xF6: t = "ICC protocol not supported"; break;
case 0xF7: t = "Bad checksum in ATR"; break;
case 0xF8: t = "Bad TS in ATR"; break;
case 0xFB: t = "An all inclusive hardware error occurred"; break;
case 0xFC: t = "Overrun error while talking to the ICC"; break;
case 0xFD: t = "Parity error while talking to the ICC"; break;
case 0xFE: t = "CCID timed out while talking to the ICC"; break;
case 0xFF: t = "Host aborted the current activity"; break;
default:
if (ec > 0 && ec < 128)
sprintf (buffer, "Parameter error at offset %d", ec);
else
sprintf (buffer, "Error code %02X", ec);
t = buffer;
break;
}
DEBUGOUT_1 ("CCID command failed: %s\n", t);
}
static void
print_pr_data (const unsigned char *data, size_t datalen, size_t off)
{
int any = 0;
for (; off < datalen; off++)
{
if (!any || !(off % 16))
{
if (any)
DEBUGOUT_LF ();
DEBUGOUT_1 (" [%04lu] ", (unsigned long) off);
}
DEBUGOUT_CONT_1 (" %02X", data[off]);
any = 1;
}
if (any && (off % 16))
DEBUGOUT_LF ();
}
static void
print_p2r_header (const char *name, const unsigned char *msg, size_t msglen)
{
DEBUGOUT_1 ("%s:\n", name);
if (msglen < 7)
return;
DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
}
static void
print_p2r_iccpoweron (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOn", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_2 (" bPowerSelect ......: 0x%02x (%s)\n", msg[7],
msg[7] == 0? "auto":
msg[7] == 1? "5.0 V":
msg[7] == 2? "3.0 V":
msg[7] == 3? "1.8 V":"");
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_iccpoweroff (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccPowerOff", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_getslotstatus (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetSlotStatus", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_xfrblock (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_XfrBlock", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bBWI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_getparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_GetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_resetparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_ResetParameters", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setparameters (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetParameters", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bProtocolNum ......: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_escape (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Escape", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_iccclock (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_IccClock", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bClockCommand .....: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_to0apdu (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_T0APDU", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bmChanges .........: 0x%02x\n", msg[7]);
DEBUGOUT_1 (" bClassGetResponse .: 0x%02x\n", msg[8]);
DEBUGOUT_1 (" bClassEnvelope ....: 0x%02x\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_secure (const unsigned char *msg, size_t msglen)
{
unsigned int val;
print_p2r_header ("PC_to_RDR_Secure", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bBMI ..............: 0x%02x\n", msg[7]);
val = convert_le_u16 (msg+8);
DEBUGOUT_2 (" wLevelParameter ...: 0x%04x%s\n", val,
val == 1? " (continued)":
val == 2? " (continues+ends)":
val == 3? " (continues+continued)":
val == 16? " (DataBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_p2r_mechanical (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Mechanical", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bFunction .........: 0x%02x\n", msg[7]);
print_pr_data (msg, msglen, 8);
}
static void
print_p2r_abort (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_Abort", msg, msglen);
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_setdatarate (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("PC_to_RDR_SetDataRate", msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 7);
}
static void
print_p2r_unknown (const unsigned char *msg, size_t msglen)
{
print_p2r_header ("Unknown PC_to_RDR command", msg, msglen);
if (msglen < 10)
return;
print_pr_data (msg, msglen, 0);
}
static void
print_r2p_header (const char *name, const unsigned char *msg, size_t msglen)
{
DEBUGOUT_1 ("%s:\n", name);
if (msglen < 9)
return;
DEBUGOUT_1 (" dwLength ..........: %u\n", convert_le_u32 (msg+1));
DEBUGOUT_1 (" bSlot .............: %u\n", msg[5]);
DEBUGOUT_1 (" bSeq ..............: %u\n", msg[6]);
DEBUGOUT_1 (" bStatus ...........: %u\n", msg[7]);
if (msg[8])
DEBUGOUT_1 (" bError ............: %u\n", msg[8]);
}
static void
print_r2p_datablock (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataBlock", msg, msglen);
if (msglen < 10)
return;
if (msg[9])
DEBUGOUT_2 (" bChainParameter ...: 0x%02x%s\n", msg[9],
msg[9] == 1? " (continued)":
msg[9] == 2? " (continues+ends)":
msg[9] == 3? " (continues+continued)":
msg[9] == 16? " (XferBlock-expected)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_slotstatus (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_SlotStatus", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_2 (" bClockStatus ......: 0x%02x%s\n", msg[9],
msg[9] == 0? " (running)":
msg[9] == 1? " (stopped-L)":
msg[9] == 2? " (stopped-H)":
msg[9] == 3? " (stopped)":"");
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_parameters (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_Parameters", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" protocol ..........: T=%d\n", msg[9]);
if (msglen == 17 && msg[9] == 1)
{
/* Protocol T=1. */
DEBUGOUT_1 (" bmFindexDindex ....: %02X\n", msg[10]);
DEBUGOUT_1 (" bmTCCKST1 .........: %02X\n", msg[11]);
DEBUGOUT_1 (" bGuardTimeT1 ......: %02X\n", msg[12]);
DEBUGOUT_1 (" bmWaitingIntegersT1: %02X\n", msg[13]);
DEBUGOUT_1 (" bClockStop ........: %02X\n", msg[14]);
DEBUGOUT_1 (" bIFSC .............: %d\n", msg[15]);
DEBUGOUT_1 (" bNadValue .........: %d\n", msg[16]);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_escape (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_Escape", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_datarate (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("RDR_to_PC_DataRate", msg, msglen);
if (msglen < 10)
return;
if (msglen >= 18)
{
DEBUGOUT_1 (" dwClockFrequency ..: %u\n", convert_le_u32 (msg+10));
DEBUGOUT_1 (" dwDataRate ..... ..: %u\n", convert_le_u32 (msg+14));
print_pr_data (msg, msglen, 18);
}
else
print_pr_data (msg, msglen, 10);
}
static void
print_r2p_unknown (const unsigned char *msg, size_t msglen)
{
print_r2p_header ("Unknown RDR_to_PC command", msg, msglen);
if (msglen < 10)
return;
DEBUGOUT_1 (" bMessageType ......: %02X\n", msg[0]);
DEBUGOUT_1 (" buffer[9] .........: %02X\n", msg[9]);
print_pr_data (msg, msglen, 10);
}
/* Parse a CCID descriptor, optionally print all available features
and test whether this reader is usable by this driver. Returns 0
if it is usable.
Note, that this code is based on the one in lsusb.c of the
usb-utils package, I wrote on 2003-09-01. -wk. */
static int
parse_ccid_descriptor (ccid_driver_t handle, unsigned short bcd_device,
const unsigned char *buf, size_t buflen)
{
unsigned int i;
unsigned int us;
int have_t1 = 0, have_tpdu=0;
handle->nonnull_nad = 0;
handle->auto_ifsd = 0;
handle->max_ifsd = 32;
handle->has_pinpad = 0;
handle->apdu_level = 0;
handle->auto_voltage = 0;
handle->auto_param = 0;
handle->auto_pps = 0;
DEBUGOUT_3 ("idVendor: %04X idProduct: %04X bcdDevice: %04X\n",
handle->id_vendor, handle->id_product, bcd_device);
if (buflen < 54 || buf[0] < 54)
{
DEBUGOUT ("CCID device descriptor is too short\n");
return -1;
}
DEBUGOUT ("ChipCard Interface Descriptor:\n");
DEBUGOUT_1 (" bLength %5u\n", buf[0]);
DEBUGOUT_1 (" bDescriptorType %5u\n", buf[1]);
DEBUGOUT_2 (" bcdCCID %2x.%02x", buf[3], buf[2]);
if (buf[3] != 1 || buf[2] != 0)
DEBUGOUT_CONT(" (Warning: Only accurate for version 1.0)");
DEBUGOUT_LF ();
DEBUGOUT_1 (" nMaxSlotIndex %5u\n", buf[4]);
DEBUGOUT_2 (" bVoltageSupport %5u %s\n",
buf[5], (buf[5] == 1? "5.0V" : buf[5] == 2? "3.0V"
: buf[5] == 3? "1.8V":"?"));
us = convert_le_u32 (buf+6);
DEBUGOUT_1 (" dwProtocols %5u ", us);
if ((us & 1))
DEBUGOUT_CONT (" T=0");
if ((us & 2))
{
DEBUGOUT_CONT (" T=1");
have_t1 = 1;
}
if ((us & ~3))
DEBUGOUT_CONT (" (Invalid values detected)");
DEBUGOUT_LF ();
us = convert_le_u32(buf+10);
DEBUGOUT_1 (" dwDefaultClock %5u\n", us);
us = convert_le_u32(buf+14);
DEBUGOUT_1 (" dwMaxiumumClock %5u\n", us);
DEBUGOUT_1 (" bNumClockSupported %5u\n", buf[18]);
us = convert_le_u32(buf+19);
DEBUGOUT_1 (" dwDataRate %7u bps\n", us);
us = convert_le_u32(buf+23);
DEBUGOUT_1 (" dwMaxDataRate %7u bps\n", us);
DEBUGOUT_1 (" bNumDataRatesSupp. %5u\n", buf[27]);
us = convert_le_u32(buf+28);
DEBUGOUT_1 (" dwMaxIFSD %5u\n", us);
handle->max_ifsd = us;
us = convert_le_u32(buf+32);
DEBUGOUT_1 (" dwSyncProtocols %08X ", us);
if ((us&1))
DEBUGOUT_CONT ( " 2-wire");
if ((us&2))
DEBUGOUT_CONT ( " 3-wire");
if ((us&4))
DEBUGOUT_CONT ( " I2C");
DEBUGOUT_LF ();
us = convert_le_u32(buf+36);
DEBUGOUT_1 (" dwMechanical %08X ", us);
if ((us & 1))
DEBUGOUT_CONT (" accept");
if ((us & 2))
DEBUGOUT_CONT (" eject");
if ((us & 4))
DEBUGOUT_CONT (" capture");
if ((us & 8))
DEBUGOUT_CONT (" lock");
DEBUGOUT_LF ();
us = convert_le_u32(buf+40);
DEBUGOUT_1 (" dwFeatures %08X\n", us);
if ((us & 0x0002))
{
DEBUGOUT (" Auto configuration based on ATR (assumes auto voltage)\n");
handle->auto_voltage = 1;
}
if ((us & 0x0004))
DEBUGOUT (" Auto activation on insert\n");
if ((us & 0x0008))
{
DEBUGOUT (" Auto voltage selection\n");
handle->auto_voltage = 1;
}
if ((us & 0x0010))
DEBUGOUT (" Auto clock change\n");
if ((us & 0x0020))
DEBUGOUT (" Auto baud rate change\n");
if ((us & 0x0040))
{
DEBUGOUT (" Auto parameter negotiation made by CCID\n");
handle->auto_param = 1;
}
else if ((us & 0x0080))
{
DEBUGOUT (" Auto PPS made by CCID\n");
handle->auto_pps = 1;
}
if ((us & (0x0040 | 0x0080)) == (0x0040 | 0x0080))
DEBUGOUT (" WARNING: conflicting negotiation features\n");
if ((us & 0x0100))
DEBUGOUT (" CCID can set ICC in clock stop mode\n");
if ((us & 0x0200))
{
DEBUGOUT (" NAD value other than 0x00 accepted\n");
handle->nonnull_nad = 1;
}
if ((us & 0x0400))
{
DEBUGOUT (" Auto IFSD exchange\n");
handle->auto_ifsd = 1;
}
if ((us & 0x00010000))
{
DEBUGOUT (" TPDU level exchange\n");
have_tpdu = 1;
}
else if ((us & 0x00020000))
{
DEBUGOUT (" Short APDU level exchange\n");
handle->apdu_level = 1;
}
else if ((us & 0x00040000))
{
DEBUGOUT (" Short and extended APDU level exchange\n");
handle->apdu_level = 2;
}
else if ((us & 0x00070000))
DEBUGOUT (" WARNING: conflicting exchange levels\n");
us = convert_le_u32(buf+44);
DEBUGOUT_1 (" dwMaxCCIDMsgLen %5u\n", us);
handle->max_ccid_msglen = us;
DEBUGOUT ( " bClassGetResponse ");
if (buf[48] == 0xff)
DEBUGOUT_CONT ("echo\n");
else
DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
DEBUGOUT ( " bClassEnvelope ");
if (buf[49] == 0xff)
DEBUGOUT_CONT ("echo\n");
else
DEBUGOUT_CONT_1 (" %02X\n", buf[48]);
DEBUGOUT ( " wlcdLayout ");
if (!buf[50] && !buf[51])
DEBUGOUT_CONT ("none\n");
else
DEBUGOUT_CONT_2 ("%u cols %u lines\n", buf[50], buf[51]);
DEBUGOUT_1 (" bPINSupport %5u ", buf[52]);
if ((buf[52] & 1))
{
DEBUGOUT_CONT ( " verification");
handle->has_pinpad |= 1;
}
if ((buf[52] & 2))
{
DEBUGOUT_CONT ( " modification");
handle->has_pinpad |= 2;
}
DEBUGOUT_LF ();
DEBUGOUT_1 (" bMaxCCIDBusySlots %5u\n", buf[53]);
if (buf[0] > 54)
{
DEBUGOUT (" junk ");
for (i=54; i < buf[0]-54; i++)
DEBUGOUT_CONT_1 (" %02X", buf[i]);
DEBUGOUT_LF ();
}
if (!have_t1 || !(have_tpdu || handle->apdu_level))
{
DEBUGOUT ("this drivers requires that the reader supports T=1, "
"TPDU or APDU level exchange - this is not available\n");
return -1;
}
/* SCM drivers get stuck in their internal USB stack if they try to
send a frame of n*wMaxPacketSize back to us. Given that
wMaxPacketSize is 64 for these readers we set the IFSD to a value
lower than that:
64 - 10 CCID header - 4 T1frame - 2 reserved = 48
Product Ids:
0xe001 - SCR 331
0x5111 - SCR 331-DI
0x5115 - SCR 335
0xe003 - SPR 532
The
0x5117 - SCR 3320 USB ID-000 reader
seems to be very slow but enabling this workaround boosts the
performance to a more or less acceptable level (tested by David).
*/
if (handle->id_vendor == VENDOR_SCM
&& handle->max_ifsd > 48
&& ( (handle->id_product == SCM_SCR331 && bcd_device < 0x0516)
||(handle->id_product == SCM_SCR331DI && bcd_device < 0x0620)
||(handle->id_product == SCM_SCR335 && bcd_device < 0x0514)
||(handle->id_product == SCM_SPR532 && bcd_device < 0x0504)
||(handle->id_product == SCM_SCR3320 && bcd_device < 0x0522)
))
{
DEBUGOUT ("enabling workaround for buggy SCM readers\n");
handle->max_ifsd = 48;
}
if (handle->id_vendor == VENDOR_GEMPC)
{
DEBUGOUT ("enabling product quirk: disable non-null NAD\n");
handle->nonnull_nad = 0;
}
return 0;
}
static char *
get_escaped_usb_string (libusb_device_handle *idev, int idx,
const char *prefix, const char *suffix)
{
int rc;
unsigned char buf[280];
unsigned char *s;
unsigned int langid;
size_t i, n, len;
char *result;
if (!idx)
return NULL;
/* Fixme: The next line is for the current Valgrid without support
for USB IOCTLs. */
memset (buf, 0, sizeof buf);
/* First get the list of supported languages and use the first one.
If we do don't find it we try to use English. Note that this is
all in a 2 bute Unicode encoding using little endian. */
my_npth_unprotect ();
rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8), 0,
buf, sizeof buf, 1000 /* ms timeout */);
my_npth_protect ();
if (rc < 4)
langid = 0x0409; /* English. */
else
langid = (buf[3] << 8) | buf[2];
my_npth_unprotect ();
rc = libusb_control_transfer (idev, LIBUSB_ENDPOINT_IN,
LIBUSB_REQUEST_GET_DESCRIPTOR,
(LIBUSB_DT_STRING << 8) + idx, langid,
buf, sizeof buf, 1000 /* ms timeout */);
my_npth_protect ();
if (rc < 2 || buf[1] != LIBUSB_DT_STRING)
return NULL; /* Error or not a string. */
len = buf[0];
if (len > rc)
return NULL; /* Larger than our buffer. */
for (s=buf+2, i=2, n=0; i+1 < len; i += 2, s += 2)
{
if (s[1])
n++; /* High byte set. */
else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
n += 3 ;
else
n++;
}
result = malloc (strlen (prefix) + n + strlen (suffix) + 1);
if (!result)
return NULL;
strcpy (result, prefix);
n = strlen (prefix);
for (s=buf+2, i=2; i+1 < len; i += 2, s += 2)
{
if (s[1])
result[n++] = '\xff'; /* High byte set. */
else if (*s <= 0x20 || *s >= 0x7f || *s == '%' || *s == ':')
{
sprintf (result+n, "%%%02X", *s);
n += 3;
}
else
result[n++] = *s;
}
strcpy (result+n, suffix);
return result;
}
/* This function creates an reader id to be used to find the same
physical reader after a reset. It returns an allocated and possibly
percent escaped string or NULL if not enough memory is available. */
static char *
make_reader_id (libusb_device_handle *idev,
unsigned int vendor, unsigned int product,
unsigned char serialno_index)
{
char *rid;
char prefix[20];
sprintf (prefix, "%04X:%04X:", (vendor & 0xffff), (product & 0xffff));
rid = get_escaped_usb_string (idev, serialno_index, prefix, ":0");
if (!rid)
{
rid = malloc (strlen (prefix) + 3 + 1);
if (!rid)
return NULL;
strcpy (rid, prefix);
strcat (rid, "X:0");
}
return rid;
}
/* Helper to find the endpoint from an interface descriptor. */
static int
find_endpoint (const struct libusb_interface_descriptor *ifcdesc, int mode)
{
int no;
int want_bulk_in = 0;
if (mode == 1)
want_bulk_in = 0x80;
for (no=0; no < ifcdesc->bNumEndpoints; no++)
{
const struct libusb_endpoint_descriptor *ep = ifcdesc->endpoint + no;
if (ep->bDescriptorType != LIBUSB_DT_ENDPOINT)
;
else if (mode == 2
&& ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_INTERRUPT)
&& (ep->bEndpointAddress & 0x80))
return ep->bEndpointAddress;
else if ((mode == 0 || mode == 1)
&& ((ep->bmAttributes & LIBUSB_TRANSFER_TYPE_MASK)
== LIBUSB_TRANSFER_TYPE_BULK)
&& (ep->bEndpointAddress & 0x80) == want_bulk_in)
return ep->bEndpointAddress;
}
return -1;
}
/* Helper for scan_devices. This function returns true if a
requested device has been found or the caller should stop scanning
for other reasons. */
static void
scan_usb_device (int *count, char **rid_list, struct libusb_device *dev)
{
int ifc_no;
int set_no;
const struct libusb_interface_descriptor *ifcdesc;
char *rid;
libusb_device_handle *idev = NULL;
int err;
struct libusb_config_descriptor *config;
struct libusb_device_descriptor desc;
char *p;
err = libusb_get_device_descriptor (dev, &desc);
if (err)
return;
err = libusb_get_active_config_descriptor (dev, &config);
if (err)
return;
for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
for (set_no=0; set_no < config->interface[ifc_no].num_altsetting; set_no++)
{
ifcdesc = (config->interface[ifc_no].altsetting + set_no);
/* The second condition is for older SCM SPR 532 who did
not know about the assigned CCID class. The third
condition does the same for a Cherry SmartTerminal
ST-2000. Instead of trying to interpret the strings
we simply check the product ID. */
if (ifcdesc && ifcdesc->extra
&& ((ifcdesc->bInterfaceClass == 11
&& ifcdesc->bInterfaceSubClass == 0
&& ifcdesc->bInterfaceProtocol == 0)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_SCM
&& desc.idProduct == SCM_SPR532)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_CHERRY
&& desc.idProduct == CHERRY_ST2000)))
{
++*count;
err = libusb_open (dev, &idev);
if (err)
{
DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (err));
continue; /* with next setting. */
}
rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
desc.iSerialNumber);
if (!rid)
{
libusb_free_config_descriptor (config);
return;
}
/* We are collecting infos about all available CCID
readers. Store them and continue. */
DEBUGOUT_2 ("found CCID reader %d (ID=%s)\n", *count, rid);
p = malloc ((*rid_list? strlen (*rid_list):0) + 1
+ strlen (rid) + 1);
if (p)
{
*p = 0;
if (*rid_list)
{
strcat (p, *rid_list);
free (*rid_list);
}
strcat (p, rid);
strcat (p, "\n");
*rid_list = p;
}
else /* Out of memory. */
{
libusb_free_config_descriptor (config);
free (rid);
return;
}
free (rid);
libusb_close (idev);
idev = NULL;
}
}
libusb_free_config_descriptor (config);
}
/* Scan all CCID devices.
The function returns 0 if a reader has been found or when a scan
returned without error.
R_RID should be the address where to store the list of reader_ids
we found. If on return this list is empty, no CCID device has been
found; otherwise it points to an allocated linked list of reader
IDs.
*/
static int
scan_devices (char **r_rid)
{
char *rid_list = NULL;
int count = 0;
libusb_device **dev_list = NULL;
libusb_device *dev;
int i;
ssize_t n;
/* Set return values to a default. */
if (r_rid)
*r_rid = NULL;
n = libusb_get_device_list (NULL, &dev_list);
for (i = 0; i < n; i++)
{
dev = dev_list[i];
scan_usb_device (&count, &rid_list, dev);
}
libusb_free_device_list (dev_list, 1);
*r_rid = rid_list;
return 0;
}
#ifdef USE_LIBUSB_DEBUG_CB
static void
debug_libusb_cb (libusb_context *ctx, enum libusb_log_level level,
const char *str)
{
int n = str? strlen (str):0;
(void)ctx;
/* Strip the LF so that our logging filter does not escape it. */
if (n && str[n-1] == '\n')
n--;
log_debug ("libusb{%d}: %.*s\n", level, n, str);
}
#endif /* USE_LIBUSB_DEBUG_CB */
/* Set the level of debugging to LEVEL and return the old level. -1
just returns the old level. A level of 0 disables debugging, 1
enables debugging, 2 enables additional tracing of the T=1
protocol, 3 additionally enables debugging for GetSlotStatus, other
values are not yet defined.
Note that libusb may provide its own debugging feature which is
enabled by setting the envvar USB_DEBUG. */
int
ccid_set_debug_level (int level)
{
int old = debug_level;
if (level != -1)
debug_level = level;
#ifdef USE_LIBUSB_DEBUG_CB
if (level > 4)
{
log_debug ("libusb: Enable logging\n");
libusb_set_log_cb (NULL, debug_libusb_cb, LIBUSB_LOG_CB_GLOBAL);
libusb_set_option (NULL, LIBUSB_OPTION_LOG_LEVEL, LIBUSB_LOG_LEVEL_DEBUG);
}
else
libusb_set_log_cb (NULL, NULL, LIBUSB_LOG_CB_GLOBAL);
#endif /* USE_LIBUSB_DEBUG_CB */
return old;
}
char *
ccid_get_reader_list (void)
{
char *reader_list;
if (!initialized_usb)
{
int rc;
if ((rc = libusb_init (NULL)))
{
DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
return NULL;
}
initialized_usb = 1;
}
if (scan_devices (&reader_list))
return NULL; /* Error. */
return reader_list;
}
/* Vendor specific custom initialization. */
static int
ccid_vendor_specific_init (ccid_driver_t handle)
{
int r = 0;
if (handle->id_vendor == VENDOR_VEGA && handle->id_product == VEGA_ALPHA)
{
/*
* Vega alpha has a feature to show retry counter on the pinpad
* display. But it assumes that the card returns the value of
* retry counter by VERIFY with empty data (return code of
* 63Cx). Unfortunately, existing OpenPGP cards don't support
* VERIFY command with empty data. This vendor specific command
* sequence is to disable the feature.
*/
const unsigned char cmd[] = { '\xb5', '\x01', '\x00', '\x03', '\x00' };
r = send_escape_cmd (handle, cmd, sizeof (cmd), NULL, 0, NULL);
}
else if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532)
{
/*
* It seems that SEQ may be out of sync between host and the card reader,
* and SET_INTERFACE doesn't reset it. Make sure it works at the init.
*/
abort_cmd (handle, 0, 1);
}
if (r != 0 && r != CCID_DRIVER_ERR_CARD_INACTIVE
&& r != CCID_DRIVER_ERR_NO_CARD)
return r;
else
return 0;
}
static int
ccid_vendor_specific_setup (ccid_driver_t handle)
{
if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532)
{
my_npth_unprotect ();
libusb_clear_halt (handle->idev, handle->ep_intr);
my_npth_protect ();
}
return 0;
}
static int
ccid_vendor_specific_pinpad_setup (ccid_driver_t handle)
{
if (handle->id_vendor == VENDOR_SCM && handle->id_product == SCM_SPR532)
{
DEBUGOUT ("sending escape sequence to switch to a case 1 APDU\n");
send_escape_cmd (handle, (const unsigned char*)"\x80\x02\x00", 3,
NULL, 0, NULL);
}
return 0;
}
gpg_error_t
ccid_dev_scan (int *idx_max_p, void **t_p)
{
ssize_t n;
libusb_device *dev;
int i;
int ifc_no;
int set_no;
int idx = 0;
int err = 0;
*idx_max_p = 0;
*t_p = NULL;
if (!initialized_usb)
{
int rc;
if ((rc = libusb_init (NULL)))
{
DEBUGOUT_1 ("usb_init failed: %s.\n", libusb_error_name (rc));
return gpg_error (GPG_ERR_ENODEV);
}
initialized_usb = 1;
}
n = libusb_get_device_list (NULL, &ccid_usb_dev_list);
for (i = 0; i < n; i++)
{
struct libusb_config_descriptor *config;
struct libusb_device_descriptor desc;
dev = ccid_usb_dev_list[i];
if (libusb_get_device_descriptor (dev, &desc))
continue;
if (libusb_get_active_config_descriptor (dev, &config))
continue;
for (ifc_no=0; ifc_no < config->bNumInterfaces; ifc_no++)
for (set_no=0; set_no < config->interface[ifc_no].num_altsetting;
set_no++)
{
const struct libusb_interface_descriptor *ifcdesc;
ifcdesc = &config->interface[ifc_no].altsetting[set_no];
/* The second condition is for older SCM SPR 532 who did
not know about the assigned CCID class. The third
condition does the same for a Cherry SmartTerminal
ST-2000. Instead of trying to interpret the strings
we simply check the product ID. */
if (ifcdesc && ifcdesc->extra
&& ((ifcdesc->bInterfaceClass == 11
&& ifcdesc->bInterfaceSubClass == 0
&& ifcdesc->bInterfaceProtocol == 0)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_SCM
&& desc.idProduct == SCM_SPR532)
|| (ifcdesc->bInterfaceClass == 255
&& desc.idVendor == VENDOR_CHERRY
&& desc.idProduct == CHERRY_ST2000)))
{
/* Found a reader. */
unsigned char *ifcdesc_extra;
ifcdesc_extra = malloc (ifcdesc->extra_length);
if (!ifcdesc_extra)
{
err = gpg_error_from_syserror ();
libusb_free_config_descriptor (config);
goto scan_finish;
}
memcpy (ifcdesc_extra, ifcdesc->extra, ifcdesc->extra_length);
ccid_dev_table[idx].n = i;
ccid_dev_table[idx].interface_number = ifc_no;
ccid_dev_table[idx].setting_number = set_no;
ccid_dev_table[idx].ifcdesc_extra = ifcdesc_extra;
ccid_dev_table[idx].ifcdesc_extra_len = ifcdesc->extra_length;
ccid_dev_table[idx].ep_bulk_out = find_endpoint (ifcdesc, 0);
ccid_dev_table[idx].ep_bulk_in = find_endpoint (ifcdesc, 1);
ccid_dev_table[idx].ep_intr = find_endpoint (ifcdesc, 2);
idx++;
if (idx >= CCID_MAX_DEVICE)
{
libusb_free_config_descriptor (config);
err = 0;
goto scan_finish;
}
}
}
libusb_free_config_descriptor (config);
}
scan_finish:
if (err)
{
for (i = 0; i < idx; i++)
{
free (ccid_dev_table[i].ifcdesc_extra);
ccid_dev_table[i].n = 0;
ccid_dev_table[i].interface_number = 0;
ccid_dev_table[i].setting_number = 0;
ccid_dev_table[i].ifcdesc_extra = NULL;
ccid_dev_table[i].ifcdesc_extra_len = 0;
ccid_dev_table[i].ep_bulk_out = 0;
ccid_dev_table[i].ep_bulk_in = 0;
ccid_dev_table[i].ep_intr = 0;
}
libusb_free_device_list (ccid_usb_dev_list, 1);
ccid_usb_dev_list = NULL;
}
else
{
*idx_max_p = idx;
if (idx)
*t_p = ccid_dev_table;
else
*t_p = NULL;
}
return err;
}
void
ccid_dev_scan_finish (void *tbl0, int max)
{
int i;
struct ccid_dev_table *tbl = tbl0;
for (i = 0; i < max; i++)
{
free (tbl[i].ifcdesc_extra);
tbl[i].n = 0;
tbl[i].interface_number = 0;
tbl[i].setting_number = 0;
tbl[i].ifcdesc_extra = NULL;
tbl[i].ifcdesc_extra_len = 0;
tbl[i].ep_bulk_out = 0;
tbl[i].ep_bulk_in = 0;
tbl[i].ep_intr = 0;
}
libusb_free_device_list (ccid_usb_dev_list, 1);
ccid_usb_dev_list = NULL;
}
unsigned int
ccid_get_BAI (int idx, void *tbl0)
{
int n;
int bus, addr, intf;
unsigned int bai;
libusb_device *dev;
struct ccid_dev_table *tbl = tbl0;
n = tbl[idx].n;
dev = ccid_usb_dev_list[n];
bus = libusb_get_bus_number (dev);
addr = libusb_get_device_address (dev);
intf = tbl[idx].interface_number;
bai = (bus << 16) | (addr << 8) | intf;
return bai;
}
int
ccid_compare_BAI (ccid_driver_t handle, unsigned int bai)
{
return handle->bai == bai;
}
static void
intr_cb (struct libusb_transfer *transfer)
{
ccid_driver_t handle = transfer->user_data;
DEBUGOUT_2 ("CCID: interrupt callback %d (%d)\n",
transfer->status, transfer->actual_length);
if (transfer->status == LIBUSB_TRANSFER_TIMED_OUT)
{
int err;
submit_again:
/* Submit the URB again to keep watching the INTERRUPT transfer. */
err = libusb_submit_transfer (transfer);
if (err == LIBUSB_ERROR_NO_DEVICE)
goto device_removed;
DEBUGOUT_1 ("CCID submit transfer again %d\n", err);
}
else if (transfer->status == LIBUSB_TRANSFER_COMPLETED)
{
size_t len = transfer->actual_length;
unsigned char *p = transfer->buffer;
int card_removed = 0;
while (len)
{
if (*p == RDR_to_PC_NotifySlotChange)
{
if (len < 2)
break;
DEBUGOUT_1 ("CCID: NotifySlotChange: %02x\n", p[1]);
if ((p[1] & 1))
card_removed = 0;
else
card_removed = 1;
p += 2;
len -= 2;
}
else if (*p == RDR_to_PC_HardwareError)
{
if (len < 4)
break;
DEBUGOUT_1 ("CCID: hardware error detected: %02x\n", p[3]);
p += 4;
len -= 4;
}
else
{
DEBUGOUT_1 ("CCID: unknown intr: %02x\n", p[0]);
break;
}
}
if (card_removed)
{
DEBUGOUT ("CCID: card removed\n");
handle->powered_off = 1;
#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
#endif
}
else
{
/* Event other than card removal. */
goto submit_again;
}
}
else if (transfer->status == LIBUSB_TRANSFER_CANCELLED)
handle->powered_off = 1;
else if (transfer->status == LIBUSB_TRANSFER_OVERFLOW)
{
/* Something goes wrong. Ignore. */
DEBUGOUT ("CCID: interrupt transfer overflow\n");
}
else
{
device_removed:
DEBUGOUT ("CCID: device removed\n");
handle->powered_off = 1;
#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
#endif
}
}
static void
ccid_setup_intr (ccid_driver_t handle)
{
struct libusb_transfer *transfer;
int err;
transfer = libusb_alloc_transfer (0);
handle->transfer = transfer;
libusb_fill_interrupt_transfer (transfer, handle->idev, handle->ep_intr,
handle->intr_buf, sizeof (handle->intr_buf),
intr_cb, handle, 0);
err = libusb_submit_transfer (transfer);
DEBUGOUT_2 ("CCID submit transfer (%x): %d", handle->ep_intr, err);
}
static void *
ccid_usb_thread (void *arg)
{
libusb_context *ctx = arg;
while (ccid_usb_thread_is_alive)
{
my_npth_unprotect ();
libusb_handle_events_completed (ctx, NULL);
my_npth_protect ();
}
return NULL;
}
static int
ccid_open_usb_reader (const char *spec_reader_name,
int idx, void *ccid_table0,
ccid_driver_t *handle, char **rdrname_p)
{
libusb_device *dev;
libusb_device_handle *idev = NULL;
char *rid = NULL;
int rc = 0;
int ifc_no, set_no;
struct libusb_device_descriptor desc;
int n;
int bus, addr;
unsigned int bai;
struct ccid_dev_table *ccid_table = ccid_table0;
n = ccid_table[idx].n;
ifc_no = ccid_table[idx].interface_number;
set_no = ccid_table[idx].setting_number;
dev = ccid_usb_dev_list[n];
bus = libusb_get_bus_number (dev);
addr = libusb_get_device_address (dev);
bai = (bus << 16) | (addr << 8) | ifc_no;
rc = libusb_open (dev, &idev);
if (rc)
{
DEBUGOUT_1 ("usb_open failed: %s\n", libusb_error_name (rc));
free (*handle);
*handle = NULL;
return map_libusb_error (rc);
}
if (ccid_usb_thread_is_alive++ == 0)
{
npth_t thread;
npth_attr_t tattr;
int err;
err = npth_attr_init (&tattr);
if (err)
{
DEBUGOUT_1 ("npth_attr_init failed: %s\n", strerror (err));
free (*handle);
*handle = NULL;
return err;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
err = npth_create (&thread, &tattr, ccid_usb_thread, NULL);
if (err)
{
DEBUGOUT_1 ("npth_create failed: %s\n", strerror (err));
free (*handle);
*handle = NULL;
return err;
}
npth_setname_np (thread, "ccid_usb_thread");
npth_attr_destroy (&tattr);
}
rc = libusb_get_device_descriptor (dev, &desc);
if (rc)
{
DEBUGOUT ("get_device_descripor failed\n");
rc = map_libusb_error (rc);
goto leave;
}
rid = make_reader_id (idev, desc.idVendor, desc.idProduct,
desc.iSerialNumber);
/* Check to see if reader name matches the spec. */
if (spec_reader_name
&& strncmp (rid, spec_reader_name, strlen (spec_reader_name)))
{
DEBUGOUT ("device not matched\n");
rc = CCID_DRIVER_ERR_NO_READER;
goto leave;
}
(*handle)->id_vendor = desc.idVendor;
(*handle)->id_product = desc.idProduct;
(*handle)->idev = idev;
(*handle)->bai = bai;
(*handle)->ifc_no = ifc_no;
(*handle)->ep_bulk_out = ccid_table[idx].ep_bulk_out;
(*handle)->ep_bulk_in = ccid_table[idx].ep_bulk_in;
(*handle)->ep_intr = ccid_table[idx].ep_intr;
DEBUGOUT_2 ("using CCID reader %d (ID=%s)\n", idx, rid);
if (parse_ccid_descriptor (*handle, desc.bcdDevice,
ccid_table[idx].ifcdesc_extra,
ccid_table[idx].ifcdesc_extra_len))
{
DEBUGOUT ("device not supported\n");
rc = CCID_DRIVER_ERR_NO_READER;
goto leave;
}
my_npth_unprotect ();
if (!(opt.compat_flags & COMPAT_CCID_NO_AUTO_DETACH))
{
rc = libusb_set_auto_detach_kernel_driver (idev, 1);
if (rc)
{
my_npth_protect ();
DEBUGOUT_1 ("note: set_auto_detach_kernel_driver failed: %d\n", rc);
my_npth_unprotect ();
}
}
rc = libusb_claim_interface (idev, ifc_no);
if (rc)
{
my_npth_protect ();
DEBUGOUT_1 ("usb_claim_interface failed: %d\n", rc);
rc = map_libusb_error (rc);
goto leave;
}
/* Submit SET_INTERFACE control transfer which can reset the device. */
if ((*handle)->id_vendor == VENDOR_ACR && (*handle)->id_product == ACR_122U)
rc = 0; /* Not supported by this reader. */
else
rc = libusb_set_interface_alt_setting (idev, ifc_no, set_no);
if (rc)
{
my_npth_protect ();
DEBUGOUT_1 ("usb_set_interface_alt_setting failed: %d\n", rc);
rc = map_libusb_error (rc);
goto leave;
}
my_npth_protect ();
- /* Perform any vendor specific intialization. */
+ /* Perform any vendor specific initialization. */
rc = ccid_vendor_specific_init (*handle);
leave:
if (rc)
{
--ccid_usb_thread_is_alive;
free (rid);
libusb_release_interface (idev, ifc_no);
libusb_close (idev);
free (*handle);
*handle = NULL;
}
else
{
if (rdrname_p)
*rdrname_p = rid;
else
free (rid);
}
return rc;
}
/* Open the reader with the internal number READERNO and return a
pointer to be used as handle in HANDLE. Returns 0 on success. */
int
ccid_open_reader (const char *spec_reader_name, int idx,
void *ccid_table0,
ccid_driver_t *handle, char **rdrname_p)
{
struct ccid_dev_table *ccid_table = ccid_table0;
*handle = calloc (1, sizeof **handle);
if (!*handle)
{
DEBUGOUT ("out of memory\n");
return CCID_DRIVER_ERR_OUT_OF_CORE;
}
return ccid_open_usb_reader (spec_reader_name, idx, ccid_table,
handle, rdrname_p);
}
int
ccid_require_get_status (ccid_driver_t handle)
{
/* When a card reader supports interrupt transfer to check the
status of card, it is possible to submit only an interrupt
transfer, and no check is required by application layer. USB can
detect removal of a card and can detect removal of a reader.
*/
if (handle->ep_intr >= 0)
{
if (handle->id_vendor != VENDOR_SCM)
return 0;
/*
* For card reader with interrupt transfer support, ideally,
* removal is detected by intr_cb, but some card reader
* (e.g. SPR532) has a possible case of missing report to
* intr_cb, and another case of valid report to intr_cb.
*
* For such a reader, the removal should be able to be detected
* by PC_to_RDR_GetSlotStatus, too. Thus, calls to
* ccid_slot_status should go on wire even if "on_wire" is not
* requested.
*
*/
if (handle->transfer == NULL)
return 0;
}
/* Libusb actually detects the removal of USB device in use.
However, there is no good API to handle the removal (yet),
cleanly and with good portability.
There is libusb_set_pollfd_notifiers function, but it doesn't
offer libusb_device_handle* data to its callback. So, when it
watches multiple devices, there is no way to know which device is
removed.
Once, we will have a good programming interface of libusb, we can
list tokens (with no interrupt transfer support, but always with
card inserted) here to return 0, so that scdaemon can submit
minimum packet on wire.
*/
return 1;
}
static int
send_power_off (ccid_driver_t handle)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
msg[0] = PC_to_RDR_IccPowerOff;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (!rc)
bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
seqno, 2000, 0);
return rc;
}
static void
do_close_reader (ccid_driver_t handle)
{
int rc;
if (!handle->powered_off)
send_power_off (handle);
if (handle->transfer)
{
if (!handle->powered_off)
{
DEBUGOUT ("libusb_cancel_transfer\n");
rc = libusb_cancel_transfer (handle->transfer);
if (rc != LIBUSB_ERROR_NOT_FOUND)
while (!handle->powered_off)
{
DEBUGOUT ("libusb_handle_events_completed\n");
my_npth_unprotect ();
libusb_handle_events_completed (NULL, &handle->powered_off);
my_npth_protect ();
}
}
libusb_free_transfer (handle->transfer);
handle->transfer = NULL;
}
DEBUGOUT ("libusb_release_interface and libusb_close\n");
libusb_release_interface (handle->idev, handle->ifc_no);
--ccid_usb_thread_is_alive;
libusb_close (handle->idev);
handle->idev = NULL;
}
int
ccid_set_progress_cb (ccid_driver_t handle,
void (*cb)(void *, const char *, int, int, int),
void *cb_arg)
{
if (!handle)
return CCID_DRIVER_ERR_INV_VALUE;
handle->progress_cb = cb;
handle->progress_cb_arg = cb_arg;
return 0;
}
int
ccid_set_prompt_cb (ccid_driver_t handle,
void (*cb)(void *, int), void *cb_arg)
{
if (!handle)
return CCID_DRIVER_ERR_INV_VALUE;
handle->prompt_cb = cb;
handle->prompt_cb_arg = cb_arg;
return 0;
}
/* Close the reader HANDLE. */
int
ccid_close_reader (ccid_driver_t handle)
{
if (!handle)
return 0;
do_close_reader (handle);
free (handle);
return 0;
}
/* Return False if a card is present and powered. */
int
ccid_check_card_presence (ccid_driver_t handle)
{
(void)handle; /* Not yet implemented. */
return -1;
}
/* Write a MSG of length MSGLEN to the designated bulk out endpoint.
Returns 0 on success. */
static int
bulk_out (ccid_driver_t handle, unsigned char *msg, size_t msglen,
int no_debug)
{
int rc;
int transferred;
/* No need to continue and clutter the log with USB write error
messages after we got the first ENODEV. */
if (handle->enodev_seen)
return CCID_DRIVER_ERR_NO_READER;
if (debug_level && (!no_debug || debug_level >= 3))
{
switch (msglen? msg[0]:0)
{
case PC_to_RDR_IccPowerOn:
print_p2r_iccpoweron (msg, msglen);
break;
case PC_to_RDR_IccPowerOff:
print_p2r_iccpoweroff (msg, msglen);
break;
case PC_to_RDR_GetSlotStatus:
print_p2r_getslotstatus (msg, msglen);
break;
case PC_to_RDR_XfrBlock:
print_p2r_xfrblock (msg, msglen);
break;
case PC_to_RDR_GetParameters:
print_p2r_getparameters (msg, msglen);
break;
case PC_to_RDR_ResetParameters:
print_p2r_resetparameters (msg, msglen);
break;
case PC_to_RDR_SetParameters:
print_p2r_setparameters (msg, msglen);
break;
case PC_to_RDR_Escape:
print_p2r_escape (msg, msglen);
break;
case PC_to_RDR_IccClock:
print_p2r_iccclock (msg, msglen);
break;
case PC_to_RDR_T0APDU:
print_p2r_to0apdu (msg, msglen);
break;
case PC_to_RDR_Secure:
print_p2r_secure (msg, msglen);
break;
case PC_to_RDR_Mechanical:
print_p2r_mechanical (msg, msglen);
break;
case PC_to_RDR_Abort:
print_p2r_abort (msg, msglen);
break;
case PC_to_RDR_SetDataRate:
print_p2r_setdatarate (msg, msglen);
break;
default:
print_p2r_unknown (msg, msglen);
break;
}
}
my_npth_unprotect ();
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
msg, msglen, &transferred,
5000 /* ms timeout */);
my_npth_protect ();
if (rc == 0 && transferred == msglen)
return 0;
if (rc)
{
DEBUGOUT_1 ("usb_bulk_write error: %s\n", libusb_error_name (rc));
if (rc == LIBUSB_ERROR_NO_DEVICE)
{
handle->enodev_seen = 1;
return CCID_DRIVER_ERR_NO_READER;
}
}
return 0;
}
/* Read a maximum of LENGTH bytes from the bulk in endpoint into
BUFFER and return the actual read number if bytes in NREAD. SEQNO
is the sequence number used to send the request and EXPECTED_TYPE
the type of message we expect. Does checks on the ccid
header. TIMEOUT is the timeout value in ms. NO_DEBUG may be set to
avoid debug messages in case of no error; this can be overridden
with a glibal debug level of at least 3. Returns 0 on success. */
static int
bulk_in (ccid_driver_t handle, unsigned char *buffer, size_t length,
size_t *nread, int expected_type, int seqno, int timeout,
int no_debug)
{
int rc;
int msglen;
int notified = 0;
int bwi = 1;
/* Fixme: The next line for the current Valgrind without support
for USB IOCTLs. */
memset (buffer, 0, length);
retry:
my_npth_unprotect ();
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
buffer, length, &msglen, bwi*timeout);
my_npth_protect ();
if (rc)
{
DEBUGOUT_1 ("usb_bulk_read error: %s\n", libusb_error_name (rc));
if (rc == LIBUSB_ERROR_NO_DEVICE)
handle->enodev_seen = 1;
return map_libusb_error (rc);
}
if (msglen < 0)
return CCID_DRIVER_ERR_INV_VALUE; /* Faulty libusb. */
*nread = msglen;
if (msglen < 10)
{
DEBUGOUT_1 ("bulk-in msg too short (%u)\n", (unsigned int)msglen);
abort_cmd (handle, seqno, 0);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (buffer[5] != 0)
{
DEBUGOUT_1 ("unexpected bulk-in slot (%d)\n", buffer[5]);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (buffer[6] != seqno)
{
DEBUGOUT_2 ("bulk-in seqno does not match (%d/%d)\n",
seqno, buffer[6]);
/* Retry until we are synced again. */
goto retry;
}
/* We need to handle the time extension request before we check that
we got the expected message type. This is in particular required
for the Cherry keyboard which sends a time extension request for
each key hit. */
if (!(buffer[7] & 0x03) && (buffer[7] & 0xC0) == 0x80)
{
/* Card present and active, time extension requested. */
DEBUGOUT_2 ("time extension requested (%02X,%02X)\n",
buffer[7], buffer[8]);
bwi = 1;
if (buffer[8] != 0 && buffer[8] != 0xff)
bwi = buffer[8];
/* Gnuk enhancement to prompt user input by ack button */
if (buffer[8] == 0xff && !notified)
{
notified = 1;
handle->prompt_cb (handle->prompt_cb_arg, 1);
}
goto retry;
}
if (notified)
handle->prompt_cb (handle->prompt_cb_arg, 0);
if (buffer[0] != expected_type && buffer[0] != RDR_to_PC_SlotStatus)
{
DEBUGOUT_1 ("unexpected bulk-in msg type (%02x)\n", buffer[0]);
abort_cmd (handle, seqno, 0);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (debug_level && (!no_debug || debug_level >= 3))
{
switch (buffer[0])
{
case RDR_to_PC_DataBlock:
print_r2p_datablock (buffer, msglen);
break;
case RDR_to_PC_SlotStatus:
print_r2p_slotstatus (buffer, msglen);
break;
case RDR_to_PC_Parameters:
print_r2p_parameters (buffer, msglen);
break;
case RDR_to_PC_Escape:
print_r2p_escape (buffer, msglen);
break;
case RDR_to_PC_DataRate:
print_r2p_datarate (buffer, msglen);
break;
default:
print_r2p_unknown (buffer, msglen);
break;
}
}
if (CCID_COMMAND_FAILED (buffer))
{
int ec;
ec = CCID_ERROR_CODE (buffer);
print_command_failed (buffer);
if (ec == 0xEF)
return CCID_DRIVER_ERR_UI_CANCELLED;
else if (ec == 0xF0)
return CCID_DRIVER_ERR_UI_TIMEOUT;
}
/* Check whether a card is at all available. Note: If you add new
error codes here, check whether they need to be ignored in
send_escape_cmd. */
switch ((buffer[7] & 0x03))
{
case 0: /* no error */ break;
case 1: rc = CCID_DRIVER_ERR_CARD_INACTIVE; break;
case 2: rc = CCID_DRIVER_ERR_NO_CARD; break;
case 3: /* RFU */ break;
}
if (rc)
{
/*
* Communication failure by device side.
* Possibly, it was forcibly suspended and resumed.
*/
if (handle->ep_intr < 0)
{
DEBUGOUT ("CCID: card inactive/removed\n");
handle->powered_off = 1;
}
#if defined(GNUPG_MAJOR_VERSION)
scd_kick_the_loop ();
#endif
}
return rc;
}
/* Send an abort sequence and wait until everything settled. */
static int
abort_cmd (ccid_driver_t handle, int seqno, int init)
{
int rc;
unsigned char dummybuf[8];
unsigned char msg[100];
int msglen;
seqno &= 0xff;
DEBUGOUT_1 ("sending abort sequence for seqno %d\n", seqno);
/* Send the abort command to the control pipe. Note that we don't
need to keep track of sent abort commands because there should
never be another thread using the same slot concurrently. */
my_npth_unprotect ();
rc = libusb_control_transfer (handle->idev,
0x21,/* bmRequestType: host-to-device,
class specific, to interface. */
1, /* ABORT */
(seqno << 8 | 0 /* slot */),
handle->ifc_no,
dummybuf, 0,
1000 /* ms timeout */);
my_npth_protect ();
if (rc)
{
DEBUGOUT_1 ("usb_control_msg error: %s\n", libusb_error_name (rc));
if (!init)
return map_libusb_error (rc);
}
/* Now send the abort command to the bulk out pipe using the same
SEQNO and SLOT. Do this in a loop to so that all seqno are
tried. */
seqno--; /* Adjust for next increment. */
do
{
int transferred;
seqno++;
msg[0] = PC_to_RDR_Abort;
msg[5] = 0; /* slot */
msg[6] = seqno;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msglen = 10;
set_msg_len (msg, 0);
my_npth_unprotect ();
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_out,
msg, msglen, &transferred,
init? 100: 5000 /* ms timeout */);
my_npth_protect ();
if (rc == 0 && transferred == msglen)
rc = 0;
else if (rc)
DEBUGOUT_1 ("usb_bulk_write error in abort_cmd: %s\n",
libusb_error_name (rc));
if (rc)
return map_libusb_error (rc);
my_npth_unprotect ();
rc = libusb_bulk_transfer (handle->idev, handle->ep_bulk_in,
msg, sizeof msg, &msglen,
init? 100: 5000 /*ms timeout*/);
my_npth_protect ();
if (rc)
{
DEBUGOUT_1 ("usb_bulk_read error in abort_cmd: %s\n",
libusb_error_name (rc));
if (init && rc == LIBUSB_ERROR_TIMEOUT)
continue;
else
return map_libusb_error (rc);
}
if (msglen < 10)
{
DEBUGOUT_1 ("bulk-in msg in abort_cmd too short (%u)\n",
(unsigned int)msglen);
return CCID_DRIVER_ERR_INV_VALUE;
}
if (msg[5] != 0)
{
DEBUGOUT_1 ("unexpected bulk-in slot (%d) in abort_cmd\n", msg[5]);
return CCID_DRIVER_ERR_INV_VALUE;
}
DEBUGOUT_3 ("status: %02X error: %02X octet[9]: %02X\n",
msg[7], msg[8], msg[9]);
if (CCID_COMMAND_FAILED (msg))
print_command_failed (msg);
}
while (rc == LIBUSB_ERROR_TIMEOUT
|| (msg[0] != RDR_to_PC_SlotStatus && msg[5] != 0 && msg[6] != seqno));
handle->seqno = ((seqno + 1) & 0xff);
DEBUGOUT ("sending abort sequence succeeded\n");
return 0;
}
/* Note that this function won't return the error codes NO_CARD or
CARD_INACTIVE. IF RESULT is not NULL, the result from the
operation will get returned in RESULT and its length in RESULTLEN.
If the response is larger than RESULTMAX, an error is returned and
the required buffer length returned in RESULTLEN. */
static int
send_escape_cmd (ccid_driver_t handle,
const unsigned char *data, size_t datalen,
unsigned char *result, size_t resultmax, size_t *resultlen)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
if (resultlen)
*resultlen = 0;
if (datalen > sizeof msg - 10)
return CCID_DRIVER_ERR_INV_VALUE; /* Escape data too large. */
msg[0] = PC_to_RDR_Escape;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
memcpy (msg+10, data, datalen);
msglen = 10 + datalen;
set_msg_len (msg, datalen);
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Escape,
seqno, 5000, 0);
if (result)
switch (rc)
{
/* We need to ignore certain errorcode here. */
case 0:
case CCID_DRIVER_ERR_CARD_INACTIVE:
case CCID_DRIVER_ERR_NO_CARD:
{
if (msglen > resultmax)
rc = CCID_DRIVER_ERR_INV_VALUE; /* Response too large. */
else
{
memcpy (result, msg, msglen);
if (resultlen)
*resultlen = msglen;
rc = 0;
}
}
break;
default:
break;
}
return rc;
}
int
ccid_transceive_escape (ccid_driver_t handle,
const unsigned char *data, size_t datalen,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
return send_escape_cmd (handle, data, datalen, resp, maxresplen, nresp);
}
/* experimental */
int
ccid_poll (ccid_driver_t handle)
{
int rc;
unsigned char msg[10];
int msglen;
int i, j;
rc = libusb_interrupt_transfer (handle->idev, handle->ep_intr,
msg, sizeof msg, &msglen,
0 /* ms timeout */ );
if (rc == LIBUSB_ERROR_TIMEOUT)
return 0;
if (rc)
{
DEBUGOUT_1 ("usb_intr_read error: %s\n", libusb_error_name (rc));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msglen < 1)
{
DEBUGOUT ("intr-in msg too short\n");
return CCID_DRIVER_ERR_INV_VALUE;
}
if (msg[0] == RDR_to_PC_NotifySlotChange)
{
DEBUGOUT ("notify slot change:");
for (i=1; i < msglen; i++)
for (j=0; j < 4; j++)
DEBUGOUT_CONT_3 (" %d:%c%c",
(i-1)*4+j,
(msg[i] & (1<<(j*2)))? 'p':'-',
(msg[i] & (2<<(j*2)))? '*':' ');
DEBUGOUT_LF ();
}
else if (msg[0] == RDR_to_PC_HardwareError)
{
DEBUGOUT ("hardware error occurred\n");
}
else
{
DEBUGOUT_1 ("unknown intr-in msg of type %02X\n", msg[0]);
}
return 0;
}
/* Note that this function won't return the error codes NO_CARD or
CARD_INACTIVE */
int
ccid_slot_status (ccid_driver_t handle, int *statusbits, int on_wire)
{
int rc;
unsigned char msg[100];
size_t msglen;
unsigned char seqno;
int retries = 0;
if (handle->powered_off)
return CCID_DRIVER_ERR_NO_READER;
/* If the card (with its lower-level driver) doesn't require
GET_STATUS on wire (because it supports INTERRUPT transfer for
status change, or it's a token which has a card always inserted),
no need to send on wire. */
if (!on_wire && !ccid_require_get_status (handle))
{
/* Setup interrupt transfer at the initial call of slot_status
with ON_WIRE == 0 */
if (handle->transfer == NULL)
ccid_setup_intr (handle);
*statusbits = 0;
return 0;
}
retry:
msg[0] = PC_to_RDR_GetSlotStatus;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
rc = bulk_out (handle, msg, 10, 1);
if (rc)
return rc;
/* Note that we set the NO_DEBUG flag here, so that the logs won't
get cluttered up by a ticker function checking for the slot
status and debugging enabled. */
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_SlotStatus,
seqno, retries? 1000 : 200, 1);
if ((rc == CCID_DRIVER_ERR_CARD_IO_ERROR || rc == CCID_DRIVER_ERR_USB_TIMEOUT)
&& retries < 3)
{
if (!retries)
{
DEBUGOUT ("USB: CALLING USB_CLEAR_HALT\n");
my_npth_unprotect ();
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
libusb_clear_halt (handle->idev, handle->ep_bulk_out);
my_npth_protect ();
}
else
DEBUGOUT ("USB: RETRYING bulk_in AGAIN\n");
retries++;
goto retry;
}
if (rc && rc != CCID_DRIVER_ERR_NO_CARD && rc != CCID_DRIVER_ERR_CARD_INACTIVE)
return rc;
*statusbits = (msg[7] & 3);
return 0;
}
/* Parse ATR string (of ATRLEN) and update parameters at PARAM.
Calling this routine, it should prepare default values at PARAM
beforehand. This routine assumes that card is accessed by T=1
protocol. It doesn't analyze historical bytes at all.
Returns < 0 value on error:
-1 for parse error or integrity check error
-2 for card doesn't support T=1 protocol
-3 for parameters are nod explicitly defined by ATR
-4 for this driver doesn't support CRC
Returns >= 0 on success:
0 for card is negotiable mode
1 for card is specific mode (and not negotiable)
*/
static int
update_param_by_atr (unsigned char *param, unsigned char *atr, size_t atrlen)
{
int i = -1;
int t, y, chk;
int historical_bytes_num, negotiable = 1;
#define NEXTBYTE() do { i++; if (atrlen <= i) return -1; } while (0)
NEXTBYTE ();
if (atr[i] == 0x3F)
param[1] |= 0x02; /* Convention is inverse. */
NEXTBYTE ();
y = (atr[i] >> 4);
historical_bytes_num = atr[i] & 0x0f;
NEXTBYTE ();
if ((y & 1))
{
param[0] = atr[i]; /* TA1 - Fi & Di */
NEXTBYTE ();
}
if ((y & 2))
NEXTBYTE (); /* TB1 - ignore */
if ((y & 4))
{
param[2] = atr[i]; /* TC1 - Guard Time */
NEXTBYTE ();
}
if ((y & 8))
{
y = (atr[i] >> 4); /* TD1 */
t = atr[i] & 0x0f;
NEXTBYTE ();
if ((y & 1))
{ /* TA2 - PPS mode */
if ((atr[i] & 0x0f) != 1)
return -2; /* Wrong card protocol (!= 1). */
if ((atr[i] & 0x10) != 0x10)
return -3; /* Transmission parameters are implicitly defined. */
negotiable = 0; /* TA2 means specific mode. */
NEXTBYTE ();
}
if ((y & 2))
NEXTBYTE (); /* TB2 - ignore */
if ((y & 4))
NEXTBYTE (); /* TC2 - ignore */
if ((y & 8))
{
y = (atr[i] >> 4); /* TD2 */
t = atr[i] & 0x0f;
NEXTBYTE ();
}
else
y = 0;
while (y)
{
if ((y & 1))
{ /* TAx */
if (t == 1)
param[5] = atr[i]; /* IFSC */
else if (t == 15)
/* XXX: check voltage? */
param[4] = (atr[i] >> 6); /* ClockStop */
NEXTBYTE ();
}
if ((y & 2))
{
if (t == 1)
param[3] = atr[i]; /* TBx - BWI & CWI */
NEXTBYTE ();
}
if ((y & 4))
{
if (t == 1)
param[1] |= (atr[i] & 0x01); /* TCx - LRC/CRC */
NEXTBYTE ();
if (param[1] & 0x01)
return -4; /* CRC not supported yet. */
}
if ((y & 8))
{
y = (atr[i] >> 4); /* TDx */
t = atr[i] & 0x0f;
NEXTBYTE ();
}
else
y = 0;
}
}
i += historical_bytes_num - 1;
NEXTBYTE ();
if (atrlen != i+1)
return -1;
#undef NEXTBYTE
chk = 0;
do
{
chk ^= atr[i];
i--;
}
while (i > 0);
if (chk != 0)
return -1;
return negotiable;
}
/* Return the ATR of the card. This is not a cached value and thus an
actual reset is done. */
int
ccid_get_atr (ccid_driver_t handle,
unsigned char *atr, size_t maxatrlen, size_t *atrlen)
{
int rc;
int statusbits;
unsigned char msg[100];
unsigned char *tpdu;
size_t msglen, tpdulen;
unsigned char seqno;
int use_crc = 0;
unsigned int edc;
int tried_iso = 0;
int got_param;
unsigned char param[7] = { /* For Protocol T=1 */
0x11, /* bmFindexDindex */
0x10, /* bmTCCKST1 */
0x00, /* bGuardTimeT1 */
0x4d, /* bmWaitingIntegersT1 */
0x00, /* bClockStop */
0x20, /* bIFSC */
0x00 /* bNadValue */
};
/* First check whether a card is available. */
rc = ccid_slot_status (handle, &statusbits, 1);
if (rc)
return rc;
if (statusbits == 2)
return CCID_DRIVER_ERR_NO_CARD;
/*
* In the first invocation of ccid_slot_status, card reader may
* return CCID_DRIVER_ERR_CARD_INACTIVE and handle->powered_off may
* become 1. Because inactive card is no problem (we are turning it
* ON here), clear the flag.
*/
handle->powered_off = 0;
/* For an inactive and also for an active card, issue the PowerOn
command to get the ATR. */
again:
msg[0] = PC_to_RDR_IccPowerOn;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
/* power select (0=auto, 1=5V, 2=3V, 3=1.8V) */
msg[7] = handle->auto_voltage ? 0 : 1;
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
seqno, 5000, 0);
if (rc)
return rc;
if (!tried_iso && CCID_COMMAND_FAILED (msg) && CCID_ERROR_CODE (msg) == 0xbb
&& ((handle->id_vendor == VENDOR_CHERRY
&& handle->id_product == 0x0005)
|| (handle->id_vendor == VENDOR_GEMPC
&& handle->id_product == 0x4433)
))
{
tried_iso = 1;
/* Try switching to ISO mode. */
if (!send_escape_cmd (handle, (const unsigned char*)"\xF1\x01", 2,
NULL, 0, NULL))
goto again;
}
else if (statusbits == 0 && CCID_COMMAND_FAILED (msg))
{
/* Card was active already, and something went wrong with
PC_to_RDR_IccPowerOn command. It may be baud-rate mismatch
between the card and the reader. To recover from this state,
send PC_to_RDR_IccPowerOff command to reset the card and try
again.
*/
rc = send_power_off (handle);
if (rc)
return rc;
statusbits = 1;
goto again;
}
else if (CCID_COMMAND_FAILED (msg))
return CCID_DRIVER_ERR_CARD_IO_ERROR;
handle->powered_off = 0;
if (atr)
{
size_t n = msglen - 10;
if (n > maxatrlen)
n = maxatrlen;
memcpy (atr, msg+10, n);
*atrlen = n;
}
param[6] = handle->nonnull_nad? ((1 << 4) | 0): 0;
rc = update_param_by_atr (param, msg+10, msglen - 10);
if (rc < 0)
{
DEBUGOUT_1 ("update_param_by_atr failed: %d\n", rc);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
got_param = 0;
if (handle->auto_param)
{
msg[0] = PC_to_RDR_GetParameters;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (!rc)
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
seqno, 2000, 0);
if (rc)
DEBUGOUT ("GetParameters failed\n");
else if (msglen == 17 && msg[9] == 1)
got_param = 1;
}
else if (handle->auto_pps)
;
else if (rc == 1) /* It's negotiable, send PPS. */
{
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0;
msg[8] = 0;
msg[9] = 0;
msg[10] = 0xff; /* PPSS */
msg[11] = 0x11; /* PPS0: PPS1, Protocol T=1 */
msg[12] = param[0]; /* PPS1: Fi / Di */
msg[13] = 0xff ^ 0x11 ^ param[0]; /* PCK */
set_msg_len (msg, 4);
msglen = 10 + 4;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_DataBlock,
seqno, 5000, 0);
if (rc)
return rc;
if (msglen != 10 + 4)
{
DEBUGOUT_1 ("Setting PPS failed: %zu\n", msglen);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
if (msg[10] != 0xff || msg[11] != 0x11 || msg[12] != param[0])
{
DEBUGOUT_1 ("Setting PPS failed: 0x%02x\n", param[0]);
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
/* Setup parameters to select T=1. */
msg[0] = PC_to_RDR_SetParameters;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 1; /* Select T=1. */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
if (!got_param)
memcpy (&msg[10], param, 7);
set_msg_len (msg, 7);
msglen = 10 + 7;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen, RDR_to_PC_Parameters,
seqno, 5000, 0);
if (rc)
DEBUGOUT ("SetParameters failed (ignored)\n");
if (!rc && msglen > 15 && msg[15] >= 16 && msg[15] <= 254 )
handle->ifsc = msg[15];
else
handle->ifsc = 128; /* Something went wrong, assume 128 bytes. */
if (handle->nonnull_nad && msglen > 16 && msg[16] == 0)
{
DEBUGOUT ("Use Null-NAD, clearing handle->nonnull_nad.\n");
handle->nonnull_nad = 0;
}
handle->t1_ns = 0;
handle->t1_nr = 0;
/* Send an S-Block with our maximum IFSD to the CCID. */
if (!handle->apdu_level && !handle->auto_ifsd)
{
tpdu = msg+10;
/* NAD: DAD=1, SAD=0 */
tpdu[0] = handle->nonnull_nad? ((1 << 4) | 0): 0;
tpdu[1] = (0xc0 | 0 | 1); /* S-block request: change IFSD */
tpdu[2] = 1;
tpdu[3] = handle->max_ifsd? handle->max_ifsd : 32;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0;
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, tpdulen);
msglen = 10 + tpdulen;
if (debug_level > 1)
DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10)
: !!(msg[11] & 0x40)),
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, 5000, 0);
if (rc)
return rc;
tpdu = msg + 10;
tpdulen = msglen - 10;
if (tpdulen < 4)
return CCID_DRIVER_ERR_ABORTED;
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10)
: !!(msg[11] & 0x40)),
((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
if ((tpdu[1] & 0xe0) != 0xe0 || tpdu[2] != 1)
{
DEBUGOUT ("invalid response for S-block (Change-IFSD)\n");
return -1;
}
DEBUGOUT_1 ("IFSD has been set to %d\n", tpdu[3]);
}
ccid_vendor_specific_setup (handle);
return 0;
}
static unsigned int
compute_edc (const unsigned char *data, size_t datalen, int use_crc)
{
if (use_crc)
{
return 0x42; /* Not yet implemented. */
}
else
{
unsigned char crc = 0;
for (; datalen; datalen--)
crc ^= *data++;
return crc;
}
}
/* Return true if APDU is an extended length one. */
static int
is_exlen_apdu (const unsigned char *apdu, size_t apdulen)
{
if (apdulen < 7 || apdu[4])
return 0; /* Too short or no Z byte. */
return 1;
}
/* Helper for ccid_transceive used for APDU level exchanges. */
static int
ccid_transceive_apdu_level (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_len,
unsigned char *resp, size_t maxresplen,
size_t *nresp)
{
int rc;
unsigned char msg[CCID_MAX_BUF];
const unsigned char *apdu_p;
size_t apdu_part_len;
size_t msglen;
unsigned char seqno;
int bwi = 0;
unsigned char chain = 0;
if (apdu_len == 0 || apdu_len > sizeof (msg) - 10)
return CCID_DRIVER_ERR_INV_VALUE; /* Invalid length. */
apdu_p = apdu_buf;
while (1)
{
apdu_part_len = apdu_len;
if (apdu_part_len > handle->max_ccid_msglen - 10)
{
apdu_part_len = handle->max_ccid_msglen - 10;
chain |= 0x01;
}
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = bwi;
msg[8] = chain;
msg[9] = 0;
memcpy (msg+10, apdu_p, apdu_part_len);
set_msg_len (msg, apdu_part_len);
msglen = 10 + apdu_part_len;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
apdu_p += apdu_part_len;
apdu_len -= apdu_part_len;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
if (!(chain & 0x01))
break;
chain = 0x02;
}
apdu_len = 0;
while (1)
{
apdu_part_len = msglen - 10;
if (resp && apdu_len + apdu_part_len <= maxresplen)
memcpy (resp + apdu_len, msg+10, apdu_part_len);
apdu_len += apdu_part_len;
if (!(msg[9] & 0x01))
break;
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = bwi;
msg[8] = 0x10; /* Request next data block */
msg[9] = 0;
set_msg_len (msg, 0);
msglen = 10;
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
rc = bulk_in (handle, msg, sizeof msg, &msglen,
RDR_to_PC_DataBlock, seqno, CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
}
if (resp)
{
if (apdu_len > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)apdu_len, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
*nresp = apdu_len;
}
return 0;
}
/*
Protocol T=1 overview
Block Structure:
Prologue Field:
1 byte Node Address (NAD)
1 byte Protocol Control Byte (PCB)
1 byte Length (LEN)
Information Field:
0-254 byte APDU or Control Information (INF)
Epilogue Field:
1 byte Error Detection Code (EDC)
NAD:
bit 7 unused
bit 4..6 Destination Node Address (DAD)
bit 3 unused
bit 2..0 Source Node Address (SAD)
If node addresses are not used, SAD and DAD should be set to 0 on
the first block sent to the card. If they are used they should
have different values (0 for one is okay); that first block sets up
the addresses of the nodes.
PCB:
Information Block (I-Block):
bit 7 0
bit 6 Sequence number (yep, that is modulo 2)
bit 5 Chaining flag
bit 4..0 reserved
Received-Ready Block (R-Block):
bit 7 1
bit 6 0
bit 5 0
bit 4 Sequence number
bit 3..0 0 = no error
1 = EDC or parity error
2 = other error
other values are reserved
Supervisory Block (S-Block):
bit 7 1
bit 6 1
bit 5 clear=request,set=response
bit 4..0 0 = resynchronization request
1 = information field size request
2 = abort request
3 = extension of BWT request
4 = VPP error
other values are reserved
*/
int
ccid_transceive (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_buflen,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
int rc;
/* The size of the buffer used to be 10+259. For the via_escape
hack we need one extra byte, thus 11+259. */
unsigned char send_buffer[11+259], recv_buffer[11+259];
const unsigned char *apdu;
size_t apdulen;
unsigned char *msg, *tpdu, *p;
size_t msglen, tpdulen, last_tpdulen, n;
unsigned char seqno;
unsigned int edc;
int use_crc = 0;
int hdrlen, pcboff;
size_t dummy_nresp;
int via_escape = 0;
int next_chunk = 1;
int sending = 1;
int retries = 0;
int resyncing = 0;
int nad_byte;
int wait_more = 0;
if (!nresp)
nresp = &dummy_nresp;
*nresp = 0;
/* Smarter readers allow sending APDUs directly; divert here. */
if (handle->apdu_level)
{
/* We employ a hack for Omnikey readers which are able to send
TPDUs using an escape sequence. There is no documentation
but the Windows driver does it this way. Tested using a
CM6121. This method works also for the Cherry XX44
keyboards; however there are problems with the
ccid_transceive_secure which leads to a loss of sync on the
CCID level. If Cherry wants to make their keyboard work
again, they should hand over some docs. */
if ((handle->id_vendor == VENDOR_OMNIKEY)
&& handle->apdu_level < 2
&& is_exlen_apdu (apdu_buf, apdu_buflen))
via_escape = 1;
else
return ccid_transceive_apdu_level (handle, apdu_buf, apdu_buflen,
resp, maxresplen, nresp);
}
/* The other readers we support require sending TPDUs. */
tpdulen = 0; /* Avoid compiler warning about no initialization. */
msg = send_buffer;
hdrlen = via_escape? 11 : 10;
/* NAD: DAD=1, SAD=0 */
nad_byte = handle->nonnull_nad? ((1 << 4) | 0): 0;
if (via_escape)
nad_byte = 0;
last_tpdulen = 0; /* Avoid gcc warning (controlled by RESYNCING). */
for (;;)
{
if (next_chunk)
{
next_chunk = 0;
apdu = apdu_buf;
apdulen = apdu_buflen;
log_assert (apdulen);
/* Construct an I-Block. */
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = ((handle->t1_ns & 1) << 6); /* I-block */
if (apdulen > handle->ifsc )
{
apdulen = handle->ifsc;
apdu_buf += handle->ifsc;
apdu_buflen -= handle->ifsc;
tpdu[1] |= (1 << 5); /* Set more bit. */
}
tpdu[2] = apdulen;
memcpy (tpdu+3, apdu, apdulen);
tpdulen = 3 + apdulen;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
}
if (via_escape)
{
msg[0] = PC_to_RDR_Escape;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* RFU */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msg[10] = 0x1a; /* Omnikey command to send a TPDU. */
set_msg_len (msg, 1 + tpdulen);
}
else
{
msg[0] = PC_to_RDR_XfrBlock;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = wait_more; /* bBWI */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
set_msg_len (msg, tpdulen);
}
msglen = hdrlen + tpdulen;
if (!resyncing)
last_tpdulen = tpdulen;
pcboff = hdrlen+1;
if (debug_level > 1)
DEBUGOUT_3 ("T=1: put %c-block seq=%d%s\n",
((msg[pcboff] & 0xc0) == 0x80)? 'R' :
(msg[pcboff] & 0x80)? 'S' : 'I',
((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
: !!(msg[pcboff] & 0x40)),
(!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
" [more]":""));
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
msg = recv_buffer;
rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
via_escape? RDR_to_PC_Escape : RDR_to_PC_DataBlock, seqno,
(wait_more ? wait_more : 1) * CCID_CMD_TIMEOUT, 0);
if (rc)
return rc;
tpdu = msg + hdrlen;
tpdulen = msglen - hdrlen;
resyncing = 0;
if (tpdulen < 4)
{
my_npth_unprotect ();
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
my_npth_protect ();
return CCID_DRIVER_ERR_ABORTED;
}
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[pcboff] & 0xc0) == 0x80)? 'R' :
(msg[pcboff] & 0x80)? 'S' : 'I',
((msg[pcboff] & 0x80)? !!(msg[pcboff]& 0x10)
: !!(msg[pcboff] & 0x40)),
((msg[pcboff] & 0xc0) == 0x80)? (msg[pcboff] & 0x0f) : 0,
(!(msg[pcboff] & 0x80) && (msg[pcboff] & 0x20)?
" [more]":""));
wait_more = 0;
if (!(tpdu[1] & 0x80))
{ /* This is an I-block. */
retries = 0;
if (sending)
{ /* last block sent was successful. */
handle->t1_ns ^= 1;
sending = 0;
}
if (!!(tpdu[1] & 0x40) != handle->t1_nr)
{ /* Response does not match our sequence number. */
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4 | 2); /* R-block */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
continue;
}
handle->t1_nr ^= 1;
p = tpdu + 3; /* Skip the prologue field. */
n = tpdulen - 3 - 1; /* Strip the epilogue field. */
/* fixme: verify the checksum. */
if (resp)
{
if (n > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)n, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, p, n);
resp += n;
*nresp += n;
maxresplen -= n;
}
if (!(tpdu[1] & 0x20))
return 0; /* No chaining requested - ready. */
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0x80 | (handle->t1_nr & 1) << 4); /* R-block */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
}
else if ((tpdu[1] & 0xc0) == 0x80)
{ /* This is a R-block. */
if ( (tpdu[1] & 0x0f))
{
retries++;
if (via_escape && retries == 1 && (msg[pcboff] & 0x0f))
{
/* Error probably due to switching to TPDU. Send a
resync request. We use the recv_buffer so that
we don't corrupt the send_buffer. */
msg = recv_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = 0xc0; /* S-block resync request. */
tpdu[2] = 0;
tpdulen = 3;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
resyncing = 1;
DEBUGOUT ("T=1: requesting resync\n");
}
else if (retries > 3)
{
DEBUGOUT ("T=1: 3 failed retries\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else
{
/* Error: repeat last block */
msg = send_buffer;
tpdulen = last_tpdulen;
}
}
else if (sending && !!(tpdu[1] & 0x10) == handle->t1_ns)
{ /* Response does not match our sequence number. */
DEBUGOUT ("R-block with wrong seqno received on more bit\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if (sending)
{ /* Send next chunk. */
retries = 0;
msg = send_buffer;
next_chunk = 1;
handle->t1_ns ^= 1;
}
else
{
DEBUGOUT ("unexpected ACK R-block received\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
else
{ /* This is a S-block. */
retries = 0;
DEBUGOUT_2 ("T=1: S-block %s received cmd=%d\n",
(tpdu[1] & 0x20)? "response": "request",
(tpdu[1] & 0x1f));
if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 1 && tpdu[2] == 1)
{
/* Information field size request. */
unsigned char ifsc = tpdu[3];
if (ifsc < 16 || ifsc > 254)
return CCID_DRIVER_ERR_CARD_IO_ERROR;
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0xc0 | 0x20 | 1); /* S-block response */
tpdu[2] = 1;
tpdu[3] = ifsc;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
DEBUGOUT_1 ("T=1: requesting an ifsc=%d\n", ifsc);
}
else if ( !(tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 3 && tpdu[2])
{
/* Wait time extension request. */
unsigned char bwi = tpdu[3];
wait_more = bwi;
msg = send_buffer;
tpdu = msg + hdrlen;
tpdu[0] = nad_byte;
tpdu[1] = (0xc0 | 0x20 | 3); /* S-block response */
tpdu[2] = 1;
tpdu[3] = bwi;
tpdulen = 4;
edc = compute_edc (tpdu, tpdulen, use_crc);
if (use_crc)
tpdu[tpdulen++] = (edc >> 8);
tpdu[tpdulen++] = edc;
DEBUGOUT_1 ("T=1: waittime extension of bwi=%d\n", bwi);
print_progress (handle);
}
else if ( (tpdu[1] & 0x20) && (tpdu[1] & 0x1f) == 0 && !tpdu[2])
{
DEBUGOUT ("T=1: resync ack from reader\n");
/* Repeat previous block. */
msg = send_buffer;
tpdulen = last_tpdulen;
}
else
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
} /* end T=1 protocol loop. */
return 0;
}
/* Send the CCID Secure command to the reader. APDU_BUF should
contain the APDU template. PIN_MODE defines how the pin gets
formatted:
1 := The PIN is ASCII encoded and of variable length. The
length of the PIN entered will be put into Lc by the reader.
The APDU should me made up of 4 bytes without Lc.
PINLEN_MIN and PINLEN_MAX define the limits for the pin length. 0
may be used t enable reasonable defaults.
When called with RESP and NRESP set to NULL, the function will
merely check whether the reader supports the secure command for the
given APDU and PIN_MODE. */
int
ccid_transceive_secure (ccid_driver_t handle,
const unsigned char *apdu_buf, size_t apdu_buflen,
pininfo_t *pininfo,
unsigned char *resp, size_t maxresplen, size_t *nresp)
{
int rc;
unsigned char send_buffer[10+259], recv_buffer[10+259];
unsigned char *msg, *tpdu, *p;
size_t msglen, tpdulen, n;
unsigned char seqno;
size_t dummy_nresp;
int testmode;
int cherry_mode = 0;
int add_zero = 0;
int enable_varlen = 0;
testmode = !resp && !nresp;
if (!nresp)
nresp = &dummy_nresp;
*nresp = 0;
if (apdu_buflen >= 4 && apdu_buf[1] == 0x20 && (handle->has_pinpad & 1))
;
else if (apdu_buflen >= 4 && apdu_buf[1] == 0x24 && (handle->has_pinpad & 2))
;
else
return CCID_DRIVER_ERR_NO_PINPAD;
if (!pininfo->minlen)
pininfo->minlen = 1;
if (!pininfo->maxlen)
pininfo->maxlen = 15;
/* Note that the 25 is the maximum value the SPR532 allows. */
if (pininfo->minlen < 1 || pininfo->minlen > 25
|| pininfo->maxlen < 1 || pininfo->maxlen > 25
|| pininfo->minlen > pininfo->maxlen)
return CCID_DRIVER_ERR_INV_VALUE;
/* We have only tested a few readers so better don't risk anything
and do not allow the use with other readers. */
switch (handle->id_vendor)
{
case VENDOR_SCM: /* Tested with SPR 532. */
case VENDOR_KAAN: /* Tested with KAAN Advanced (1.02). */
case VENDOR_FSIJ: /* Tested with Gnuk (0.21). */
pininfo->maxlen = 25;
enable_varlen = 1;
break;
case VENDOR_REINER:/* Tested with cyberJack go */
case VENDOR_VASCO: /* Tested with DIGIPASS 920 */
enable_varlen = 1;
break;
case VENDOR_CHERRY:
pininfo->maxlen = 15;
enable_varlen = 1;
/* The CHERRY XX44 keyboard echos an asterisk for each entered
character on the keyboard channel. We use a special variant
of PC_to_RDR_Secure which directs these characters to the
smart card's bulk-in channel. We also need to append a zero
Lc byte to the APDU. It seems that it will be replaced with
the actual length instead of being appended before the APDU
is send to the card. */
add_zero = 1;
if (handle->id_product != CHERRY_ST2000)
cherry_mode = 1;
break;
case VENDOR_NXP:
if (handle->id_product == CRYPTOUCAN)
{
pininfo->maxlen = 25;
enable_varlen = 1;
break;
}
return CCID_DRIVER_ERR_NOT_SUPPORTED;
case VENDOR_GEMPC:
if (handle->id_product == GEMPC_PINPAD)
{
enable_varlen = 0;
pininfo->minlen = 4;
pininfo->maxlen = 8;
break;
}
else if (handle->id_product == GEMPC_EZIO)
{
pininfo->maxlen = 25;
enable_varlen = 1;
break;
}
return CCID_DRIVER_ERR_NOT_SUPPORTED;
default:
if ((handle->id_vendor == VENDOR_VEGA &&
handle->id_product == VEGA_ALPHA))
{
enable_varlen = 0;
pininfo->minlen = 4;
pininfo->maxlen = 8;
break;
}
return CCID_DRIVER_ERR_NOT_SUPPORTED;
}
if (enable_varlen)
pininfo->fixedlen = 0;
if (testmode)
return 0; /* Success */
if (pininfo->fixedlen < 0 || pininfo->fixedlen >= 16)
return CCID_DRIVER_ERR_NOT_SUPPORTED;
ccid_vendor_specific_pinpad_setup (handle);
msg = send_buffer;
msg[0] = cherry_mode? 0x89 : PC_to_RDR_Secure;
msg[5] = 0; /* slot */
msg[6] = seqno = handle->seqno++;
msg[7] = 0; /* bBWI */
msg[8] = 0; /* RFU */
msg[9] = 0; /* RFU */
msg[10] = apdu_buf[1] == 0x20 ? 0 : 1;
/* Perform PIN verification or PIN modification. */
msg[11] = 0; /* Timeout in seconds. */
msg[12] = 0x82; /* bmFormatString: Byte, pos=0, left, ASCII. */
if (handle->id_vendor == VENDOR_SCM)
{
/* For the SPR532 the next 2 bytes need to be zero. We do this
for all SCM products. Kudos to Martin Paljak for this
hint. */
msg[13] = msg[14] = 0;
}
else
{
msg[13] = pininfo->fixedlen; /* bmPINBlockString:
0 bits of pin length to insert.
PIN block size by fixedlen. */
msg[14] = 0x00; /* bmPINLengthFormat:
Units are bytes, position is 0. */
}
msglen = 15;
if (apdu_buf[1] == 0x24)
{
msg[msglen++] = 0; /* bInsertionOffsetOld */
msg[msglen++] = pininfo->fixedlen; /* bInsertionOffsetNew */
}
/* The following is a little endian word. */
msg[msglen++] = pininfo->maxlen; /* wPINMaxExtraDigit-Maximum. */
msg[msglen++] = pininfo->minlen; /* wPINMaxExtraDigit-Minimum. */
if (apdu_buf[1] == 0x24)
msg[msglen++] = apdu_buf[2] == 0 ? 0x03 : 0x01;
/* bConfirmPIN
* 0x00: new PIN once
* 0x01: new PIN twice (confirmation)
* 0x02: old PIN and new PIN once
* 0x03: old PIN and new PIN twice (confirmation)
*/
msg[msglen] = 0x02; /* bEntryValidationCondition:
Validation key pressed */
if (pininfo->minlen && pininfo->maxlen && pininfo->minlen == pininfo->maxlen)
msg[msglen] |= 0x01; /* Max size reached. */
msglen++;
if (apdu_buf[1] == 0x20)
msg[msglen++] = 0x01; /* bNumberMessage. */
else
msg[msglen++] = 0x03; /* bNumberMessage. */
msg[msglen++] = 0x09; /* wLangId-Low: English FIXME: use the first entry. */
msg[msglen++] = 0x04; /* wLangId-High. */
if (apdu_buf[1] == 0x20)
msg[msglen++] = 0; /* bMsgIndex. */
else
{
msg[msglen++] = 0; /* bMsgIndex1. */
msg[msglen++] = 1; /* bMsgIndex2. */
msg[msglen++] = 2; /* bMsgIndex3. */
}
/* Calculate Lc. */
n = pininfo->fixedlen;
if (apdu_buf[1] == 0x24)
n += pininfo->fixedlen;
/* bTeoProlog follows: */
msg[msglen++] = handle->nonnull_nad? ((1 << 4) | 0): 0;
msg[msglen++] = ((handle->t1_ns & 1) << 6); /* I-block */
if (n)
msg[msglen++] = n + 5; /* apdulen should be filled for fixed length. */
else
msg[msglen++] = 0; /* The apdulen will be filled in by the reader. */
/* APDU follows: */
msg[msglen++] = apdu_buf[0]; /* CLA */
msg[msglen++] = apdu_buf[1]; /* INS */
msg[msglen++] = apdu_buf[2]; /* P1 */
msg[msglen++] = apdu_buf[3]; /* P2 */
if (add_zero)
msg[msglen++] = 0;
else if (pininfo->fixedlen != 0)
{
msg[msglen++] = n;
memset (&msg[msglen], 0xff, n);
msglen += n;
}
/* An EDC is not required. */
set_msg_len (msg, msglen - 10);
rc = bulk_out (handle, msg, msglen, 0);
if (rc)
return rc;
msg = recv_buffer;
rc = bulk_in (handle, msg, sizeof recv_buffer, &msglen,
RDR_to_PC_DataBlock, seqno, 30000, 0);
if (rc)
return rc;
tpdu = msg + 10;
tpdulen = msglen - 10;
if (handle->apdu_level)
{
if (resp)
{
if (tpdulen > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)tpdulen, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, tpdu, tpdulen);
*nresp = tpdulen;
}
return 0;
}
if (tpdulen < 4)
{
my_npth_unprotect ();
libusb_clear_halt (handle->idev, handle->ep_bulk_in);
my_npth_protect ();
return CCID_DRIVER_ERR_ABORTED;
}
if (debug_level > 1)
DEBUGOUT_4 ("T=1: got %c-block seq=%d err=%d%s\n",
((msg[11] & 0xc0) == 0x80)? 'R' :
(msg[11] & 0x80)? 'S' : 'I',
((msg[11] & 0x80)? !!(msg[11]& 0x10) : !!(msg[11] & 0x40)),
((msg[11] & 0xc0) == 0x80)? (msg[11] & 0x0f) : 0,
(!(msg[11] & 0x80) && (msg[11] & 0x20)? " [more]":""));
if (!(tpdu[1] & 0x80))
{ /* This is an I-block. */
/* Last block sent was successful. */
handle->t1_ns ^= 1;
if (!!(tpdu[1] & 0x40) != handle->t1_nr)
{ /* Response does not match our sequence number. */
DEBUGOUT ("I-block with wrong seqno received\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
handle->t1_nr ^= 1;
p = tpdu + 3; /* Skip the prologue field. */
n = tpdulen - 3 - 1; /* Strip the epilogue field. */
/* fixme: verify the checksum. */
if (resp)
{
if (n > maxresplen)
{
DEBUGOUT_2 ("provided buffer too short for received data "
"(%u/%u)\n",
(unsigned int)n, (unsigned int)maxresplen);
return CCID_DRIVER_ERR_INV_VALUE;
}
memcpy (resp, p, n);
*nresp += n;
}
if (!(tpdu[1] & 0x20))
return 0; /* No chaining requested - ready. */
DEBUGOUT ("chaining requested but not supported for Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if ((tpdu[1] & 0xc0) == 0x80)
{ /* This is a R-block. */
if ( (tpdu[1] & 0x0f))
{ /* Error: repeat last block */
DEBUGOUT ("No retries supported for Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else if (!!(tpdu[1] & 0x10) == handle->t1_ns)
{ /* Response does not match our sequence number. */
DEBUGOUT ("R-block with wrong seqno received on more bit\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
else
{ /* Send next chunk. */
DEBUGOUT ("chaining not supported on Secure operation\n");
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
}
else
{ /* This is a S-block. */
DEBUGOUT_2 ("T=1: S-block %s received cmd=%d for Secure operation\n",
(tpdu[1] & 0x20)? "response": "request",
(tpdu[1] & 0x1f));
return CCID_DRIVER_ERR_CARD_IO_ERROR;
}
return 0;
}
#ifdef TEST
static void
print_error (int err)
{
const char *p;
char buf[50];
switch (err)
{
case 0: p = "success"; break;
case CCID_DRIVER_ERR_OUT_OF_CORE: p = "out of core"; break;
case CCID_DRIVER_ERR_INV_VALUE: p = "invalid value"; break;
case CCID_DRIVER_ERR_NO_DRIVER: p = "no driver"; break;
case CCID_DRIVER_ERR_NOT_SUPPORTED: p = "not supported"; break;
case CCID_DRIVER_ERR_LOCKING_FAILED: p = "locking failed"; break;
case CCID_DRIVER_ERR_BUSY: p = "busy"; break;
case CCID_DRIVER_ERR_NO_CARD: p = "no card"; break;
case CCID_DRIVER_ERR_CARD_INACTIVE: p = "card inactive"; break;
case CCID_DRIVER_ERR_CARD_IO_ERROR: p = "card I/O error"; break;
case CCID_DRIVER_ERR_GENERAL_ERROR: p = "general error"; break;
case CCID_DRIVER_ERR_NO_READER: p = "no reader"; break;
case CCID_DRIVER_ERR_ABORTED: p = "aborted"; break;
default: sprintf (buf, "0x%05x", err); p = buf; break;
}
fprintf (stderr, "operation failed: %s\n", p);
}
static void
print_data (const unsigned char *data, size_t length)
{
if (length >= 2)
{
fprintf (stderr, "operation status: %02X%02X\n",
data[length-2], data[length-1]);
length -= 2;
}
if (length)
{
fputs (" returned data:", stderr);
for (; length; length--, data++)
fprintf (stderr, " %02X", *data);
putc ('\n', stderr);
}
}
static void
print_result (int rc, const unsigned char *data, size_t length)
{
if (rc)
print_error (rc);
else if (data)
print_data (data, length);
}
int
main (int argc, char **argv)
{
gpg_error_t err;
ccid_driver_t ccid;
int slotstat;
unsigned char result[512];
size_t resultlen;
int no_pinpad = 0;
int verify_123456 = 0;
int did_verify = 0;
int no_poll = 0;
int idx_max;
struct ccid_dev_table *ccid_table;
if (argc)
{
argc--;
argv++;
}
while (argc)
{
if ( !strcmp (*argv, "--list"))
{
char *p;
p = ccid_get_reader_list ();
if (!p)
return 1;
fputs (p, stderr);
free (p);
return 0;
}
else if ( !strcmp (*argv, "--debug"))
{
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
argc--; argv++;
}
else if ( !strcmp (*argv, "--no-poll"))
{
no_poll = 1;
argc--; argv++;
}
else if ( !strcmp (*argv, "--no-pinpad"))
{
no_pinpad = 1;
argc--; argv++;
}
else if ( !strcmp (*argv, "--verify-123456"))
{
verify_123456 = 1;
argc--; argv++;
}
else
break;
}
err = ccid_dev_scan (&idx_max, &ccid_table);
if (err)
return 1;
if (idx_max == 0)
return 1;
err = ccid_open_reader (argc? *argv:NULL, 0, ccid_table, &ccid, NULL);
if (err)
return 1;
ccid_dev_scan_finish (ccid_table, idx_max);
if (!no_poll)
ccid_poll (ccid);
fputs ("getting ATR ...\n", stderr);
err = ccid_get_atr (ccid, NULL, 0, NULL);
if (err)
{
print_error (err);
return 1;
}
if (!no_poll)
ccid_poll (ccid);
fputs ("getting slot status ...\n", stderr);
err = ccid_slot_status (ccid, &slotstat, 1);
if (err)
{
print_error (err);
return 1;
}
if (!no_poll)
ccid_poll (ccid);
fputs ("selecting application OpenPGP ....\n", stderr);
{
static unsigned char apdu[] = {
0, 0xA4, 4, 0, 6, 0xD2, 0x76, 0x00, 0x01, 0x24, 0x01};
err = ccid_transceive (ccid,
apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
if (!no_poll)
ccid_poll (ccid);
fputs ("getting OpenPGP DO 0x65 ....\n", stderr);
{
static unsigned char apdu[] = { 0, 0xCA, 0, 0x65, 254 };
err = ccid_transceive (ccid, apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
if (!no_pinpad)
{
}
if (!no_pinpad)
{
static unsigned char apdu[] = { 0, 0x20, 0, 0x81 };
pininfo_t pininfo = { 0, 0, 0 };
if (ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
NULL, 0, NULL))
fputs ("can't verify using a PIN-Pad reader\n", stderr);
else
{
fputs ("verifying CHV1 using the PINPad ....\n", stderr);
err = ccid_transceive_secure (ccid, apdu, sizeof apdu, &pininfo,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
did_verify = 1;
}
}
if (verify_123456 && !did_verify)
{
fputs ("verifying that CHV1 is 123456....\n", stderr);
{
static unsigned char apdu[] = {0, 0x20, 0, 0x81,
6, '1','2','3','4','5','6'};
err = ccid_transceive (ccid, apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
}
if (!err)
{
fputs ("getting OpenPGP DO 0x5E ....\n", stderr);
{
static unsigned char apdu[] = { 0, 0xCA, 0, 0x5E, 254 };
err = ccid_transceive (ccid, apdu, sizeof apdu,
result, sizeof result, &resultlen);
print_result (err, result, resultlen);
}
}
ccid_close_reader (ccid);
return 0;
}
/*
* Disabled Local Variables:
* compile-command: "gcc -DTEST -DGPGRT_ENABLE_ES_MACROS -DHAVE_NPTH -DUSE_NPTH -Wall -I/usr/include/libusb-1.0 -I/usr/local/include -lusb-1.0 -g ccid-driver.c -lnpth -lgpg-error"
* End:
*/
#endif /*TEST*/
#endif /*HAVE_LIBUSB*/
diff --git a/scd/command.c b/scd/command.c
index a2274f15a..97e6accff 100644
--- a/scd/command.c
+++ b/scd/command.c
@@ -1,3047 +1,3047 @@
/* command.c - SCdaemon command handler
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2007, 2008, 2009, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <signal.h>
#ifdef USE_NPTH
# include <npth.h>
#endif
#include "scdaemon.h"
#include <assuan.h>
#include <ksba.h>
#include "iso7816.h"
#include "apdu.h" /* Required for apdu_*_reader (). */
#include "atr.h"
#ifdef HAVE_LIBUSB
#include "ccid-driver.h"
#endif
#include "../common/asshelp.h"
#include "../common/server-help.h"
#include "../common/ssh-utils.h"
/* Maximum length allowed as a PIN; used for INQUIRE NEEDPIN. That
* length needs to small compared to the maximum Assuan line length. */
#define MAXLEN_PIN 100
/* Maximum allowed size of key data as used in inquiries. */
#define MAXLEN_KEYDATA 4096
/* Maximum allowed total data size for SETDATA. */
#define MAXLEN_SETDATA 4096
/* Maximum allowed size of certificate data as used in inquiries. */
#define MAXLEN_CERTDATA 16384
/* Maximum allowed size for "SETATTR --inquire". */
#define MAXLEN_SETATTRDATA 16384
#define set_error(e,t) assuan_set_error (ctx, gpg_error (e), (t))
#define IS_LOCKED(c) (locked_session && locked_session != (c)->server_local)
/* Data used to associate an Assuan context with local server data.
This object describes the local properties of one session. */
struct server_local_s
{
/* We keep a list of all active sessions with the anchor at
SESSION_LIST (see below). This field is used for linking. */
struct server_local_s *next_session;
/* This object is usually assigned to a CTRL object (which is
globally visible). While enumerating all sessions we sometimes
need to access data of the CTRL object; thus we keep a
backpointer here. */
ctrl_t ctrl_backlink;
/* The Assuan context used by this session/server. */
assuan_context_t assuan_ctx;
#ifdef HAVE_W32_SYSTEM
void *event_signal; /* Or NULL if not used. */
#else
int event_signal; /* Or 0 if not used. */
#endif
/* True if the card has been removed and a reset is required to
continue operation. */
unsigned int card_removed:1;
/* If set to true we will be terminate ourself at the end of the
this session. */
unsigned int stopme:1;
/* If set to true, status change will be reported. */
unsigned int watching_status:1;
};
/* To keep track of all running sessions, we link all active server
contexts and the anchor in this variable. */
static struct server_local_s *session_list;
/* If a session has been locked we store a link to its server object
in this variable. */
static struct server_local_s *locked_session;
/* Local prototypes. */
static int command_has_option (const char *cmd, const char *cmdopt);
/* Convert the STRING into a newly allocated buffer while translating
the hex numbers. Stops at the first invalid character. Blanks and
colons are allowed to separate the hex digits. Returns NULL on
error or a newly malloced buffer and its length in LENGTH. */
static unsigned char *
hex_to_buffer (const char *string, size_t *r_length)
{
unsigned char *buffer;
const char *s;
size_t n;
buffer = xtrymalloc (strlen (string)+1);
if (!buffer)
return NULL;
for (s=string, n=0; *s; s++)
{
if (spacep (s) || *s == ':')
continue;
if (hexdigitp (s) && hexdigitp (s+1))
{
buffer[n++] = xtoi_2 (s);
s++;
}
else
break;
}
*r_length = n;
return buffer;
}
/* Reset the card and free the application context. With SEND_RESET
set to true actually send a RESET to the reader; this is the normal
way of calling the function. If KEEP_LOCK is set and the session
- is locked that lock wil not be released. */
+ is locked that lock will not be released. */
static void
do_reset (ctrl_t ctrl, int send_reset, int keep_lock)
{
card_t card = card_get (ctrl, NULL);
if (card)
{
if (!IS_LOCKED (ctrl) && send_reset)
card_reset (card);
else
{
ctrl->card_ctx = NULL;
ctrl->current_apptype = APPTYPE_NONE;
card_unref_locked (card);
}
card_put (card);
}
/* If we hold a lock, unlock now. */
if (!keep_lock && locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESET\n");
}
}
static gpg_error_t
reset_notify (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
do_reset (ctrl, 1, has_option (line, "--keep-lock"));
return 0;
}
static gpg_error_t
option_handler (assuan_context_t ctx, const char *key, const char *value)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
if (!strcmp (key, "event-signal"))
{
/* A value of 0 is allowed to reset the event signal. */
#ifdef HAVE_W32_SYSTEM
if (!*value)
return gpg_error (GPG_ERR_ASS_PARAMETER);
#ifdef _WIN64
ctrl->server_local->event_signal = (void *)strtoull (value, NULL, 16);
#else
ctrl->server_local->event_signal = (void *)strtoul (value, NULL, 16);
#endif
#else
int i = *value? atoi (value) : -1;
if (i < 0)
return gpg_error (GPG_ERR_ASS_PARAMETER);
ctrl->server_local->event_signal = i;
#endif
}
return 0;
}
/* If the card has not yet been opened, do it. */
static gpg_error_t
open_card (ctrl_t ctrl)
{
/* If we ever got a card not present error code, return that. Only
the SERIALNO command and a reset are able to clear from that
state. */
if (ctrl->server_local->card_removed)
return gpg_error (GPG_ERR_CARD_REMOVED);
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
if (ctrl->card_ctx)
return 0;
return select_application (ctrl, NULL, 0, NULL, 0);
}
/* Explicitly open a card for a specific use of APPTYPE or SERIALNO.
* If OPT_ALL is set also add all possible additional apps. */
static gpg_error_t
open_card_with_request (card_t *card_p, ctrl_t ctrl,
const char *apptypestr, const char *serialno,
int opt_all)
{
gpg_error_t err;
unsigned char *serialno_bin = NULL;
size_t serialno_bin_len = 0;
card_t card = card_get (ctrl, NULL);
if (serialno)
serialno_bin = hex_to_buffer (serialno, &serialno_bin_len);
/* If we are already initialized for one specific application we
need to check that the client didn't requested a specific
application different from the one in use before we continue. */
if (apptypestr && card)
{
err = check_application_conflict (card, apptypestr,
serialno_bin, serialno_bin_len);
if (gpg_err_code (err) == GPG_ERR_FALSE)
{
/* Different application but switching is supported. */
err = select_additional_application (card, ctrl, apptypestr);
}
if (err)
card_put (card);
goto leave;
}
/* Re-scan USB devices. Release CARD, before the scan. */
if (card)
{
ctrl->card_ctx = NULL;
ctrl->current_apptype = APPTYPE_NONE;
card_unref_locked (card);
card_put (card);
}
err = select_application (ctrl, apptypestr, 1,
serialno_bin, serialno_bin_len);
card = card_get (ctrl, NULL);
if (!err && opt_all)
{
if (card)
{
err = select_additional_application (card, ctrl, NULL);
if (err)
card_put (card);
}
}
leave:
if (!err)
*card_p = card;
xfree (serialno_bin);
return err;
}
static const char hlp_serialno[] =
"SERIALNO [--demand=<serialno>] [--all] [<apptype>]\n"
"\n"
"Return the serial number of the card using a status response. This\n"
"function should be used to check for the presence of a card.\n"
"\n"
"If --demand is given, an application on the card with SERIALNO is\n"
"selected and an error is returned if no such card available.\n"
"\n"
"If --all is given, all possible other applications of the card are\n"
"also selected to prepare for things like \"LEARN --force --multi\".\n"
"\n"
"If APPTYPE is given, an application of that type is selected and an\n"
"error is returned if the application is not supported or available.\n"
"The default is to auto-select the application using a hardwired\n"
"preference system.\n"
"\n"
"This function is special in that it can be used to reset the card.\n"
"Most other functions will return an error when a card change has\n"
"been detected and the use of this function is therefore required.\n"
"\n"
"Background: We want to keep the client clear of handling card\n"
"changes between operations; i.e. the client can assume that all\n"
"operations are done on the same card unless he calls this function.";
static gpg_error_t
cmd_serialno (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
struct server_local_s *sl;
gpg_error_t err = 0;
char *serial;
const char *demand;
int opt_all = has_option (line, "--all");
card_t card = NULL;
int thisslot;
if ( IS_LOCKED (ctrl) )
return gpg_error (GPG_ERR_LOCKED);
if ((demand = has_option_name (line, "--demand")))
{
if (*demand != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
line = (char *)++demand;
while (*line && !spacep (line))
line++;
if (*line)
*line++ = 0;
}
else
demand = NULL;
line = skip_options (line);
/* Clear the remove flag so that the open_card is able to reread it. */
ctrl->server_local->card_removed = 0;
err = open_card_with_request (&card, ctrl, *line? line:NULL, demand, opt_all);
/* Now clear or set the card_removed flag for all sessions using the
* current slot. In the error case make sure that the flag is set
* for the current session. */
thisslot = card? card->slot : -1;
for (sl=session_list; sl; sl = sl->next_session)
{
ctrl_t c = sl->ctrl_backlink;
if (c && c->card_ctx && c->card_ctx->slot == thisslot)
c->server_local->card_removed = err? 1 : 0;
}
if (err)
{
ctrl->server_local->card_removed = 1;
return err;
}
serial = card_get_serialno (card);
card_put (card);
if (!serial)
return gpg_error (GPG_ERR_INV_VALUE);
err = assuan_write_status (ctx, "SERIALNO", serial);
xfree (serial);
return err;
}
static const char hlp_switchcard[] =
"SWITCHCARD [<serialno>]\n"
"\n"
"Make the card with SERIALNO the current card.\n"
"The command \"getinfo card_list\" can be used to list\n"
"the serial numbers of inserted and known cards. Note\n"
"that the command \"SERIALNO\" can be used to refresh\n"
"the list of known cards. A simple SERIALNO status\n"
"is printed on success.";
static gpg_error_t
cmd_switchcard (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
unsigned char *sn_bin = NULL;
size_t sn_bin_len = 0;
if ((err = open_card (ctrl)))
return err;
line = skip_options (line);
if (*line)
{
sn_bin = hex_to_buffer (line, &sn_bin_len);
if (!sn_bin)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
/* Note that an SN_BIN of NULL will only print the status. */
err = app_switch_current_card (ctrl, sn_bin, sn_bin_len);
leave:
xfree (sn_bin);
return err;
}
static const char hlp_switchapp[] =
"SWITCHAPP [<appname>]\n"
"\n"
"Make APPNAME the active application for the current card.\n"
"Only some cards support switching between application; the\n"
"command \"getinfo active_app\" can be used to get a list of\n"
"applications which can be switched to. A SERIALNO status\n"
"including the active appname is printed on success.";
static gpg_error_t
cmd_switchapp (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
card_t card;
if ((err = open_card (ctrl)))
return err;
line = skip_options (line);
card = card_get (ctrl, NULL);
if (card)
{
err = app_switch_active_app (card, ctrl, line);
card_put (card);
}
else
err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
return err;
}
static const char hlp_learn[] =
"LEARN [--force] [--keypairinfo] [--reread] [--multi]\n"
"\n"
"Learn all useful information of the currently inserted card. When\n"
"used without the force options, the command might do an INQUIRE\n"
"like this:\n"
"\n"
" INQUIRE KNOWNCARDP <hexstring_with_serialNumber>\n"
"\n"
"The client should just send an \"END\" if the processing should go on\n"
"or a \"CANCEL\" to force the function to terminate with a Cancel\n"
"error message.\n"
"\n"
"With the option --keypairinfo only KEYPAIRINFO status lines are\n"
"returned. With the option --reread information from the card are\n"
"read again without the need for a reset (sone some cards).\n"
"\n"
"The response of this command is a list of status lines formatted as\n"
"this:\n"
"\n"
" S APPTYPE <apptype>\n"
"\n"
"This returns the type of the application, currently the strings:\n"
"\n"
" P15 = PKCS-15 structure used\n"
" DINSIG = DIN SIG\n"
" OPENPGP = OpenPGP card\n"
" PIV = PIV card\n"
" NKS = NetKey card\n"
"\n"
"are implemented. These strings are aliases for the AID. With option\n"
"--multi information for all switchable apps are returned.\n"
"\n"
" S KEYPAIRINFO <hexgrip> <keyref> [<usage>] [<keytime>] [<algo>]\n"
"\n"
"If there is no certificate yet stored on the card a single 'X' is\n"
"returned as the keygrip. For more info see doc/DETAILS. In addition\n"
"to the keypair info, information about all certificates stored on the\n"
"card is also returned:\n"
"\n"
" S CERTINFO <certtype> <keyref> [<label>]\n"
"\n"
"Where CERTTYPE is a number indicating the type of certificate:\n"
" 0 := Unknown\n"
" 100 := Regular X.509 cert\n"
" 101 := Trusted X.509 cert\n"
" 102 := Useful X.509 cert\n"
" 110 := Root CA cert in a special format (e.g. DINSIG)\n"
" 111 := Root CA cert as standard X509 cert.\n"
"\n"
"For certain cards, more information will be returned:\n"
"\n"
" S KEY-FPR <keyref> <hexstring>\n"
"\n"
"For some cards this returns the stored fingerprints of the\n"
"keys. This can be used check whether a key is available on the\n"
"card. KEYREF may be 1, 2 or 3 for OpenPGP or a standard keyref.\n"
"\n"
" S CA-FPR <no> <hexstring>\n"
"\n"
"Similar to above, these are the fingerprints of keys assumed to be\n"
"ultimately trusted.\n"
"\n"
" S DISP-NAME <name_of_card_holder>\n"
"\n"
"The name of the card holder as stored on the card; percent\n"
"escaping takes place, spaces are encoded as '+'\n"
"\n"
" S PUBKEY-URL <url>\n"
"\n"
"The URL to be used for locating the entire public key.\n"
" \n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_learn (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
int only_keypairinfo = has_option (line, "--keypairinfo");
int opt_multi = has_option (line, "--multi");
int opt_reread = has_option (line, "--reread");
int opt_force = has_option (line, "--force");
unsigned int flags;
card_t card;
const char *keygrip = NULL;
if ((rc = open_card (ctrl)))
return rc;
line = skip_options (line);
if (strlen (line) == 40)
keygrip = line;
card = card_get (ctrl, keygrip);
if (!card)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
/* Unless the force option is used we try a shortcut by identifying
the card using a serial number and inquiring the client with
that. The client may choose to cancel the operation if he already
knows about this card */
if (!only_keypairinfo)
{
const char *reader;
char *serial;
reader = apdu_get_reader_name (card->slot);
if (!reader)
{
card_put (card);
return out_of_core ();
}
send_status_direct (ctrl, "READER", reader);
/* No need to free the string of READER. */
serial = card_get_serialno (card);
if (!serial)
{
card_put (card);
return gpg_error (GPG_ERR_INV_VALUE);
}
rc = assuan_write_status (ctx, "SERIALNO", serial);
if (rc < 0)
{
xfree (serial);
card_put (card);
return out_of_core ();
}
if (!opt_force)
{
char *command;
rc = gpgrt_asprintf (&command, "KNOWNCARDP %s", serial);
if (rc < 0)
{
xfree (serial);
card_put (card);
return out_of_core ();
}
rc = assuan_inquire (ctx, command, NULL, NULL, 0);
xfree (command);
if (rc)
{
if (gpg_err_code (rc) != GPG_ERR_ASS_CANCELED)
log_error ("inquire KNOWNCARDP failed: %s\n",
gpg_strerror (rc));
xfree (serial);
card_put (card);
return rc;
}
/* Not canceled, so we have to proceed. */
}
xfree (serial);
}
/* Let the application print out its collection of useful status
information. */
flags = 0;
if (only_keypairinfo)
flags |= APP_LEARN_FLAG_KEYPAIRINFO;
if (opt_multi)
flags |= APP_LEARN_FLAG_MULTI;
if (opt_reread)
flags |= APP_LEARN_FLAG_REREAD;
if (!rc)
rc = app_write_learn_status (card, ctrl, flags);
card_put (card);
return rc;
}
static const char hlp_readcert[] =
"READCERT <hexified_certid>|<keyid>|<oid>\n"
"\n"
"Note, that this function may even be used on a locked card.";
static gpg_error_t
cmd_readcert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *cert;
size_t ncert;
card_t card;
const char *keygrip = NULL;
if ((rc = open_card (ctrl)))
return rc;
line = xtrystrdup (line); /* Need a copy of the line. */
if (!line)
return gpg_error_from_syserror ();
if (strlen (line) == 40)
keygrip = line;
card = card_get (ctrl, keygrip);
if (!card)
{
xfree (line);
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
}
rc = app_readcert (card, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
card_put (card);
xfree (line);
line = NULL;
if (!rc)
{
rc = assuan_send_data (ctx, cert, ncert);
xfree (cert);
if (rc)
return rc;
}
return rc;
}
static gpg_error_t
do_readkey (card_t card, ctrl_t ctrl, const char *line,
int opt_info, unsigned char **pk_p, size_t *pklen_p)
{
int rc;
int direct_readkey = 0;
/* If the application supports the READKEY function we use that.
Otherwise we use the old way by extracting it from the
certificate. */
rc = app_readkey (card, ctrl, line,
opt_info? APP_READKEY_FLAG_INFO : 0,
pk_p, pklen_p);
if (!rc)
direct_readkey = 1; /* Got the key. */
else if (gpg_err_code (rc) == GPG_ERR_UNSUPPORTED_OPERATION
|| gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
/* Fall back to certificate reading. */
unsigned char *cert = NULL;
size_t ncert;
rc = app_readcert (card, ctrl, line, &cert, &ncert);
if (rc)
log_error ("app_readcert failed: %s\n", gpg_strerror (rc));
else
{
rc = app_help_pubkey_from_cert (cert, ncert, pk_p, pklen_p);
xfree (cert);
if (rc)
log_error ("failed to parse the certificate: %s\n",
gpg_strerror (rc));
}
}
else
log_error ("app_readkey failed: %s\n", gpg_strerror (rc));
if (!rc && opt_info && !direct_readkey)
{
char keygripstr[KEYGRIP_LEN*2+1];
char *algostr;
rc = app_help_get_keygrip_string_pk (*pk_p, *pklen_p,
keygripstr, NULL, NULL,
&algostr);
if (rc)
{
log_error ("app_help_get_keygrip_string failed: %s\n",
gpg_strerror (rc));
return rc;
}
/* FIXME: Using LINE is not correct because it might be an
* OID and has not been canonicalized (i.e. uppercased). */
send_status_info (ctrl, "KEYPAIRINFO",
keygripstr, strlen (keygripstr),
line, strlen (line),
"-", (size_t)1,
"-", (size_t)1,
algostr, strlen (algostr),
NULL, (size_t)0);
xfree (algostr);
}
return rc;
}
static const char hlp_readkey[] =
"READKEY [--format=advanced|ssh] [--info[-only]] <keyid>|<oid>|<keygrip>\n"
"\n"
"Return the public key for the given cert or key ID as a standard\n"
"S-expression. With --format option, it may be returned in advanced\n"
"S-expression format, or SSH format. With --info a KEYPAIRINFO\n"
"status line is also emitted; with --info-only the regular output is\n"
"suppressed.";
static gpg_error_t
cmd_readkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
int advanced = 0;
int ssh = 0;
int opt_info = 0;
int opt_nokey = 0;
unsigned char *pk = NULL;
size_t pklen;
card_t card;
const char *keygrip = NULL;
if ((err = open_card (ctrl)))
return err;
if (has_option (line, "--advanced"))
advanced = 1;
if (has_option (line, "--format=advanced"))
advanced = 1;
if (has_option (line, "--format=ssh"))
ssh = 1;
if (has_option (line, "--info"))
opt_info = 1;
if (has_option (line, "--info-only"))
opt_info = opt_nokey = 1;
line = skip_options (line);
line = xtrystrdup (line); /* Need a copy of the line. */
if (!line)
return gpg_error_from_syserror ();
if (strlen (line) == 40)
keygrip = line;
card = card_get (ctrl, keygrip);
if (card)
{
err = do_readkey (card, ctrl, line, opt_info, &pk, &pklen);
card_put (card);
}
else
err = gpg_error (GPG_ERR_NO_SECKEY);
if (err)
goto leave;
if (opt_nokey)
;
else if (ssh)
{
estream_t stream = NULL;
gcry_sexp_t s_key;
void *buf = NULL;
size_t buflen;
stream = es_fopenmem (0, "r+b");
if (!stream)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_sexp_new (&s_key, pk, pklen, 0);
if (err)
{
es_fclose (stream);
goto leave;
}
err = ssh_public_key_in_base64 (s_key, stream, "(none)");
if (err)
{
gcry_sexp_release (s_key);
es_fclose (stream);
goto leave;
}
err = es_fclose_snatch (stream, &buf, &buflen);
gcry_sexp_release (s_key);
if (!err)
err = assuan_send_data (ctx, buf, buflen);
}
else if (advanced)
{
gcry_sexp_t s_key;
unsigned char *pkadv;
size_t pkadvlen;
err = gcry_sexp_new (&s_key, pk, pklen, 0);
if (err)
goto leave;
pkadvlen = gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, NULL, 0);
pkadv = xtrymalloc (pkadvlen);
if (!pkadv)
{
err = gpg_error_from_syserror ();
gcry_sexp_release (s_key);
goto leave;
}
log_assert (pkadvlen);
gcry_sexp_sprint (s_key, GCRYSEXP_FMT_ADVANCED, pkadv, pkadvlen);
gcry_sexp_release (s_key);
/* (One less to adjust for the trailing '\0') */
err = assuan_send_data (ctx, pkadv, pkadvlen-1);
xfree (pkadv);
}
else
err = assuan_send_data (ctx, pk, pklen);
leave:
xfree (pk);
xfree (line);
return err;
}
static const char hlp_setdata[] =
"SETDATA [--append] <hexstring>\n"
"\n"
"The client should use this command to tell us the data he want to sign.\n"
"With the option --append, the data is appended to the data set by a\n"
"previous SETDATA command.";
static gpg_error_t
cmd_setdata (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int append;
int n, i, off;
char *p;
unsigned char *buf;
append = (ctrl->in_data.value && has_option (line, "--append"));
line = skip_options (line);
if (locked_session && locked_session != ctrl->server_local)
return gpg_error (GPG_ERR_LOCKED);
/* Parse the hexstring. */
for (p=line,n=0; hexdigitp (p); p++, n++)
;
if (*p)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hexstring");
if (!n)
return set_error (GPG_ERR_ASS_PARAMETER, "no data given");
if ((n&1))
return set_error (GPG_ERR_ASS_PARAMETER, "odd number of digits");
n /= 2;
if (append)
{
if (ctrl->in_data.valuelen + n > MAXLEN_SETDATA)
return set_error (GPG_ERR_TOO_LARGE,
"limit on total size of data reached");
buf = xtrymalloc (ctrl->in_data.valuelen + n);
}
else
buf = xtrymalloc (n);
if (!buf)
return out_of_core ();
if (append)
{
memcpy (buf, ctrl->in_data.value, ctrl->in_data.valuelen);
off = ctrl->in_data.valuelen;
}
else
off = 0;
for (p=line, i=0; i < n; p += 2, i++)
buf[off+i] = xtoi_2 (p);
xfree (ctrl->in_data.value);
ctrl->in_data.value = buf;
ctrl->in_data.valuelen = off+n;
return 0;
}
static gpg_error_t
pin_cb (void *opaque, const char *info, char **retstr)
{
assuan_context_t ctx = opaque;
char *command;
int rc;
unsigned char *value;
size_t valuelen;
if (!retstr)
{
/* We prompt for pinpad entry. To make sure that the popup has
been show we use an inquire and not just a status message.
We ignore any value returned. */
if (info)
{
if (DBG_IPC)
log_debug ("prompting for pinpad entry '%s'\n", info);
rc = gpgrt_asprintf (&command, "POPUPPINPADPROMPT %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
xfree (command);
}
else
{
if (DBG_IPC)
log_debug ("dismiss pinpad entry prompt\n");
rc = assuan_inquire (ctx, "DISMISSPINPADPROMPT",
&value, &valuelen, MAXLEN_PIN);
}
if (!rc)
xfree (value);
return rc;
}
*retstr = NULL;
if (DBG_IPC)
log_debug ("asking for PIN '%s'\n", info);
rc = gpgrt_asprintf (&command, "NEEDPIN %s", info);
if (rc < 0)
return gpg_error (gpg_err_code_from_errno (errno));
/* Fixme: Write an inquire function which returns the result in
secure memory and check all further handling of the PIN. */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, command, &value, &valuelen, MAXLEN_PIN);
assuan_end_confidential (ctx);
xfree (command);
if (rc)
return rc;
if (!valuelen || value[valuelen-1])
{
/* We require that the returned value is an UTF-8 string */
xfree (value);
return gpg_error (GPG_ERR_INV_RESPONSE);
}
*retstr = (char*)value;
return 0;
}
static const char hlp_pksign[] =
"PKSIGN [--hash=[rmd160|sha{1,224,256,384,512}|md5|none]] <hexified_id>\n"
"\n"
"The --hash option is optional; the default is SHA1.";
static gpg_error_t
cmd_pksign (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
int hash_algo;
card_t card;
const char *keygrip = NULL;
if (has_option (line, "--hash=rmd160"))
hash_algo = GCRY_MD_RMD160;
else if (has_option (line, "--hash=sha1"))
hash_algo = GCRY_MD_SHA1;
else if (has_option (line, "--hash=sha224"))
hash_algo = GCRY_MD_SHA224;
else if (has_option (line, "--hash=sha256"))
hash_algo = GCRY_MD_SHA256;
else if (has_option (line, "--hash=sha384"))
hash_algo = GCRY_MD_SHA384;
else if (has_option (line, "--hash=sha512"))
hash_algo = GCRY_MD_SHA512;
else if (has_option (line, "--hash=md5"))
hash_algo = GCRY_MD_MD5;
else if (has_option (line, "--hash=none")) /* For raw RSA. */
hash_algo = 0;
else if (!strstr (line, "--"))
hash_algo = GCRY_MD_SHA1;
else
return set_error (GPG_ERR_ASS_PARAMETER, "invalid hash algorithm");
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
/* When it's a keygrip, we directly use the card, with no change of
ctrl->card_ctx. */
if (strlen (keyidstr) == 40)
keygrip = keyidstr;
card = card_get (ctrl, keygrip);
if (card)
{
rc = app_sign (card, ctrl,
keyidstr, hash_algo,
pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen);
card_put (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
xfree (keyidstr);
if (rc)
{
log_error ("app_sign failed: %s\n", gpg_strerror (rc));
}
else
{
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_pkauth[] =
"PKAUTH [--challenge-response] <hexified_id>";
static gpg_error_t
cmd_pkauth (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
card_t card;
const char *keygrip = NULL;
int challenge_response = 0;
if ((rc = open_card (ctrl)))
return rc;
if (has_option (line, "--challenge-response"))
challenge_response = 1;
line = skip_options (line);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid */
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
/* When it's a keygrip, we directly use CARD, with no change of
ctrl->card_ctx. */
if (strlen (keyidstr) == 40)
keygrip = keyidstr;
if (challenge_response)
{
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
card = card_get (ctrl, keygrip);
if (card)
{
rc = app_auth (card, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen);
card_put (card);
}
else
rc = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
xfree (keyidstr);
if (rc)
{
log_error ("app_auth failed: %s\n", gpg_strerror (rc));
}
else
{
if (!challenge_response)
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
}
return rc;
}
static const char hlp_pkdecrypt[] =
"PKDECRYPT <hexified_id>";
static gpg_error_t
cmd_pkdecrypt (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
unsigned char *outdata;
size_t outdatalen;
char *keyidstr;
unsigned int infoflags;
card_t card;
const char *keygrip = NULL;
if ((rc = open_card (ctrl)))
return rc;
keyidstr = xtrystrdup (line);
if (!keyidstr)
return out_of_core ();
/* When it's a keygrip, we directly use CARD, with no change of
ctrl->card_ctx. */
if (strlen (keyidstr) == 40)
keygrip = keyidstr;
card = card_get (ctrl, keygrip);
if (card)
{
rc = app_decipher (card, ctrl, keyidstr, pin_cb, ctx,
ctrl->in_data.value, ctrl->in_data.valuelen,
&outdata, &outdatalen, &infoflags);
card_put (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
xfree (keyidstr);
if (rc)
{
log_error ("app_decipher failed: %s\n", gpg_strerror (rc));
}
else
{
/* If the card driver told us that there is no padding, send a
status line. If there is a padding it is assumed that the
caller knows what padding is used. It would have been better
to always send that information but for backward
compatibility we can't do that. */
if ((infoflags & APP_DECIPHER_INFO_NOPAD))
send_status_direct (ctrl, "PADDING", "0");
rc = assuan_send_data (ctx, outdata, outdatalen);
xfree (outdata);
if (rc)
return rc; /* that is already an assuan error code */
}
return rc;
}
static const char hlp_getattr[] =
"GETATTR <name> [<keygrip>]\n"
"\n"
"This command is used to retrieve data from a smartcard. The\n"
"allowed names depend on the currently selected smartcard\n"
"application. NAME must be percent and '+' escaped. The value is\n"
"returned through status message, see the LEARN command for details.\n"
"\n"
"However, the current implementation assumes that Name is not escaped;\n"
"this works as long as no one uses arbitrary escaping. \n"
"\n"
"Note, that this function may even be used on a locked card.\n"
"When KEYGRIP is specified, it accesses directly with the KEYGRIP.";
static gpg_error_t
cmd_getattr (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
const char *keyword;
card_t card;
const char *keygrip = NULL;
if ((rc = open_card (ctrl)))
return rc;
keyword = line;
while (*line && !spacep (line))
line++;
if (*line)
*line++ = 0;
if (strlen (line) == 40)
keygrip = line;
card = card_get (ctrl, keygrip);
if (card)
{
/* FIXME: Applications should not return sensitive data if the card
is locked. */
rc = app_getattr (card, ctrl, keyword);
card_put (card);
}
else
rc = gpg_error (GPG_ERR_NO_SECKEY);
return rc;
}
static const char hlp_setattr[] =
"SETATTR [--inquire] <name> <value> \n"
"\n"
"This command is used to store data on a smartcard. The allowed\n"
"names and values are depend on the currently selected smartcard\n"
"application. NAME and VALUE must be percent and '+' escaped.\n"
"\n"
"However, the current implementation assumes that NAME is not\n"
"escaped; this works as long as no one uses arbitrary escaping.\n"
"\n"
"If the option --inquire is used, VALUE shall not be given; instead\n"
"an inquiry using the keyword \"VALUE\" is used to retrieve it. The\n"
"value is in this case considered to be confidential and not logged.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"setattr function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_setattr (assuan_context_t ctx, char *orig_line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *keyword;
int keywordlen;
size_t nbytes;
char *line, *linebuf;
int opt_inquire;
card_t card;
opt_inquire = has_option (orig_line, "--inquire");
orig_line = skip_options (orig_line);
if ((err = open_card (ctrl)))
return err;
/* We need to use a copy of LINE, because PIN_CB uses the same
context and thus reuses the Assuan provided LINE. */
line = linebuf = xtrystrdup (orig_line);
if (!line)
return out_of_core ();
card = card_get (ctrl, NULL);
if (!card)
{
xfree (linebuf);
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
}
keyword = line;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
if (*line)
*line++ = 0;
while (spacep (line))
line++;
if (opt_inquire)
{
unsigned char *value;
assuan_begin_confidential (ctx);
err = assuan_inquire (ctx, "VALUE", &value, &nbytes, MAXLEN_SETATTRDATA);
assuan_end_confidential (ctx);
if (!err)
{
err = app_setattr (card, ctrl, keyword, pin_cb, ctx,
value, nbytes);
wipememory (value, nbytes);
xfree (value);
}
}
else
{
nbytes = percent_plus_unescape_inplace (line, 0);
err = app_setattr (card, ctrl, keyword, pin_cb, ctx,
(const unsigned char*)line, nbytes);
}
card_put (card);
xfree (linebuf);
return err;
}
static const char hlp_writecert[] =
"WRITECERT <hexified_certid>\n"
"\n"
"This command is used to store a certificate on a smartcard. The\n"
"allowed certids depend on the currently selected smartcard\n"
- "application. The actual certifciate is requested using the inquiry\n"
+ "application. The actual certificate is requested using the inquiry\n"
"\"CERTDATA\" and needs to be provided in its raw (e.g. DER) form.\n"
"\n"
"In almost all cases a PIN will be requested. See the related\n"
"writecert function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writecert (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *certid;
unsigned char *certdata;
size_t certdatalen;
card_t card;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no certid given");
certid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
card = card_get (ctrl, NULL);
if (!card)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
certid = xtrystrdup (certid);
if (!certid)
{
card_put (card);
return out_of_core ();
}
/* Now get the actual keydata. */
rc = assuan_inquire (ctx, "CERTDATA",
&certdata, &certdatalen, MAXLEN_CERTDATA);
if (rc)
{
card_put (card);
xfree (certid);
return rc;
}
/* Write the certificate to the card. */
rc = app_writecert (card, ctrl, certid,
pin_cb, ctx, certdata, certdatalen);
card_put (card);
xfree (certid);
xfree (certdata);
return rc;
}
static const char hlp_writekey[] =
"WRITEKEY [--force] <keyid> \n"
"\n"
"This command is used to store a secret key on a smartcard. The\n"
"allowed keyids depend on the currently selected smartcard\n"
"application. The actual keydata is requested using the inquiry\n"
"\"KEYDATA\" and need to be provided without any protection. With\n"
"--force set an existing key under this KEYID will get overwritten.\n"
"The keydata is expected to be the usual canonical encoded\n"
"S-expression.\n"
"\n"
"A PIN will be requested for most NAMEs. See the corresponding\n"
"writekey function of the actually used application (app-*.c) for\n"
"details.";
static gpg_error_t
cmd_writekey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *keyid;
int force = has_option (line, "--force");
unsigned char *keydata;
size_t keydatalen;
card_t card;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no keyid given");
keyid = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((rc = open_card (ctrl)))
return rc;
card = card_get (ctrl, NULL);
if (!card)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
keyid = xtrystrdup (keyid);
if (!keyid)
{
card_put (card);
return out_of_core ();
}
/* Now get the actual keydata. */
assuan_begin_confidential (ctx);
rc = assuan_inquire (ctx, "KEYDATA", &keydata, &keydatalen, MAXLEN_KEYDATA);
assuan_end_confidential (ctx);
if (rc)
{
xfree (keyid);
return rc;
}
/* Write the key to the card. */
rc = app_writekey (card, ctrl, keyid, force? 1:0,
pin_cb, ctx, keydata, keydatalen);
card_put (card);
xfree (keyid);
xfree (keydata);
return rc;
}
static const char hlp_genkey[] =
"GENKEY [--force] [--timestamp=<isodate>] [--algo=ALGO] <keyref>\n"
"\n"
"Generate a key on-card identified by <keyref>, which is application\n"
"specific. Return values are also application specific. For OpenPGP\n"
"cards 3 status lines are returned:\n"
"\n"
" S KEY-FPR <hexstring>\n"
" S KEY-CREATED-AT <seconds_since_epoch>\n"
" S KEY-DATA [-|p|n] <hexdata>\n"
"\n"
" 'p' and 'n' are the names of the RSA parameters; '-' is used to\n"
" indicate that HEXDATA is the first chunk of a parameter given\n"
" by the next KEY-DATA. Only used by GnuPG version < 2.1.\n"
"\n"
"--force is required to overwrite an already existing key. The\n"
"KEY-CREATED-AT is required for further processing because it is\n"
"part of the hashed key material for the fingerprint.\n"
"\n"
"If --timestamp is given an OpenPGP key will be created using this\n"
"value. The value needs to be in ISO Format; e.g.\n"
"\"--timestamp=20030316T120000\" and after 1970-01-01 00:00:00.\n"
"\n"
"The option --algo can be used to request creation using a specific\n"
"algorithm. The possible algorithms are card dependent.\n"
"\n"
"The public part of the key can also later be retrieved using the\n"
"READKEY command.";
static gpg_error_t
cmd_genkey (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *keyref_buffer = NULL;
char *keyref;
int force;
const char *s;
char *opt_algo = NULL;
time_t timestamp;
card_t card;
force = has_option (line, "--force");
if ((s=has_option_name (line, "--timestamp")))
{
if (*s != '=')
return set_error (GPG_ERR_ASS_PARAMETER, "missing value for option");
timestamp = isotime2epoch (s+1);
if (timestamp < 1)
return set_error (GPG_ERR_ASS_PARAMETER, "invalid time value");
}
else
timestamp = 0;
err = get_option_value (line, "--algo", &opt_algo);
if (err)
goto leave;
line = skip_options (line);
if (!*line)
{
err = set_error (GPG_ERR_ASS_PARAMETER, "no key number given");
goto leave;
}
keyref = line;
while (*line && !spacep (line))
line++;
*line = 0;
if ((err = open_card (ctrl)))
goto leave;
card = card_get (ctrl, NULL);
if (!card)
{
err = gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
goto leave;
}
keyref = keyref_buffer = xtrystrdup (keyref);
if (!keyref)
{
err = gpg_error_from_syserror ();
card_put (card);
goto leave;
}
err = app_genkey (card, ctrl, keyref, opt_algo,
force? APP_GENKEY_FLAG_FORCE : 0,
timestamp, pin_cb, ctx);
card_put (card);
leave:
xfree (keyref_buffer);
xfree (opt_algo);
return err;
}
static const char hlp_random[] =
"RANDOM <nbytes>\n"
"\n"
"Get NBYTES of random from the card and send them back as data.\n"
"This usually involves EEPROM write on the card and thus excessive\n"
"use of this command may destroy the card.\n"
"\n"
"Note, that this function may be even be used on a locked card.";
static gpg_error_t
cmd_random (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
size_t nbytes;
unsigned char *buffer;
card_t card;
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER,
"number of requested bytes missing");
nbytes = strtoul (line, NULL, 0);
if ((rc = open_card (ctrl)))
return rc;
card = card_get (ctrl, NULL);
if (!card)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
buffer = xtrymalloc (nbytes);
if (!buffer)
{
card_put (card);
return out_of_core ();
}
rc = app_get_challenge (card, ctrl, nbytes, buffer);
card_put (card);
if (!rc)
{
rc = assuan_send_data (ctx, buffer, nbytes);
xfree (buffer);
return rc; /* that is already an assuan error code */
}
xfree (buffer);
return rc;
}
static const char hlp_passwd[] =
"PASSWD [--reset] [--nullpin] [--clear] <chvno> [<keygrip>]\n"
"\n"
"Change the PIN or, if --reset is given, reset the retry counter of\n"
"the card holder verification vector CHVNO. The option --nullpin is\n"
"used for TCOS cards to set the initial PIN. The option --clear clears\n"
"the security status associated with the PIN so that the PIN needs to\n"
"be presented again. The format of CHVNO depends on the card application.\n"
"\n"
"The target card is the currently selected smartcard, when KEYPGIP is not\n"
"specified. When it is specified, it accesses directly with the KEYGRIP.";
static gpg_error_t
cmd_passwd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc;
char *chvnostr;
unsigned int flags = 0;
card_t card;
const char *keygrip = NULL;
if (has_option (line, "--reset"))
flags |= APP_CHANGE_FLAG_RESET;
if (has_option (line, "--nullpin"))
flags |= APP_CHANGE_FLAG_NULLPIN;
if (has_option (line, "--clear"))
flags |= APP_CHANGE_FLAG_CLEAR;
line = skip_options (line);
if (!*line)
return set_error (GPG_ERR_ASS_PARAMETER, "no CHV number given");
chvnostr = line;
while (*line && !spacep (line))
line++;
if (*line)
*line++ = 0;
if (strlen (line) == 40)
keygrip = line;
/* Do not allow other flags aside of --clear. */
if ((flags & APP_CHANGE_FLAG_CLEAR) && (flags & ~APP_CHANGE_FLAG_CLEAR))
return set_error (GPG_ERR_UNSUPPORTED_OPERATION,
"--clear used with other options");
if ((rc = open_card (ctrl)))
return rc;
card = card_get (ctrl, keygrip);
if (!card)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
chvnostr = xtrystrdup (chvnostr);
if (!chvnostr)
{
card_put (card);
return out_of_core ();
}
rc = app_change_pin (card, ctrl, chvnostr, flags, pin_cb, ctx);
card_put (card);
if (rc)
log_error ("command passwd failed: %s\n", gpg_strerror (rc));
xfree (chvnostr);
return rc;
}
static const char hlp_checkpin[] =
"CHECKPIN <idstr>\n"
"\n"
"Perform a VERIFY operation without doing anything else. This may\n"
"be used to initialize a the PIN cache earlier to long lasting\n"
"operations. Its use is highly application dependent.\n"
"\n"
"For OpenPGP:\n"
"\n"
" Perform a simple verify operation for CHV1 and CHV2, so that\n"
" further operations won't ask for CHV2 and it is possible to do a\n"
" cheap check on the PIN: If there is something wrong with the PIN\n"
" entry system, only the regular CHV will get blocked and not the\n"
" dangerous CHV3. IDSTR is the usual card's serial number in hex\n"
" notation; an optional fingerprint part will get ignored. There\n"
" is however a special mode if the IDSTR is suffixed with the\n"
" literal string \"[CHV3]\": In this case the Admin PIN is checked\n"
" if and only if the retry counter is still at 3.\n"
"\n"
"For Netkey:\n"
"\n"
" Any of the valid PIN Ids may be used. These are the strings:\n"
"\n"
" PW1.CH - Global password 1\n"
" PW2.CH - Global password 2\n"
" PW1.CH.SIG - SigG password 1\n"
" PW2.CH.SIG - SigG password 2\n"
"\n"
" For a definitive list, see the implementation in app-nks.c.\n"
" Note that we call a PW2.* PIN a \"PUK\" despite that since TCOS\n"
" 3.0 they are technically alternative PINs used to mutally\n"
" unblock each other.\n"
"\n"
"For PKCS#15:\n"
"\n"
" The key's ID string or the PIN's label may be used.";
static gpg_error_t
cmd_checkpin (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err;
char *idstr;
card_t card;
if ((err = open_card (ctrl)))
return err;
card = card_get (ctrl, NULL);
if (!card)
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
/* We have to use a copy of the key ID because the function may use
the pin_cb which in turn uses the assuan line buffer and thus
overwriting the original line with the keyid. */
idstr = xtrystrdup (line);
if (!idstr)
{
card_put (card);
return out_of_core ();
}
err = app_check_pin (card, ctrl, idstr, pin_cb, ctx);
card_put (card);
xfree (idstr);
if (err)
log_error ("app_check_pin failed: %s\n", gpg_strerror (err));
return err;
}
static const char hlp_lock[] =
"LOCK [--wait]\n"
"\n"
"Grant exclusive card access to this session. Note that there is\n"
"no lock counter used and a second lock from the same session will\n"
"be ignored. A single unlock (or RESET) unlocks the session.\n"
"Return GPG_ERR_LOCKED if another session has locked the reader.\n"
"\n"
"If the option --wait is given the command will wait until a\n"
"lock has been released.";
static gpg_error_t
cmd_lock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
retry:
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
}
else
locked_session = ctrl->server_local;
#ifdef USE_NPTH
if (rc && has_option (line, "--wait"))
{
rc = 0;
gnupg_sleep (1); /* Better implement an event mechanism. However,
for card operations this should be
sufficient. */
/* Send a progress so that we can detect a connection loss. */
rc = send_status_printf (ctrl, "PROGRESS", "scd_locked . 0 0");
if (!rc)
goto retry;
}
#endif /*USE_NPTH*/
if (rc)
log_error ("cmd_lock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_unlock[] =
"UNLOCK\n"
"\n"
"Release exclusive card access.";
static gpg_error_t
cmd_unlock (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
int rc = 0;
(void)line;
if (locked_session)
{
if (locked_session != ctrl->server_local)
rc = gpg_error (GPG_ERR_LOCKED);
else
locked_session = NULL;
}
else
rc = gpg_error (GPG_ERR_NOT_LOCKED);
if (rc)
log_error ("cmd_unlock failed: %s\n", gpg_strerror (rc));
return rc;
}
static const char hlp_getinfo[] =
"GETINFO <what>\n"
"\n"
"Multi purpose command to return certain information. \n"
"Supported values of WHAT are:\n"
"\n"
" version - Return the version of the program.\n"
" pid - Return the process id of the server.\n"
" socket_name - Return the name of the socket.\n"
" connections - Return number of active connections.\n"
" status - Return the status of the current reader (in the future,\n"
" may also return the status of all readers). The status\n"
" is a list of one-character flags. The following flags\n"
" are currently defined:\n"
" 'u' Usable card present.\n"
" 'r' Card removed. A reset is necessary.\n"
" These flags are exclusive.\n"
" reader_list - Return a list of detected card readers. Does\n"
" currently only work with the internal CCID driver.\n"
" deny_admin - Returns OK if admin commands are not allowed or\n"
" GPG_ERR_GENERAL if admin commands are allowed.\n"
" app_list - Return a list of supported applications. One\n"
" application per line, fields delimited by colons,\n"
" first field is the name.\n"
" card_list - Return a list of serial numbers of all inserted cards.\n"
" active_apps - Return a list of active apps on the current card.\n"
" all_active_apps\n"
" - Return a list of active apps on all inserted cards.\n"
" cmd_has_option CMD OPT\n"
" - Returns OK if command CMD has option OPT.\n"
" apdu_strerror NUMBER\n"
" - Return a string for a status word.\n";
static gpg_error_t
cmd_getinfo (assuan_context_t ctx, char *line)
{
int rc = 0;
const char *s;
if (!strcmp (line, "version"))
{
s = VERSION;
rc = assuan_send_data (ctx, s, strlen (s));
}
else if (!strcmp (line, "pid"))
{
char numbuf[50];
snprintf (numbuf, sizeof numbuf, "%lu", (unsigned long)getpid ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strncmp (line, "cmd_has_option", 14)
&& (line[14] == ' ' || line[14] == '\t' || !line[14]))
{
char *cmd, *cmdopt;
line += 14;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmd = line;
while (*line && (*line != ' ' && *line != '\t'))
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
*line++ = 0;
while (*line == ' ' || *line == '\t')
line++;
if (!*line)
rc = gpg_error (GPG_ERR_MISSING_VALUE);
else
{
cmdopt = line;
if (!command_has_option (cmd, cmdopt))
rc = gpg_error (GPG_ERR_FALSE);
}
}
}
}
else if (!strcmp (line, "socket_name"))
{
s = scd_get_socket_name ();
if (s)
rc = assuan_send_data (ctx, s, strlen (s));
else
rc = gpg_error (GPG_ERR_NO_DATA);
}
else if (!strcmp (line, "connections"))
{
char numbuf[20];
snprintf (numbuf, sizeof numbuf, "%d", get_active_connection_count ());
rc = assuan_send_data (ctx, numbuf, strlen (numbuf));
}
else if (!strcmp (line, "status"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
char flag;
if (open_card (ctrl))
flag = 'r';
else
flag = 'u';
rc = assuan_send_data (ctx, &flag, 1);
}
else if (!strcmp (line, "reader_list"))
{
#ifdef HAVE_LIBUSB
char *p = ccid_get_reader_list ();
#else
char *p = NULL;
#endif
if (p)
rc = assuan_send_data (ctx, p, strlen (p));
else
rc = gpg_error (GPG_ERR_NO_DATA);
xfree (p);
}
else if (!strcmp (line, "deny_admin"))
rc = opt.allow_admin? gpg_error (GPG_ERR_GENERAL) : 0;
else if (!strcmp (line, "app_list"))
{
char *p = get_supported_applications ();
if (p)
rc = assuan_send_data (ctx, p, strlen (p));
else
rc = 0;
xfree (p);
}
else if (!strcmp (line, "card_list"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = app_send_card_list (ctrl);
}
else if (!strcmp (line, "active_apps"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card = card_get (ctrl, NULL);
if (!card)
rc = 0; /* No current card - no active apps. */
else
{
rc = app_send_active_apps (card, ctrl);
card_put (card);
}
}
else if (!strcmp (line, "all_active_apps"))
{
ctrl_t ctrl = assuan_get_pointer (ctx);
rc = app_send_active_apps (NULL, ctrl);
}
else if ((s=has_leading_keyword (line, "apdu_strerror")))
{
unsigned long ul = strtoul (s, NULL, 0);
s = apdu_strerror (ul);
rc = assuan_send_data (ctx, s, strlen (s));
}
else
rc = set_error (GPG_ERR_ASS_PARAMETER, "unknown value for WHAT");
return rc;
}
static const char hlp_restart[] =
"RESTART\n"
"\n"
"Restart the current connection; this is a kind of warm reset. It\n"
"deletes the context used by this connection but does not send a\n"
"RESET to the card. Thus the card itself won't get reset. \n"
"\n"
"This is used by gpg-agent to reuse a primary pipe connection and\n"
"may be used by clients to backup from a conflict in the serial\n"
"command; i.e. to select another application.";
static gpg_error_t
cmd_restart (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card = card_get (ctrl, NULL);
(void)line;
if (card)
{
ctrl->card_ctx = NULL;
ctrl->current_apptype = APPTYPE_NONE;
card_unref_locked (card);
card_put (card);
}
if (locked_session && ctrl->server_local == locked_session)
{
locked_session = NULL;
log_info ("implicitly unlocking due to RESTART\n");
}
return 0;
}
static const char hlp_disconnect[] =
"DISCONNECT\n"
"\n"
"Disconnect the card if the backend supports a disconnect operation.";
static gpg_error_t
cmd_disconnect (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card;
(void)line;
card = card_get (ctrl, NULL);
if (card)
{
apdu_disconnect (card->slot);
card_put (card);
return 0;
}
else
return gpg_error (GPG_ERR_UNSUPPORTED_OPERATION);
}
static const char hlp_apdu[] =
"APDU [--[dump-]atr] [--more] [--exlen[=N]] [hexstring]\n"
"\n"
"Send an APDU to the current reader. This command bypasses the high\n"
"level functions and sends the data directly to the card. HEXSTRING\n"
"is expected to be a proper APDU. If HEXSTRING is not given no\n"
"commands are set to the card but the command will implicitly check\n"
"whether the card is ready for use. \n"
"\n"
"Using the option \"--atr\" returns the ATR of the card as a status\n"
"message before any data like this:\n"
" S CARD-ATR 3BFA1300FF813180450031C173C00100009000B1\n"
"\n"
"Using the option --more handles the card status word MORE_DATA\n"
"(61xx) and concatenates all responses to one block.\n"
"\n"
"Using the option \"--exlen\" the returned APDU may use extended\n"
"length up to N bytes. If N is not given a default value is used\n"
"(currently 4096).";
static gpg_error_t
cmd_apdu (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card;
int rc;
unsigned char *apdu;
size_t apdulen;
int with_atr;
int handle_more;
const char *s;
size_t exlen;
if (has_option (line, "--dump-atr"))
with_atr = 3;
else if (has_option (line, "--data-atr"))
with_atr = 2;
else
with_atr = has_option (line, "--atr");
handle_more = has_option (line, "--more");
if ((s=has_option_name (line, "--exlen")))
{
if (*s == '=')
exlen = strtoul (s+1, NULL, 0);
else
exlen = 4096;
}
else
exlen = 0;
line = skip_options (line);
if ((rc = open_card (ctrl)))
return rc;
card = card_get (ctrl, NULL);
if (!card)
return gpg_error (GPG_ERR_CARD_NOT_PRESENT);
if (with_atr)
{
unsigned char *atr;
size_t atrlen;
char hexbuf[400];
atr = apdu_get_atr (card->slot, &atrlen);
if (!atr || atrlen > sizeof hexbuf - 2 )
{
rc = gpg_error (GPG_ERR_INV_CARD);
goto leave;
}
if (with_atr == 3)
{
char *string, *p, *pend;
string = atr_dump (atr, atrlen);
if (string)
{
for (rc=0, p=string; !rc && (pend = strchr (p, '\n')); p = pend+1)
{
rc = assuan_send_data (ctx, p, pend - p + 1);
if (!rc)
rc = assuan_send_data (ctx, NULL, 0);
}
if (!rc && *p)
rc = assuan_send_data (ctx, p, strlen (p));
es_free (string);
if (rc)
{
xfree (atr);
goto leave;
}
}
}
else if (with_atr == 2)
{
rc = assuan_send_data (ctx, atr, atrlen);
if (rc)
{
xfree (atr);
goto leave;
}
}
else
{
bin2hex (atr, atrlen, hexbuf);
send_status_info (ctrl, "CARD-ATR", hexbuf, strlen (hexbuf), NULL, 0);
}
xfree (atr);
}
apdu = hex_to_buffer (line, &apdulen);
if (!apdu)
{
rc = gpg_error_from_syserror ();
goto leave;
}
if (apdulen)
{
unsigned char *result = NULL;
size_t resultlen;
card->maybe_check_aid = 1;
rc = apdu_send_direct (card->slot, exlen,
apdu, apdulen, handle_more,
NULL, &result, &resultlen);
if (rc)
{
log_error ("apdu_send_direct failed: %s\n", apdu_strerror (rc));
rc = iso7816_map_sw (rc);
}
else
{
rc = assuan_send_data (ctx, result, resultlen);
xfree (result);
}
}
xfree (apdu);
leave:
card_put (card);
return rc;
}
static const char hlp_killscd[] =
"KILLSCD\n"
"\n"
"Commit suicide.";
static gpg_error_t
cmd_killscd (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
(void)line;
ctrl->server_local->stopme = 1;
assuan_set_flag (ctx, ASSUAN_FORCE_CLOSE, 1);
return 0;
}
static const char hlp_keyinfo[] =
"KEYINFO [--list[=auth|encr|sign]] [--data] <keygrip>\n"
"\n"
"Return information about the key specified by the KEYGRIP. If the\n"
"key is not available GPG_ERR_NOT_FOUND is returned. If the option\n"
"--list is given the keygrip is ignored and information about all\n"
"available keys are returned. Capability may limit the listing.\n"
"Unless --data is given, the\n"
"information is returned as a status line using the format:\n"
"\n"
" KEYINFO <keygrip> T <serialno> <idstr> <usage>\n"
"\n"
"KEYGRIP is the keygrip.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"IDSTR is a string used to distinguish keys on a smartcard. If it\n"
" is not known a dash is used instead.\n"
"\n"
"USAGE is a string of capabilities of the key, 's' for sign, \n"
"'e' for encryption, 'a' for auth, and 'c' for cert. If it is not\n"
"known a dash is used instead.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
cmd_keyinfo (assuan_context_t ctx, char *line)
{
int cap;
int opt_data;
int action;
char *keygrip_str;
ctrl_t ctrl = assuan_get_pointer (ctx);
card_t card;
opt_data = has_option (line, "--data");
cap = 0;
keygrip_str = NULL;
if (has_option (line, "--list"))
cap = 0;
else if (has_option (line, "--list=sign"))
cap = GCRY_PK_USAGE_SIGN;
else if (has_option (line, "--list=encr"))
cap = GCRY_PK_USAGE_ENCR;
else if (has_option (line, "--list=auth"))
cap = GCRY_PK_USAGE_AUTH;
else
keygrip_str = skip_options (line);
if (opt_data)
action = KEYGRIP_ACTION_SEND_DATA;
else
action = KEYGRIP_ACTION_WRITE_STATUS;
card = app_do_with_keygrip (ctrl, action, keygrip_str, cap);
if (keygrip_str && !card)
return gpg_error (GPG_ERR_NOT_FOUND);
return 0;
}
/* Send a keyinfo string as used by the KEYGRIP_ACTION_SEND_DATA. If
* DATA is true the string is emitted as a data line, else as a status
* line. */
void
send_keyinfo (ctrl_t ctrl, int data, const char *keygrip_str,
const char *serialno, const char *idstr, const char *usage)
{
char *string;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
string = xtryasprintf ("%s T %s %s %s%s", keygrip_str,
serialno? serialno : "-",
idstr? idstr : "-",
usage? usage : "-",
data? "\n" : "");
if (!string)
return;
if (!data)
assuan_write_status (ctx, "KEYINFO", string);
else
assuan_send_data (ctx, string, strlen (string));
xfree (string);
return;
}
static const char hlp_devinfo[] =
"DEVINFO [--watch]\n"
"\n"
"Return information about devices. If the option --watch is given,\n"
"it keeps reporting status change until it detects no device is\n"
"available.\n"
"The information is returned as a status line using the format:\n"
"\n"
" DEVICE <card_type> <serialno> <app_type>\n"
"\n"
"CARD_TYPE is the type of the card.\n"
"\n"
"SERIALNO is an ASCII string with the serial number of the\n"
" smartcard. If the serial number is not known a single\n"
" dash '-' is used instead.\n"
"\n"
"APP_TYPE is the type of the application.\n"
"\n"
"More information may be added in the future.";
static gpg_error_t
cmd_devinfo (assuan_context_t ctx, char *line)
{
ctrl_t ctrl = assuan_get_pointer (ctx);
gpg_error_t err = 0;
int watch = 0;
card_t card;
if (has_option (line, "--watch"))
{
watch = 1;
ctrl->server_local->watching_status = 1;
}
/* Firstly, send information of available devices. */
err = app_send_devinfo (ctrl, 0);
/* If not watching, that's all. */
if (!watch)
return err;
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
return err;
/* Secondly, try to open device(s) available. */
/* Clear the remove flag so that the open_card is able to reread it. */
if (ctrl->server_local->card_removed)
ctrl->server_local->card_removed = 0;
if ((err = open_card (ctrl))
&& gpg_err_code (err) != GPG_ERR_ENODEV)
return err;
err = 0;
card = card_get (ctrl, NULL);
if (card)
{
/* If any, remove reference to the card in CTRL. */
ctrl->card_ctx = NULL;
ctrl->current_apptype = APPTYPE_NONE;
card_unref_locked (card);
card_put (card);
}
/* Then, keep watching the status change. */
err = app_send_devinfo (ctrl, 1);
ctrl->server_local->watching_status = 0;
return err;
}
/* Return true if the command CMD implements the option OPT. */
static int
command_has_option (const char *cmd, const char *cmdopt)
{
if (!strcmp (cmd, "SERIALNO"))
{
if (!strcmp (cmdopt, "all"))
return 1;
}
return 0;
}
/* Tell the assuan library about our commands */
static int
register_commands (assuan_context_t ctx)
{
static struct {
const char *name;
assuan_handler_t handler;
const char * const help;
} table[] = {
{ "SERIALNO", cmd_serialno, hlp_serialno },
{ "SWITCHCARD", cmd_switchcard,hlp_switchcard },
{ "SWITCHAPP", cmd_switchapp,hlp_switchapp },
{ "LEARN", cmd_learn, hlp_learn },
{ "READCERT", cmd_readcert, hlp_readcert },
{ "READKEY", cmd_readkey, hlp_readkey },
{ "SETDATA", cmd_setdata, hlp_setdata },
{ "PKSIGN", cmd_pksign, hlp_pksign },
{ "PKAUTH", cmd_pkauth, hlp_pkauth },
{ "PKDECRYPT", cmd_pkdecrypt,hlp_pkdecrypt },
{ "INPUT", NULL },
{ "OUTPUT", NULL },
{ "GETATTR", cmd_getattr, hlp_getattr },
{ "SETATTR", cmd_setattr, hlp_setattr },
{ "WRITECERT", cmd_writecert,hlp_writecert },
{ "WRITEKEY", cmd_writekey, hlp_writekey },
{ "GENKEY", cmd_genkey, hlp_genkey },
{ "RANDOM", cmd_random, hlp_random },
{ "PASSWD", cmd_passwd, hlp_passwd },
{ "CHECKPIN", cmd_checkpin, hlp_checkpin },
{ "LOCK", cmd_lock, hlp_lock },
{ "UNLOCK", cmd_unlock, hlp_unlock },
{ "GETINFO", cmd_getinfo, hlp_getinfo },
{ "RESTART", cmd_restart, hlp_restart },
{ "DISCONNECT", cmd_disconnect,hlp_disconnect },
{ "APDU", cmd_apdu, hlp_apdu },
{ "KILLSCD", cmd_killscd, hlp_killscd },
{ "KEYINFO", cmd_keyinfo, hlp_keyinfo },
{ "DEVINFO", cmd_devinfo, hlp_devinfo },
{ NULL }
};
int i, rc;
for (i=0; table[i].name; i++)
{
rc = assuan_register_command (ctx, table[i].name, table[i].handler,
table[i].help);
if (rc)
return rc;
}
assuan_set_hello_line (ctx, "GNU Privacy Guard's Smartcard server ready");
assuan_register_reset_notify (ctx, reset_notify);
assuan_register_option_handler (ctx, option_handler);
return 0;
}
/* Startup the server. If FD is given as -1 this is simple pipe
server, otherwise it is a regular server. Returns true if there
are no more active asessions. */
int
scd_command_handler (ctrl_t ctrl, gnupg_fd_t fd)
{
int rc;
assuan_context_t ctx = NULL;
int stopme;
rc = assuan_new (&ctx);
if (rc)
{
log_error ("failed to allocate assuan context: %s\n",
gpg_strerror (rc));
scd_exit (2);
}
if (fd == GNUPG_INVALID_FD)
{
assuan_fd_t filedes[2];
filedes[0] = assuan_fdopen (0);
filedes[1] = assuan_fdopen (1);
rc = assuan_init_pipe_server (ctx, filedes);
}
else
{
rc = assuan_init_socket_server (ctx, fd,
ASSUAN_SOCKET_SERVER_ACCEPTED);
}
if (rc)
{
log_error ("failed to initialize the server: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
rc = register_commands (ctx);
if (rc)
{
log_error ("failed to register commands with Assuan: %s\n",
gpg_strerror(rc));
scd_exit (2);
}
assuan_set_pointer (ctx, ctrl);
/* Allocate and initialize the server object. Put it into the list
of active sessions. */
ctrl->server_local = xcalloc (1, sizeof *ctrl->server_local);
ctrl->server_local->next_session = session_list;
session_list = ctrl->server_local;
ctrl->server_local->ctrl_backlink = ctrl;
ctrl->server_local->assuan_ctx = ctx;
/* Command processing loop. */
for (;;)
{
rc = assuan_accept (ctx);
if (rc == -1)
{
break;
}
else if (rc)
{
log_info ("Assuan accept problem: %s\n", gpg_strerror (rc));
break;
}
rc = assuan_process (ctx);
if (rc)
{
log_info ("Assuan processing failed: %s\n", gpg_strerror (rc));
continue;
}
}
/* Cleanup. We don't send an explicit reset to the card. */
do_reset (ctrl, 0, 0);
/* Release the server object. */
if (session_list == ctrl->server_local)
session_list = ctrl->server_local->next_session;
else
{
struct server_local_s *sl;
for (sl=session_list; sl->next_session; sl = sl->next_session)
if (sl->next_session == ctrl->server_local)
break;
if (!sl->next_session)
BUG ();
sl->next_session = ctrl->server_local->next_session;
}
stopme = ctrl->server_local->stopme;
xfree (ctrl->server_local);
ctrl->server_local = NULL;
/* Release the Assuan context. */
assuan_release (ctx);
if (stopme)
scd_exit (0);
/* If there are no more sessions return true. */
return !session_list;
}
/* Send a line with status information via assuan and escape all given
buffers. The variable elements are pairs of (char *, size_t),
terminated with a (NULL, 0). */
void
send_status_info (ctrl_t ctrl, const char *keyword, ...)
{
va_list arg_ptr;
const unsigned char *value;
size_t valuelen;
char buf[950], *p;
size_t n;
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
va_start (arg_ptr, keyword);
p = buf;
n = 0;
while ( (value = va_arg (arg_ptr, const unsigned char *))
&& n < DIM (buf)-2 )
{
valuelen = va_arg (arg_ptr, size_t);
if (!valuelen)
continue; /* empty buffer */
if (n)
{
*p++ = ' ';
n++;
}
for ( ; valuelen && n < DIM (buf)-2; n++, valuelen--, value++)
{
if (*value == '+' || *value == '\"' || *value == '%'
|| *value < ' ')
{
sprintf (p, "%%%02X", *value);
p += 3;
n += 2;
}
else if (*value == ' ')
*p++ = '+';
else
*p++ = *value;
}
}
*p = 0;
assuan_write_status (ctx, keyword, buf);
va_end (arg_ptr);
}
/* Send a ready formatted status line via assuan. */
gpg_error_t
send_status_direct (ctrl_t ctrl, const char *keyword, const char *args)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (strchr (args, '\n'))
{
log_error ("error: LF detected in status line - not sending\n");
return gpg_error (GPG_ERR_INTERNAL);
}
return assuan_write_status (ctx, keyword, args);
}
/* This status functions expects a printf style format string. No
* filtering of the data is done instead the printf formatted data is
* send using assuan_send_status. */
gpg_error_t
send_status_printf (ctrl_t ctrl, const char *keyword, const char *format, ...)
{
gpg_error_t err;
va_list arg_ptr;
assuan_context_t ctx;
if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
return 0;
va_start (arg_ptr, format);
err = vprint_assuan_status (ctx, keyword, format, arg_ptr);
va_end (arg_ptr);
return err;
}
/* Set a gcrypt key for use with the pincache. The key is a random
* key unique for this process and is useless after this process has
* terminated. This way the cached PINs stored in the gpg-agent are
* bound to this specific process. The main purpose of this
* encryption is to hide the PIN in logs of the IPC. */
static gpg_error_t
set_key_for_pincache (gcry_cipher_hd_t hd)
{
static int initialized;
static unsigned char keybuf[16];
if (!initialized)
{
gcry_randomize (keybuf, sizeof keybuf, GCRY_STRONG_RANDOM);
initialized = 1;
}
return gcry_cipher_setkey (hd, keybuf, sizeof keybuf);
}
/* Store the PIN in the PIN cache. The key to identify the PIN
* consists of (SLOT,APPNAME,PINREF). If PIN is NULL the PIN stored
* under the given key is cleared. If APPNAME and PINREF are NULL the
* entire PIN cache for SLOT is cleared. If SLOT is -1 the entire PIN
* cache is cleared. We do no use an scdaemon internal cache but let
* gpg-agent cache it because it is better suited for this. */
void
pincache_put (ctrl_t ctrl, int slot, const char *appname, const char *pinref,
const char *pin, unsigned int pinlen)
{
gpg_error_t err = 0;
assuan_context_t ctx;
char line[950];
gcry_cipher_hd_t cipherhd = NULL;
char *pinbuf = NULL;
unsigned char *wrappedkey = NULL;
size_t pinbuflen, wrappedkeylen;
if (!ctrl)
{
/* No CTRL object provided. We could pick an arbitrary
* connection and send the status to that one. However, such a
* connection is inlikley to wait for a respinse from use and
* thus it would at best be read as a response to the next
* command send to us. That is not good because it may clog up
* our connection. Thus we better don't do that. A better will
* be to queue this up and let the agent poll for general status
* messages. */
/* struct server_local_s *sl; */
/* for (sl=session_list; sl; sl = sl->next_session) */
/* if (sl->ctrl_backlink && sl->ctrl_backlink->server_local */
/* && sl->ctrl_backlink->server_local->assuan_ctx) */
/* { */
/* ctrl = sl->ctrl_backlink; */
/* break; */
/* } */
}
if (!ctrl || !ctrl->server_local || !(ctx=ctrl->server_local->assuan_ctx))
return;
if (pin && !pinlen)
return; /* Ignore an empty PIN. */
snprintf (line, sizeof line, "%d/%s/%s ",
slot, appname? appname:"", pinref? pinref:"");
/* Without an APPNAME etc or without a PIN we clear the cache and
* thus there is no need to send the pin - even if the caller
* accidentally passed a pin. */
if (pin && slot != -1 && appname && pinref)
{
/* FIXME: Replace this by OCB mode and use the cache key as
* additional data. */
/* Pad with zeroes (AESWRAP requires multiples of 64 bit but
* at least 128 bit data). */
pinbuflen = pinlen + 8 - (pinlen % 8);
if (pinbuflen < 16)
pinbuflen = 16;
pinbuf = xtrycalloc_secure (1, pinbuflen);
if (!pinbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (pinbuf, pin, pinlen);
pinlen = pinbuflen;
pin = pinbuf;
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = set_key_for_pincache (cipherhd);
if (err)
goto leave;
wrappedkeylen = pinlen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen,
pin, pinlen);
if (err)
goto leave;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
if (strlen (line) + 2*wrappedkeylen + 1 >= sizeof line)
{
log_error ("%s: PIN or pinref string too long - ignored", __func__);
goto leave;
}
bin2hex (wrappedkey, wrappedkeylen, line + strlen (line));
}
send_status_direct (ctrl, "PINCACHE_PUT", line);
leave:
xfree (pinbuf);
xfree (wrappedkey);
gcry_cipher_close (cipherhd);
if (err)
log_error ("%s: error caching PIN: %s\n", __func__, gpg_strerror (err));
}
/* Ask the agent for a cached PIN for the tuple (SLOT,APPNAME,PINREF).
* Returns on success and stores the PIN at R_PIN; the caller needs to
* wipe(!) and then free that value. On error NULL is stored at
* R_PIN and an error code returned. Common error codes are:
* GPG_ERR_NOT_SUPPORTED - Client does not support the PIN cache
* GPG_ERR_NO_DATA - No PIN cached for the given key tuple
*/
gpg_error_t
pincache_get (ctrl_t ctrl, int slot, const char *appname, const char *pinref,
char **r_pin)
{
gpg_error_t err;
assuan_context_t ctx;
char command[512];
unsigned char *value = NULL;
size_t valuelen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
if (slot == -1 || !appname || !pinref || !r_pin)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (!ctrl || !ctrl->server_local || !(ctx = ctrl->server_local->assuan_ctx))
{
err = gpg_error (GPG_ERR_USE_CONDITIONS);
log_error ("%s: called w/o assuan context\n", __func__);
goto leave;
}
snprintf (command, sizeof command, "PINCACHE_GET %d/%s/%s",
slot, appname? appname:"", pinref? pinref:"");
/* Limit the inquire to something reasonable. The 32 extra bytes
* are a guessed size for padding etc. */
err = assuan_inquire (ctx, command, &wrappedkey, &wrappedkeylen,
2*MAXLEN_PIN+32);
if (gpg_err_code (err) == GPG_ERR_ASS_CANCELED)
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
log_info ("caller does not feature a PIN cache");
goto leave;
}
if (err)
{
log_error ("%s: sending PINCACHE_GET to caller failed: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (!wrappedkey || !wrappedkeylen)
{
err = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Convert to hex to binary and store it in (wrappedkey, wrappedkeylen). */
if (!hex2str (wrappedkey, wrappedkey, wrappedkeylen, &wrappedkeylen))
{
err = gpg_error_from_syserror ();
log_error ("%s: caller returned invalid hex string: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
if (!wrappedkey || wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH); /* too short cryptogram */
goto leave;
}
valuelen = wrappedkeylen - 8;
value = xtrymalloc_secure (valuelen);
if (!value)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (!err)
err = set_key_for_pincache (cipherhd);
if (err)
goto leave;
err = gcry_cipher_decrypt (cipherhd, value, valuelen,
wrappedkey, wrappedkeylen);
if (err)
{
log_error ("%s: cached value could not be decrypted: %s\n",
__func__, gpg_strerror (err));
goto leave;
}
*r_pin = value;
value = NULL;
leave:
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (value);
return err;
}
void
popup_prompt (void *opaque, int on)
{
ctrl_t ctrl = opaque;
if (ctrl)
{
assuan_context_t ctx = ctrl->server_local->assuan_ctx;
if (ctx)
{
const char *cmd;
gpg_error_t err;
unsigned char *value;
size_t valuelen;
if (on)
cmd = "POPUPPINPADPROMPT --ack";
else
cmd = "DISMISSPINPADPROMPT";
err = assuan_inquire (ctx, cmd, &value, &valuelen, 100);
if (!err)
xfree (value);
}
}
}
/*
* Helper to send the clients a status change notification.
*
* When it's removal of card, this function also clean up all
* references by ctrl->card_ctx of all sessions.
*
* Note that this function assumes that all accesses to cards and
* applications are locked. By the mrsw-lock, it is guaranteed that
* no card/app is accessed, when this function is called..
*/
void
send_client_notifications (card_t card, int removal)
{
struct {
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
pid_t pid;
int signo;
#endif
} killed[50];
int killidx = 0;
int kidx;
struct server_local_s *sl;
for (sl=session_list; sl; sl = sl->next_session)
{
if (sl->watching_status)
{
if (removal)
assuan_write_status (sl->assuan_ctx, "DEVINFO_STATUS", "removal");
else
assuan_write_status (sl->assuan_ctx, "DEVINFO_STATUS", "new");
}
if (sl->ctrl_backlink && sl->ctrl_backlink->card_ctx == card)
{
#ifdef HAVE_W32_SYSTEM
HANDLE handle;
#else
pid_t pid;
int signo;
#endif
if (removal)
{
sl->ctrl_backlink->card_ctx = NULL;
sl->ctrl_backlink->current_apptype = APPTYPE_NONE;
sl->card_removed = 1;
card_unref_locked (card);
}
if (!sl->event_signal || !sl->assuan_ctx)
continue;
#ifdef HAVE_W32_SYSTEM
handle = sl->event_signal;
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].handle == handle)
break;
if (kidx < killidx)
{
if (opt.verbose)
log_info ("event %p already triggered for client\n",
sl->event_signal);
}
else
{
if (opt.verbose)
log_info ("triggering event %p for client\n",
sl->event_signal);
if (!SetEvent (handle))
log_error ("SetEvent(%p) failed: %s\n",
sl->event_signal, w32_strerror (-1));
if (killidx < DIM (killed))
{
killed[killidx].handle = handle;
killidx++;
}
}
#else /*!HAVE_W32_SYSTEM*/
pid = assuan_get_pid (sl->assuan_ctx);
signo = sl->event_signal;
if (pid != (pid_t)(-1) && pid && signo > 0)
{
for (kidx=0; kidx < killidx; kidx++)
if (killed[kidx].pid == pid
&& killed[kidx].signo == signo)
break;
if (kidx < killidx)
{
if (opt.verbose)
log_info ("signal %d already sent to client %d\n",
signo, (int)pid);
}
else
{
if (opt.verbose)
log_info ("sending signal %d to client %d\n",
signo, (int)pid);
kill (pid, signo);
if (killidx < DIM (killed))
{
killed[killidx].pid = pid;
killed[killidx].signo = signo;
killidx++;
}
}
}
#endif /*!HAVE_W32_SYSTEM*/
}
}
}
diff --git a/scd/iso7816.c b/scd/iso7816.c
index 703f1fdab..6634cd4a7 100644
--- a/scd/iso7816.c
+++ b/scd/iso7816.c
@@ -1,1184 +1,1184 @@
/* iso7816.c - ISO 7816 commands
* Copyright (C) 2003, 2004, 2008, 2009 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#if defined(GNUPG_MAJOR_VERSION)
# include "scdaemon.h"
#endif /*GNUPG_MAJOR_VERSION*/
#include "iso7816.h"
#include "apdu.h"
#define CMD_SELECT_FILE 0xA4
#define CMD_SELECT_DATA 0xA5
#define CMD_VERIFY ISO7816_VERIFY
#define CMD_CHANGE_REFERENCE_DATA ISO7816_CHANGE_REFERENCE_DATA
#define CMD_RESET_RETRY_COUNTER ISO7816_RESET_RETRY_COUNTER
#define CMD_GET_DATA 0xCA
#define CMD_PUT_DATA 0xDA
#define CMD_MSE 0x22
#define CMD_PSO 0x2A
#define CMD_GENERAL_AUTHENTICATE 0x87
#define CMD_INTERNAL_AUTHENTICATE 0x88
#define CMD_GENERATE_KEYPAIR 0x47
#define CMD_GET_CHALLENGE 0x84
#define CMD_READ_BINARY 0xB0
#define CMD_READ_RECORD 0xB2
#define CMD_UPDATE_BINARY 0xD6
static gpg_error_t
map_sw (int sw)
{
gpg_err_code_t ec;
switch (sw)
{
case SW_EEPROM_FAILURE: ec = GPG_ERR_HARDWARE; break;
case SW_TERM_STATE: ec = GPG_ERR_OBJ_TERM_STATE; break;
case SW_WRONG_LENGTH: ec = GPG_ERR_INV_VALUE; break;
case SW_ACK_TIMEOUT: ec = GPG_ERR_TIMEOUT; break;
case SW_SM_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_CC_NOT_SUP: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_FILE_STRUCT: ec = GPG_ERR_CARD; break;
case SW_CHV_WRONG: ec = GPG_ERR_BAD_PIN; break;
case SW_CHV_BLOCKED: ec = GPG_ERR_PIN_BLOCKED; break;
case SW_USE_CONDITIONS: ec = GPG_ERR_USE_CONDITIONS; break;
case SW_NO_CURRENT_EF: ec = GPG_ERR_ENOENT; break;
case SW_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_BAD_PARAMETER: ec = GPG_ERR_INV_VALUE; break;
case SW_FILE_NOT_FOUND: ec = GPG_ERR_ENOENT; break;
case SW_RECORD_NOT_FOUND:ec= GPG_ERR_NOT_FOUND; break;
case SW_REF_NOT_FOUND: ec = GPG_ERR_NO_OBJ; break;
case SW_INCORRECT_P0_P1:ec = GPG_ERR_INV_VALUE; break;
case SW_BAD_P0_P1: ec = GPG_ERR_INV_VALUE; break;
case SW_EXACT_LENGTH: ec = GPG_ERR_INV_VALUE; break;
case SW_INS_NOT_SUP: ec = GPG_ERR_CARD; break;
case SW_CLA_NOT_SUP: ec = GPG_ERR_CARD; break;
case SW_SUCCESS: ec = 0; break;
case SW_HOST_OUT_OF_CORE: ec = GPG_ERR_ENOMEM; break;
case SW_HOST_INV_VALUE: ec = GPG_ERR_INV_VALUE; break;
case SW_HOST_INCOMPLETE_CARD_RESPONSE: ec = GPG_ERR_CARD; break;
case SW_HOST_NOT_SUPPORTED: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_HOST_LOCKING_FAILED: ec = GPG_ERR_BUG; break;
case SW_HOST_BUSY: ec = GPG_ERR_EBUSY; break;
case SW_HOST_NO_CARD: ec = GPG_ERR_CARD_NOT_PRESENT; break;
case SW_HOST_CARD_INACTIVE: ec = GPG_ERR_CARD_RESET; break;
case SW_HOST_CARD_IO_ERROR: ec = GPG_ERR_EIO; break;
case SW_HOST_GENERAL_ERROR: ec = GPG_ERR_GENERAL; break;
case SW_HOST_NO_READER: ec = GPG_ERR_ENODEV; break;
case SW_HOST_ABORTED: ec = GPG_ERR_INV_RESPONSE; break;
case SW_HOST_NO_PINPAD: ec = GPG_ERR_NOT_SUPPORTED; break;
case SW_HOST_CANCELLED: ec = GPG_ERR_CANCELED; break;
case SW_HOST_USB_OTHER: ec = GPG_ERR_EIO; break;
case SW_HOST_USB_IO: ec = GPG_ERR_EIO; break;
case SW_HOST_USB_ACCESS: ec = GPG_ERR_EACCES; break;
case SW_HOST_USB_NO_DEVICE: ec = GPG_ERR_ENODEV; break;
case SW_HOST_USB_BUSY: ec = GPG_ERR_EBUSY; break;
case SW_HOST_USB_TIMEOUT: ec = GPG_ERR_TIMEOUT; break;
case SW_HOST_USB_OVERFLOW: ec = GPG_ERR_EOVERFLOW; break;
case SW_HOST_UI_CANCELLED: ec = GPG_ERR_CANCELED; break;
case SW_HOST_UI_TIMEOUT: ec = GPG_ERR_TIMEOUT; break;
default:
if ((sw & 0x010000))
ec = GPG_ERR_GENERAL; /* Should not happen. */
else if ((sw & 0xff00) == SW_MORE_DATA)
ec = 0; /* This should actually never been seen here. */
else if ((sw & 0xfff0) == 0x63C0)
ec = GPG_ERR_BAD_PIN;
else
ec = GPG_ERR_CARD;
}
return gpg_error (ec);
}
/* Map a status word from the APDU layer to a gpg-error code. */
gpg_error_t
iso7816_map_sw (int sw)
{
/* All APDU functions should return 0x9000 on success but for
historical reasons of the implementation some return 0 to
indicate success. We allow for that here. */
return sw? map_sw (sw) : 0;
}
/* This function is specialized version of the SELECT FILE command.
SLOT is the card and reader as created for example by
apdu_open_reader (), AID is a buffer of size AIDLEN holding the
requested application ID. The function can't be used to enumerate
AIDs and won't return the AID on success. The return value is 0
for okay or a GPG error code. Note that ISO error codes are
internally mapped. Bit 0 of FLAGS should be set if the card does
not understand P2=0xC0. */
gpg_error_t
iso7816_select_application (int slot, const char *aid, size_t aidlen,
unsigned int flags)
{
int sw;
sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 4,
(flags&1)? 0 :0x0c, aidlen, aid);
return map_sw (sw);
}
/* This is the same as iso7816_select_application but may return data
* at RESULT,RESULTLEN). */
gpg_error_t
iso7816_select_application_ext (int slot, const char *aid, size_t aidlen,
unsigned int flags,
unsigned char **result, size_t *resultlen)
{
int sw;
sw = apdu_send (slot, 0, 0x00, CMD_SELECT_FILE, 4,
(flags&1)? 0:0x0c, aidlen, aid,
result, resultlen);
return map_sw (sw);
}
/* Simple MF selection as supported by some cards. */
gpg_error_t
iso7816_select_mf (int slot)
{
int sw;
sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE, 0x00, 0x0c, -1, NULL);
return map_sw (sw);
}
gpg_error_t
iso7816_select_file (int slot, int tag, int is_dir)
{
int sw, p0, p1;
unsigned char tagbuf[2];
tagbuf[0] = (tag >> 8) & 0xff;
tagbuf[1] = tag & 0xff;
p0 = (tag == 0x3F00)? 0: is_dir? 1:2;
p1 = 0x0c; /* No FC return. */
sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE,
p0, p1, 2, (char*)tagbuf );
return map_sw (sw);
}
/* Do a select file command with a direct path. If TOPDF is set, the
* actual used path is 3f00/<topdf>/<path>. */
gpg_error_t
iso7816_select_path (int slot, const unsigned short *path, size_t pathlen,
unsigned short topdf)
{
int sw, p0, p1;
unsigned char buffer[100];
int buflen = 0;
if (pathlen*2 + 2 >= sizeof buffer)
return gpg_error (GPG_ERR_TOO_LARGE);
if (topdf)
{
buffer[buflen++] = topdf >> 8;
buffer[buflen++] = topdf;
}
for (; pathlen; pathlen--, path++)
{
buffer[buflen++] = (*path >> 8);
buffer[buflen++] = *path;
}
p0 = 0x08;
p1 = 0x0c; /* No FC return. */
sw = apdu_send_simple (slot, 0, 0x00, CMD_SELECT_FILE,
p0, p1, buflen, (char*)buffer );
return map_sw (sw);
}
/* This is a private command currently only working for TCOS cards. */
gpg_error_t
iso7816_list_directory (int slot, int list_dirs,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
sw = apdu_send (slot, 0, 0x80, 0xAA, list_dirs? 1:2, 0, -1, NULL,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
return map_sw (sw);
}
/* Wrapper around apdu_send. RESULT can be NULL if no result is
* expected. In addition to an gpg-error return code the actual
* status word is stored at R_SW unless that is NULL. */
gpg_error_t
iso7816_send_apdu (int slot, int extended_mode,
int class, int ins, int p0, int p1,
int lc, const void *data,
unsigned int *r_sw,
unsigned char **result, size_t *resultlen)
{
int sw;
if (result)
{
*result = NULL;
*resultlen = 0;
}
sw = apdu_send (slot, extended_mode, class, ins, p0, p1, lc, data,
result, resultlen);
if (sw != SW_SUCCESS && result)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
if (r_sw)
*r_sw = sw;
return map_sw (sw);
}
/* This function sends an already formatted APDU to the card. With
HANDLE_MORE set to true a MORE DATA status will be handled
internally. The return value is a gpg error code (i.e. a mapped
status word). This is basically the same as apdu_send_direct but
it maps the status word and does not return it in the result
buffer. However, it R_SW is not NULL the status word is stored
R_SW for closer inspection. */
gpg_error_t
iso7816_apdu_direct (int slot, const void *apdudata, size_t apdudatalen,
int handle_more, unsigned int *r_sw,
unsigned char **result, size_t *resultlen)
{
int sw, sw2;
if (result)
{
*result = NULL;
*resultlen = 0;
}
sw = apdu_send_direct (slot, 0, apdudata, apdudatalen, handle_more,
&sw2, result, resultlen);
if (!sw)
{
if (!result)
sw = sw2;
else if (*resultlen < 2)
sw = SW_HOST_GENERAL_ERROR;
else
{
sw = ((*result)[*resultlen-2] << 8) | (*result)[*resultlen-1];
(*resultlen)--;
(*resultlen)--;
}
}
if (sw != SW_SUCCESS && result)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
}
if (r_sw)
*r_sw = sw;
return map_sw (sw);
}
/* Check whether the reader supports the ISO command code COMMAND on
the pinpad. Returns 0 on success. */
gpg_error_t
iso7816_check_pinpad (int slot, int command, pininfo_t *pininfo)
{
int sw;
sw = apdu_check_pinpad (slot, command, pininfo);
return iso7816_map_sw (sw);
}
/* Perform a VERIFY command on SLOT using the card holder verification
vector CHVNO. With PININFO non-NULL the pinpad of the reader will
be used. Returns 0 on success. */
gpg_error_t
iso7816_verify_kp (int slot, int chvno, pininfo_t *pininfo)
{
int sw;
sw = apdu_pinpad_verify (slot, 0x00, CMD_VERIFY, 0, chvno, pininfo);
return map_sw (sw);
}
/* Perform a VERIFY command on SLOT using the card holder verification
vector CHVNO with a CHV of length CHVLEN. Returns 0 on success. */
gpg_error_t
iso7816_verify (int slot, int chvno, const char *chv, size_t chvlen)
{
int sw;
sw = apdu_send_simple (slot, 0, 0x00, CMD_VERIFY, 0, chvno, chvlen, chv);
return map_sw (sw);
}
/* Some cards support a VERIFY command variant to check the status of
* the the CHV without a need to try a CHV. In contrast to the other
* functions this function returns the special codes ISO7816_VERIFY_*
* or a non-negative number with the left attempts. */
int
iso7816_verify_status (int slot, int chvno)
{
unsigned char apdu[4];
unsigned int sw;
int result;
apdu[0] = 0x00;
apdu[1] = ISO7816_VERIFY;
apdu[2] = 0x00;
apdu[3] = chvno;
if (!iso7816_apdu_direct (slot, apdu, 4, 0, &sw, NULL, NULL))
result = ISO7816_VERIFY_NOT_NEEDED; /* Not returned by all cards. */
else if (sw == 0x6a88 || sw == 0x6a80)
result = ISO7816_VERIFY_NO_PIN;
else if (sw == 0x6983)
result = ISO7816_VERIFY_BLOCKED;
else if (sw == 0x6985)
result = ISO7816_VERIFY_NULLPIN; /* TCOS card */
else if ((sw & 0xfff0) == 0x63C0)
result = (sw & 0x000f);
else
result = ISO7816_VERIFY_ERROR;
return result;
}
/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder
verification vector CHVNO. With PININFO non-NULL the pinpad of the
reader will be used. If IS_EXCHANGE is 0, a "change reference
data" is done, otherwise an "exchange reference data". */
gpg_error_t
iso7816_change_reference_data_kp (int slot, int chvno, int is_exchange,
pininfo_t *pininfo)
{
int sw;
sw = apdu_pinpad_modify (slot, 0x00, CMD_CHANGE_REFERENCE_DATA,
is_exchange ? 1 : 0, chvno, pininfo);
return map_sw (sw);
}
/* Perform a CHANGE_REFERENCE_DATA command on SLOT for the card holder
verification vector CHVNO. If the OLDCHV is NULL (and OLDCHVLEN
0), a "change reference data" is done, otherwise an "exchange
reference data". The new reference data is expected in NEWCHV of
length NEWCHVLEN. */
gpg_error_t
iso7816_change_reference_data (int slot, int chvno,
const char *oldchv, size_t oldchvlen,
const char *newchv, size_t newchvlen)
{
int sw;
char *buf;
if ((!oldchv && oldchvlen)
|| (oldchv && !oldchvlen)
|| !newchv || !newchvlen )
return gpg_error (GPG_ERR_INV_VALUE);
buf = xtrymalloc (oldchvlen + newchvlen);
if (!buf)
return gpg_error (gpg_err_code_from_errno (errno));
if (oldchvlen)
memcpy (buf, oldchv, oldchvlen);
memcpy (buf+oldchvlen, newchv, newchvlen);
sw = apdu_send_simple (slot, 0, 0x00, CMD_CHANGE_REFERENCE_DATA,
oldchvlen? 0 : 1, chvno, oldchvlen+newchvlen, buf);
wipememory (buf, oldchvlen+newchvlen);
xfree (buf);
return map_sw (sw);
}
gpg_error_t
iso7816_reset_retry_counter_with_rc (int slot, int chvno,
const char *data, size_t datalen)
{
int sw;
if (!data || !datalen )
return gpg_error (GPG_ERR_INV_VALUE);
sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER,
0, chvno, datalen, data);
return map_sw (sw);
}
gpg_error_t
iso7816_reset_retry_counter (int slot, int chvno,
const char *newchv, size_t newchvlen)
{
int sw;
sw = apdu_send_simple (slot, 0, 0x00, CMD_RESET_RETRY_COUNTER,
2, chvno, newchvlen, newchv);
return map_sw (sw);
}
-/* Perform a SELECT DATA command to OCCURANCE of TAG. */
+/* Perform a SELECT DATA command to OCCURRENCE of TAG. */
gpg_error_t
iso7816_select_data (int slot, int occurrence, int tag)
{
int sw;
int datalen;
unsigned char data[7];
data[0] = 0x60;
data[2] = 0x5c;
if (tag <= 0xff)
{
data[3] = 1;
data[4] = tag;
datalen = 5;
}
else if (tag <= 0xffff)
{
data[3] = 2;
data[4] = (tag >> 8);
data[5] = tag;
datalen = 6;
}
else
{
data[3] = 3;
data[4] = (tag >> 16);
data[5] = (tag >> 8);
data[6] = tag;
datalen = 7;
}
data[1] = datalen - 2;
sw = apdu_send_le (slot, 0, 0x00, CMD_SELECT_DATA,
occurrence, 0x04, datalen, data, 0, NULL, NULL);
return map_sw (sw);
}
/* Perform a GET DATA command requesting TAG and storing the result in
a newly allocated buffer at the address passed by RESULT. Return
the length of this data at the address of RESULTLEN. */
gpg_error_t
iso7816_get_data (int slot, int extended_mode, int tag,
unsigned char **result, size_t *resultlen)
{
int sw;
int le;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (extended_mode > 0 && extended_mode < 256)
le = 65534; /* Not 65535 in case it is used as some special flag. */
else if (extended_mode > 0)
le = extended_mode;
else
le = 256;
sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA,
((tag >> 8) & 0xff), (tag & 0xff), -1, NULL, le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* Perform a GET DATA command requesting TAG and storing the result in
* a newly allocated buffer at the address passed by RESULT. Return
* the length of this data at the address of RESULTLEN. This variant
* is needed for long (3 octet) tags. */
gpg_error_t
iso7816_get_data_odd (int slot, int extended_mode, unsigned int tag,
unsigned char **result, size_t *resultlen)
{
int sw;
int le;
int datalen;
unsigned char data[5];
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (extended_mode > 0 && extended_mode < 256)
le = 65534; /* Not 65535 in case it is used as some special flag. */
else if (extended_mode > 0)
le = extended_mode;
else
le = 256;
data[0] = 0x5c;
if (tag <= 0xff)
{
data[1] = 1;
data[2] = tag;
datalen = 3;
}
else if (tag <= 0xffff)
{
data[1] = 2;
data[2] = (tag >> 8);
data[3] = tag;
datalen = 4;
}
else
{
data[1] = 3;
data[2] = (tag >> 16);
data[3] = (tag >> 8);
data[4] = tag;
datalen = 5;
}
sw = apdu_send_le (slot, extended_mode, 0x00, CMD_GET_DATA + 1,
0x3f, 0xff, datalen, data, le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* Perform a PUT DATA command on card in SLOT. Write DATA of length
DATALEN to TAG. EXTENDED_MODE controls whether extended length
headers or command chaining is used instead of single length
bytes. */
gpg_error_t
iso7816_put_data (int slot, int extended_mode, int tag,
const void *data, size_t datalen)
{
int sw;
sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA,
((tag >> 8) & 0xff), (tag & 0xff),
datalen, (const char*)data);
return map_sw (sw);
}
/* Same as iso7816_put_data but uses an odd instruction byte. */
gpg_error_t
iso7816_put_data_odd (int slot, int extended_mode, int tag,
const void *data, size_t datalen)
{
int sw;
sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_PUT_DATA+1,
((tag >> 8) & 0xff), (tag & 0xff),
datalen, (const char*)data);
return map_sw (sw);
}
/* Manage Security Environment. This is a weird operation and there
is no easy abstraction for it. Furthermore, some card seem to have
a different interpretation of 7816-8 and thus we resort to let the
caller decide what to do. */
gpg_error_t
iso7816_manage_security_env (int slot, int p1, int p2,
const unsigned char *data, size_t datalen)
{
int sw;
if (p1 < 0 || p1 > 255 || p2 < 0 || p2 > 255 )
return gpg_error (GPG_ERR_INV_VALUE);
sw = apdu_send_simple (slot, 0, 0x00, CMD_MSE, p1, p2,
data? datalen : -1, (const char*)data);
return map_sw (sw);
}
-/* Perform the security operation COMPUTE DIGITAL SIGANTURE. On
+/* Perform the security operation COMPUTE DIGITAL SIGNATURE. On
success 0 is returned and the data is available in a newly
allocated buffer stored at RESULT with its length stored at
RESULTLEN. For LE see do_generate_keypair. */
gpg_error_t
iso7816_compute_ds (int slot, int extended_mode,
const unsigned char *data, size_t datalen, int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_PSO, 0x9E, 0x9A,
datalen, (const char*)data,
le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* Perform the security operation DECIPHER. PADIND is the padding
indicator to be used. It should be 0 if no padding is required, a
value of -1 suppresses the padding byte. On success 0 is returned
and the plaintext is available in a newly allocated buffer stored
at RESULT with its length stored at RESULTLEN. For LE see
do_generate_keypair. */
gpg_error_t
iso7816_decipher (int slot, int extended_mode,
const unsigned char *data, size_t datalen, int le,
int padind, unsigned char **result, size_t *resultlen)
{
int sw;
unsigned char *buf;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
if (padind >= 0)
{
/* We need to prepend the padding indicator. */
buf = xtrymalloc (datalen + 1);
if (!buf)
return gpg_error (gpg_err_code_from_errno (errno));
*buf = padind; /* Padding indicator. */
memcpy (buf+1, data, datalen);
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_PSO, 0x80, 0x86,
datalen+1, (char*)buf, le,
result, resultlen);
xfree (buf);
}
else
{
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_PSO, 0x80, 0x86,
datalen, (const char *)data, le,
result, resultlen);
}
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* Perform the security operation COMPUTE SHARED SECRET. On success 0
is returned and the shared secret is available in a newly allocated
buffer stored at RESULT with its length stored at RESULTLEN. For
LE see do_generate_keypair. */
gpg_error_t
iso7816_pso_csv (int slot, int extended_mode,
const unsigned char *data, size_t datalen, int le,
unsigned char **result, size_t *resultlen)
{
int sw;
unsigned char *buf;
unsigned int nbuf;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
/* Data needs to be in BER-TLV format. */
buf = xtrymalloc (datalen + 4);
if (!buf)
return gpg_error_from_syserror ();
nbuf = 0;
buf[nbuf++] = 0x9c;
if (datalen < 128)
buf[nbuf++] = datalen;
else if (datalen < 256)
{
buf[nbuf++] = 0x81;
buf[nbuf++] = datalen;
}
else
{
buf[nbuf++] = 0x82;
buf[nbuf++] = datalen << 8;
buf[nbuf++] = datalen;
}
memcpy (buf+nbuf, data, datalen);
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_PSO, 0x80, 0xa6,
datalen+nbuf, (const char *)buf, le,
result, resultlen);
xfree (buf);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* For LE see do_generate_keypair. */
gpg_error_t
iso7816_internal_authenticate (int slot, int extended_mode,
const unsigned char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_INTERNAL_AUTHENTICATE, 0, 0,
datalen, (const char*)data,
le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* For LE see do_generate_keypair. */
gpg_error_t
iso7816_general_authenticate (int slot, int extended_mode,
int algoref, int keyref,
const unsigned char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
if (!extended_mode)
le = 256; /* Ignore provided Le and use what apdu_send uses. */
else if (le >= 0 && le < 256)
le = 256;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_GENERAL_AUTHENTICATE, algoref, keyref,
datalen, (const char*)data,
le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
/* LE is the expected return length. This is usually 0 except if
extended length mode is used and more than 256 byte will be
returned. In that case a value of -1 uses a large default
(e.g. 4096 bytes), a value larger 256 used that value. */
static gpg_error_t
do_generate_keypair (int slot, int extended_mode, int p1, int p2,
const char *data, size_t datalen, int le,
unsigned char **result, size_t *resultlen)
{
int sw;
if (!data || !datalen || !result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
sw = apdu_send_le (slot, extended_mode,
0x00, CMD_GENERATE_KEYPAIR, p1, p2,
datalen, data,
le >= 0 && le < 256? 256:le,
result, resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
return 0;
}
gpg_error_t
iso7816_generate_keypair (int slot, int extended_mode, int p1, int p2,
const char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
return do_generate_keypair (slot, extended_mode, p1, p2,
data, datalen, le, result, resultlen);
}
gpg_error_t
iso7816_read_public_key (int slot, int extended_mode,
const char *data, size_t datalen,
int le,
unsigned char **result, size_t *resultlen)
{
return do_generate_keypair (slot, extended_mode, 0x81, 0,
data, datalen, le, result, resultlen);
}
gpg_error_t
iso7816_get_challenge (int slot, int length, unsigned char *buffer)
{
int sw;
unsigned char *result;
size_t resultlen, n;
if (!buffer || length < 1)
return gpg_error (GPG_ERR_INV_VALUE);
do
{
result = NULL;
n = length > 254? 254 : length;
sw = apdu_send_le (slot, 0,
0x00, CMD_GET_CHALLENGE, 0, 0, -1, NULL, n,
&result, &resultlen);
if (sw != SW_SUCCESS)
{
/* Make sure that pending buffers are released. */
xfree (result);
return map_sw (sw);
}
if (resultlen > n)
resultlen = n;
memcpy (buffer, result, resultlen);
buffer += resultlen;
length -= resultlen;
xfree (result);
}
while (length > 0);
return 0;
}
/* Perform a READ BINARY command requesting a maximum of NMAX bytes
* from OFFSET. With NMAX = 0 the entire file is read. The result is
* stored in a newly allocated buffer at the address passed by RESULT.
* Returns the length of this data at the address of RESULTLEN. If
* R_SW is not NULL the last status word is stored there. */
gpg_error_t
iso7816_read_binary_ext (int slot, int extended_mode,
size_t offset, size_t nmax,
unsigned char **result, size_t *resultlen,
int *r_sw)
{
int sw;
unsigned char *buffer;
size_t bufferlen;
int read_all = !nmax;
size_t n;
if (r_sw)
*r_sw = 0;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
/* We can only encode 15 bits in p0,p1 to indicate an offset. Thus
we check for this limit. */
if (offset > 32767)
return gpg_error (GPG_ERR_INV_VALUE);
do
{
buffer = NULL;
bufferlen = 0;
n = read_all? 0 : nmax;
sw = apdu_send_le (slot, extended_mode, 0x00, CMD_READ_BINARY,
((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
n, &buffer, &bufferlen);
if ( SW_EXACT_LENGTH_P(sw) )
{
n = (sw & 0x00ff);
sw = apdu_send_le (slot, extended_mode, 0x00, CMD_READ_BINARY,
((offset>>8) & 0xff), (offset & 0xff) , -1, NULL,
n, &buffer, &bufferlen);
}
if (r_sw)
*r_sw = sw;
if (*result && (sw == SW_BAD_P0_P1 || sw == SW_INCORRECT_P0_P1))
{
/* Bad Parameter means that the offset is outside of the
EF. When reading all data we take this as an indication
for EOF. */
break;
}
if (sw != SW_SUCCESS && sw != SW_EOF_REACHED)
{
/* Make sure that pending buffers are released. */
xfree (buffer);
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
if (*result) /* Need to extend the buffer. */
{
unsigned char *p = xtryrealloc (*result, *resultlen + bufferlen);
if (!p)
{
gpg_error_t err = gpg_error_from_syserror ();
xfree (buffer);
xfree (*result);
*result = NULL;
*resultlen = 0;
return err;
}
*result = p;
memcpy (*result + *resultlen, buffer, bufferlen);
*resultlen += bufferlen;
xfree (buffer);
buffer = NULL;
}
else /* Transfer the buffer into our result. */
{
*result = buffer;
*resultlen = bufferlen;
}
offset += bufferlen;
if (offset > 32767)
break; /* We simply truncate the result for too large
files. */
if (nmax > bufferlen)
nmax -= bufferlen;
else
nmax = 0;
}
while ((read_all && sw != SW_EOF_REACHED) || (!read_all && nmax));
return 0;
}
gpg_error_t
iso7816_read_binary (int slot, size_t offset, size_t nmax,
unsigned char **result, size_t *resultlen)
{
return iso7816_read_binary_ext (slot, 0, offset, nmax,
result, resultlen, NULL);
}
/* Perform a READ RECORD command. RECNO gives the record number to
read with 0 indicating the current record. RECCOUNT must be 1 (not
all cards support reading of more than one record). SHORT_EF
should be 0 to read the current EF or contain a short EF. The
result is stored in a newly allocated buffer at the address passed
by RESULT. Returns the length of this data at the address of
RESULTLEN. If R_SW is not NULL the last status word is stored
there. */
gpg_error_t
iso7816_read_record_ext (int slot, int recno, int reccount, int short_ef,
unsigned char **result, size_t *resultlen,
int *r_sw)
{
int sw;
unsigned char *buffer;
size_t bufferlen;
if (r_sw)
*r_sw = 0;
if (!result || !resultlen)
return gpg_error (GPG_ERR_INV_VALUE);
*result = NULL;
*resultlen = 0;
/* We can only encode 15 bits in p0,p1 to indicate an offset. Thus
we check for this limit. */
if (recno < 0 || recno > 255 || reccount != 1
|| short_ef < 0 || short_ef > 254 )
return gpg_error (GPG_ERR_INV_VALUE);
buffer = NULL;
bufferlen = 0;
sw = apdu_send_le (slot, 0, 0x00, CMD_READ_RECORD,
recno,
short_ef? short_ef : 0x04,
-1, NULL,
0, &buffer, &bufferlen);
if (r_sw)
*r_sw = sw;
if (sw != SW_SUCCESS && sw != SW_EOF_REACHED)
{
/* Make sure that pending buffers are released. */
xfree (buffer);
xfree (*result);
*result = NULL;
*resultlen = 0;
return map_sw (sw);
}
*result = buffer;
*resultlen = bufferlen;
return 0;
}
gpg_error_t
iso7816_read_record (int slot, int recno, int reccount, int short_ef,
unsigned char **result, size_t *resultlen)
{
return iso7816_read_record_ext (slot, recno, reccount, short_ef,
result, resultlen, NULL);
}
/* Perform an UPDATE BINARY command on card in SLOT. Write DATA of
* length DATALEN to a transparent file at OFFSET. */
gpg_error_t
iso7816_update_binary (int slot, int extended_mode, size_t offset,
const void *data, size_t datalen)
{
int sw;
/* We can only encode 15 bits in p0,p1 to indicate an offset. Thus
* we check for this limit. */
if (offset > 32767)
return gpg_error (GPG_ERR_INV_VALUE);
sw = apdu_send_simple (slot, extended_mode, 0x00, CMD_UPDATE_BINARY,
((offset>>8) & 0xff), (offset & 0xff),
datalen, (const char*)data);
return map_sw (sw);
}
diff --git a/scd/scdaemon.c b/scd/scdaemon.c
index 2a9b0923c..0127c8dda 100644
--- a/scd/scdaemon.c
+++ b/scd/scdaemon.c
@@ -1,1476 +1,1476 @@
/* scdaemon.c - The GnuPG Smartcard Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2020 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2019 Werner Koch
* Copyright (C) 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*HAVE_W32_SYSTEM*/
#include <unistd.h>
#include <signal.h>
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "scdaemon.h"
#include <ksba.h>
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "iso7816.h"
#include "apdu.h"
#include "ccid-driver.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/comopt.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugCCIDDriver,
oDebugLogTid,
oDebugAllowPINLogging,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oBatch,
oReaderPort,
oCardTimeout,
octapiDriver,
opcscDriver,
opcscShared,
oDisableCCID,
oDisableOpenSC,
oDisablePinpad,
oAllowAdmin,
oDenyAdmin,
oDisableApplication,
oApplicationPriority,
oEnablePinpadVarlen,
oCompatibilityFlags,
oListenBacklog
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_header (NULL, N_("Options used for startup")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugCCIDDriver, "debug-ccid-driver", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_s_n (oDebugAllowPINLogging, "debug-allow-pin-logging", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_s (oReaderPort, "reader-port",
N_("|N|connect to reader at port N")),
ARGPARSE_s_s (octapiDriver, "ctapi-driver",
N_("|NAME|use NAME as ct-API driver")),
ARGPARSE_s_s (opcscDriver, "pcsc-driver",
N_("|NAME|use NAME as PC/SC driver")),
ARGPARSE_s_n (opcscShared, "pcsc-shared", "@"),
ARGPARSE_s_n (oDisableCCID, "disable-ccid",
#ifdef HAVE_LIBUSB
N_("do not use the internal CCID driver")
#else
"@"
#endif
/* end --disable-ccid */),
ARGPARSE_s_u (oCardTimeout, "card-timeout",
N_("|N|disconnect the card after N seconds of inactivity")),
ARGPARSE_s_n (oDisablePinpad, "disable-pinpad",
N_("do not use a reader's pinpad")),
ARGPARSE_ignore (300, "disable-keypad"),
ARGPARSE_s_n (oEnablePinpadVarlen, "enable-pinpad-varlen",
N_("use variable length input for pinpad")),
ARGPARSE_s_s (oDisableApplication, "disable-application", "@"),
ARGPARSE_s_s (oApplicationPriority, "application-priority",
N_("|LIST|change the application priority to LIST")),
ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_header("Security", N_("Options controlling the security")),
ARGPARSE_s_n (oAllowAdmin, "allow-admin", "@"),
ARGPARSE_s_n (oDenyAdmin, "deny-admin",
N_("deny the use of admin card commands")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CARD_VALUE , "card" },
{ DBG_CARD_IO_VALUE, "cardio" },
{ DBG_READER_VALUE , "reader" },
{ DBG_APP_VALUE , "app" },
{ 0, NULL }
};
/* The list of compatibility flags. */
static struct compatibility_flags_s compatibility_flags [] =
{
{ COMPAT_CCID_NO_AUTO_DETACH, "ccid-no-auto-detach" },
{ 0, NULL }
};
/* The card driver we use by default for PC/SC. */
#if defined(HAVE_W32_SYSTEM) || defined(__CYGWIN__)
#define DEFAULT_PCSC_DRIVER "winscard.dll"
#elif defined(__APPLE__)
#define DEFAULT_PCSC_DRIVER "/System/Library/Frameworks/PCSC.framework/PCSC"
#elif defined(__GLIBC__)
#define DEFAULT_PCSC_DRIVER "libpcsclite.so.1"
#else
#define DEFAULT_PCSC_DRIVER "libpcsclite.so"
#endif
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. Change at runtime with
* --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
/* FD to notify changes. */
static int notify_fd;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (gnupg_fd_t listen_fd);
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@SCDAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 1:
case 40: p = _("Usage: @SCDAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: scdaemon [options] [command [args]]\n"
"Smartcard daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_CACHE_VALUE|DBG_CARD_IO_VALUE);
else if (!strcmp (level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
scd_exit(2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
static void
setup_signal_mask (void)
{
#ifndef HAVE_W32_SYSTEM
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
}
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
const char *shell;
int debug_argparser = 0;
const char *debug_level = NULL;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
const char *application_priority = NULL;
early_system_init ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("scdaemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.allow_admin = 1;
opt.pcsc_driver = DEFAULT_PCSC_DRIVER;
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
- * flags so that the internal intialized flag won't be cleared. */
+ * flags so that the internal initialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, SCDAEMON_NAME EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
break;
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oBatch: opt.batch=1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugCCIDDriver:
#ifdef HAVE_LIBUSB
ccid_set_debug_level (ccid_set_debug_level (-1)+1);
#endif /*HAVE_LIBUSB*/
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAllowPINLogging:
opt.debug_allow_pin_logging = 1;
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oReaderPort: opt.reader_port = pargs.r.ret_str; break;
case octapiDriver: opt.ctapi_driver = pargs.r.ret_str; break;
case opcscDriver: opt.pcsc_driver = pargs.r.ret_str; break;
case opcscShared: opt.pcsc_shared = 1; break;
case oDisableCCID: opt.disable_ccid = 1; break;
case oDisableOpenSC: break;
case oDisablePinpad: opt.disable_pinpad = 1; break;
case oAllowAdmin: /* Dummy because allow is now the default. */
break;
case oDenyAdmin: opt.allow_admin = 0; break;
case oCardTimeout: opt.card_timeout = pargs.r.ret_ulong; break;
case oDisableApplication:
add_to_strlist (&opt.disabled_applications, pargs.r.ret_str);
break;
case oApplicationPriority:
application_priority = pargs.r.ret_str;
break;
case oEnablePinpadVarlen: opt.enable_pinpad_varlen = 1; break;
case oCompatibilityFlags:
if (parse_compatibility_flags (pargs.r.ret_str, &opt.compat_flags,
compatibility_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_WARNING;
}
break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
SCDAEMON_NAME EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount(0))
exit(2);
/* Process common component options. */
if (parse_comopt (GNUPG_MODULE_NAME_SCDAEMON, debug_argparser))
exit(2);
if (!logfile)
{
logfile = comopt.logfile;
comopt.logfile = NULL;
}
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage (11),gpgrt_strusage (13),gpgrt_strusage (14));
es_fprintf (es_stderr, "%s\n", gpgrt_strusage (15));
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (initialize_module_command ())
{
log_error ("initialization failed\n");
cleanup ();
exit (1);
}
if (gpgconf_list == 2)
scd_exit (0);
if (gpgconf_list)
{
/* List options and default values in the GPG Conf format. */
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("pcsc-driver:%lu:\"%s:\n",
GC_OPT_FLAG_DEFAULT, DEFAULT_PCSC_DRIVER );
es_printf ("card-timeout:%lu:%d:\n", GC_OPT_FLAG_DEFAULT, 0);
scd_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX
| GPGRT_LOG_WITH_TIME
| GPGRT_LOG_WITH_PID));
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (application_priority)
app_update_priority_list (application_priority);
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
gnupg_fd_t fd = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name, &socket_nonce);
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
scd_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
scd_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
scd_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != GNUPG_INVALID_FD)
assuan_sock_close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (SCDAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: <name>:<pid>:<protocol_version> */
if (gpgrt_asprintf (&infostr, "SCDAEMON_INFO=%s:%lu:1",
socket_name, (ulong) pid) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
*socket_name = 0; /* don't let cleanup() remove the socket -
the child should do this from now on */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export SCDAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
setup_signal_mask ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid() == -1)
{
log_error ("setsid() failed: %s\n", strerror(errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
handle_connections (fd);
assuan_sock_close (fd);
}
xfree (config_filename);
return 0;
}
void
scd_exit (int rc)
{
apdu_prepare_exit ();
#if 0
#warning no update_random_seed_file
update_random_seed_file();
#endif
#if 0
/* at this time a bit annoying */
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
#endif
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount(0)? 2 : 0;
exit (rc);
}
static void
scd_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
scd_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
scd_get_socket_name (void)
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration and resetting cards\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
app_dump_state ();
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
scd_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info( "%s %s stopped\n", gpgrt_strusage(11), gpgrt_strusage(13));
cleanup ();
scd_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
Returns: Pointer to an allocated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
scd_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
scd_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
scd_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT(fd), listen_backlog) == -1)
{
log_error ("listen(fd, %d) failed: %s\n",
listen_backlog, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
scd_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
scd_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD_DBG (ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (scd_command_handler (ctrl, ctrl->thread_startup.fd)
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD_DBG (ctrl->thread_startup.fd));
scd_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
scd_kick_the_loop ();
return NULL;
}
void
scd_kick_the_loop (void)
{
/* Kick the select loop. */
#ifdef HAVE_W32_SYSTEM
int ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for scd_kick_the_loop failed: %s\n",
w32_strerror (-1));
#elif defined(HAVE_PSELECT_NO_EINTR)
write (notify_fd, "", 1);
#else
int ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("sending signal for scd_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
notify_fd = pipe_fd[1];
#endif
ret = npth_attr_init(&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
/* Create event for manual reset, initially non-signaled. Make it
* waitable and inheritable. */
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create scd event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess(), h,
GetCurrentProcess(), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for scd_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != GNUPG_INVALID_FD)
{
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2NUM (listen_fd);
}
for (;;)
{
int periodical_check;
int max_fd = nfd;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = GNUPG_INVALID_FD;
}
periodical_check = scd_update_reader_status_file ();
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (max_fd < pipe_fd[0])
max_fd = pipe_fd[0];
#else
(void)max_fd;
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending(&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
if (listen_fd != GNUPG_INVALID_FD
&& FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
ctrl_t ctrl;
gnupg_fd_t fd;
plen = sizeof paddr;
fd = assuan_sock_accept (listen_fd,
(struct sockaddr *)&paddr, &plen);
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
assuan_sock_close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d",
FD_DBG (fd));
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
assuan_sock_close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage(11), gpgrt_strusage(13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}
diff --git a/sm/call-agent.c b/sm/call-agent.c
index acce19058..cdd596280 100644
--- a/sm/call-agent.c
+++ b/sm/call-agent.c
@@ -1,1510 +1,1510 @@
/* call-agent.c - Divert GPGSM operations to the agent
* Copyright (C) 2001, 2002, 2003, 2005, 2007,
* 2008, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpgsm.h"
#include <gcrypt.h>
#include <assuan.h>
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "keydb.h" /* fixme: Move this to import.c */
#include "../common/membuf.h"
#include "../common/shareddefs.h"
#include "passphrase.h"
static assuan_context_t agent_ctx = NULL;
struct cipher_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const unsigned char *ciphertext;
size_t ciphertextlen;
};
struct genkey_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const unsigned char *sexp;
size_t sexplen;
};
struct learn_parm_s
{
int error;
ctrl_t ctrl;
assuan_context_t ctx;
membuf_t *data;
};
struct import_key_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
const void *key;
size_t keylen;
};
struct sethash_inq_parm_s
{
assuan_context_t ctx;
const void *data;
size_t datalen;
};
struct default_inq_parm_s
{
ctrl_t ctrl;
assuan_context_t ctx;
};
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx,
const char *servername, int mode)
{
return warn_server_version_mismatch (ctx, servername, mode,
gpgsm_status2, ctrl,
!opt.quiet);
}
/* Try to connect to the agent via socket or fork it off and work by
pipes. Handle the server's initial greeting */
static int
start_agent (ctrl_t ctrl)
{
int rc;
if (agent_ctx)
rc = 0; /* fixme: We need a context for each thread or
serialize the access to the agent (which is
suitable given that the agent is not MT. */
else
{
rc = start_new_gpg_agent (&agent_ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.agent_program,
opt.lc_ctype, opt.lc_messages,
opt.session_env,
opt.autostart?ASSHELP_FLAG_AUTOSTART:0,
opt.verbose, DBG_IPC,
gpgsm_status2, ctrl);
if (!opt.autostart && gpg_err_code (rc) == GPG_ERR_NO_AGENT)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no gpg-agent running in this session\n"));
}
}
else if (!rc && !(rc = warn_version_mismatch (ctrl, agent_ctx,
GPG_AGENT_NAME, 0)))
{
/* Tell the agent that we support Pinentry notifications. No
error checking so that it will work also with older
agents. */
assuan_transact (agent_ctx, "OPTION allow-pinentry-notify",
NULL, NULL, NULL, NULL, NULL, NULL);
/* Pass on the pinentry mode. */
if (opt.pinentry_mode)
{
char *tmp = xasprintf ("OPTION pinentry-mode=%s",
str_pinentry_mode (opt.pinentry_mode));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
log_error ("setting pinentry mode '%s' failed: %s\n",
str_pinentry_mode (opt.pinentry_mode),
gpg_strerror (rc));
}
/* Pass on the request origin. */
if (opt.request_origin)
{
char *tmp = xasprintf ("OPTION pretend-request-origin=%s",
str_request_origin (opt.request_origin));
rc = assuan_transact (agent_ctx, tmp,
NULL, NULL, NULL, NULL, NULL, NULL);
xfree (tmp);
if (rc)
log_error ("setting request origin '%s' failed: %s\n",
str_request_origin (opt.request_origin),
gpg_strerror (rc));
}
/* In DE_VS mode under Windows we require that the JENT RNG
* is active. */
#ifdef HAVE_W32_SYSTEM
if (!rc && opt.compliance == CO_DE_VS)
{
if (assuan_transact (agent_ctx, "GETINFO jent_active",
NULL, NULL, NULL, NULL, NULL, NULL))
{
rc = gpg_error (GPG_ERR_FORBIDDEN);
log_error (_("%s is not compliant with %s mode\n"),
GPG_AGENT_NAME,
gnupg_compliance_option_string (opt.compliance));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"random-compliance", rc);
}
}
#endif /*HAVE_W32_SYSTEM*/
}
}
if (!ctrl->agent_seen)
{
ctrl->agent_seen = 1;
audit_log_ok (ctrl->audit, AUDIT_AGENT_READY, rc);
}
return rc;
}
/* This is the default inquiry callback. It mainly handles the
Pinentry notifications. */
static gpg_error_t
default_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct default_inq_parm_s *parm = opaque;
ctrl_t ctrl = parm->ctrl;
if (has_leading_keyword (line, "PINENTRY_LAUNCHED"))
{
err = gpgsm_proxy_pinentry_notify (ctrl, line);
if (err)
log_error (_("failed to proxy %s inquiry to client\n"),
"PINENTRY_LAUNCHED");
/* We do not pass errors to avoid breaking other code. */
}
else if ((has_leading_keyword (line, "PASSPHRASE")
|| has_leading_keyword (line, "NEW_PASSPHRASE"))
&& opt.pinentry_mode == PINENTRY_MODE_LOOPBACK
&& have_static_passphrase ())
{
const char *s = get_static_passphrase ();
assuan_begin_confidential (parm->ctx);
err = assuan_send_data (parm->ctx, s, strlen (s));
assuan_end_confidential (parm->ctx);
}
else
log_error ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* This is the inquiry callback required by the SETHASH command. */
static gpg_error_t
sethash_inq_cb (void *opaque, const char *line)
{
gpg_error_t err = 0;
struct sethash_inq_parm_s *parm = opaque;
if (has_leading_keyword (line, "TBSDATA"))
{
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else
log_error ("ignoring gpg-agent inquiry '%s'\n", line);
return err;
}
/* Call the agent to do a sign operation using the key identified by
* the hex string KEYGRIP. If DIGESTALGO is given (DIGEST,DIGESTLEN)
* gives the to be signed hash created using the given algo. If
* DIGESTALGO is not given (i.e. zero) (DIGEST,DIGESTALGO) give the
* entire data to-be-signed. */
int
gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen )
{
int rc, i;
char *p, line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
struct default_inq_parm_s inq_parm;
*r_buf = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (digestalgo && digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
snprintf (line, DIM(line), "SIGKEY %s", keygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
if (!digestalgo)
{
struct sethash_inq_parm_s sethash_inq_parm;
sethash_inq_parm.ctx = agent_ctx;
sethash_inq_parm.data = digest;
sethash_inq_parm.datalen = digestlen;
rc = assuan_transact (agent_ctx, "SETHASH --inquire",
NULL, NULL, sethash_inq_cb, &sethash_inq_parm,
NULL, NULL);
}
else
{
snprintf (line, sizeof line, "SETHASH %d ", digestalgo);
p = line + strlen (line);
for (i=0; i < digestlen ; i++, p += 2 )
sprintf (p, "%02X", digest[i]);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
}
if (rc)
return rc;
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, "PKSIGN",
put_membuf_cb, &data, default_inq_cb, &inq_parm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
*r_buf = get_membuf (&data, r_buflen);
if (!gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL))
{
xfree (*r_buf); *r_buf = NULL;
return gpg_error (GPG_ERR_INV_VALUE);
}
return *r_buf? 0 : out_of_core ();
}
/* Call the scdaemon to do a sign operation using the key identified by
the hex string KEYID. */
int
gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen )
{
int rc, i, pkalgo;
char *p, line[ASSUAN_LINELENGTH];
membuf_t data;
size_t len;
const char *hashopt;
unsigned char *sigbuf;
size_t sigbuflen;
struct default_inq_parm_s inq_parm;
gcry_sexp_t sig;
(void)desc;
*r_buf = NULL;
switch(digestalgo)
{
case GCRY_MD_SHA1: hashopt = "--hash=sha1"; break;
case GCRY_MD_RMD160:hashopt = "--hash=rmd160"; break;
case GCRY_MD_MD5: hashopt = "--hash=md5"; break;
case GCRY_MD_SHA256:hashopt = "--hash=sha256"; break;
case GCRY_MD_SHA512:hashopt = "--hash=sha512"; break;
default:
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (digestlen*2 + 50 > DIM(line))
return gpg_error (GPG_ERR_GENERAL);
/* Get the key type from the scdaemon. */
snprintf (line, DIM(line), "SCD READKEY %s", keyid);
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data, NULL, NULL, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
p = get_membuf (&data, &len);
pkalgo = get_pk_algo_from_canon_sexp (p, len);
xfree (p);
if (!pkalgo)
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
p = stpcpy (line, "SCD SETDATA " );
for (i=0; i < digestlen ; i++, p += 2 )
sprintf (p, "%02X", digest[i]);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
init_membuf (&data, 1024);
snprintf (line, DIM(line), "SCD PKSIGN %s %s", hashopt, keyid);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data, default_inq_cb, &inq_parm,
NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
sigbuf = get_membuf (&data, &sigbuflen);
switch(pkalgo)
{
case GCRY_PK_RSA:
rc = gcry_sexp_build (&sig, NULL, "(sig-val(rsa(s%b)))",
(int)sigbuflen, sigbuf);
break;
case GCRY_PK_ECC:
rc = gcry_sexp_build (&sig, NULL, "(sig-val(ecdsa(r%b)(s%b)))",
(int)sigbuflen/2, sigbuf,
(int)sigbuflen/2, sigbuf + sigbuflen/2);
break;
case GCRY_PK_EDDSA:
rc = gcry_sexp_build (&sig, NULL, "(sig-val(eddsa(r%b)(s%b)))",
(int)sigbuflen/2, sigbuf,
(int)sigbuflen/2, sigbuf + sigbuflen/2);
break;
default:
rc = gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
break;
}
xfree (sigbuf);
if (rc)
return rc;
rc = make_canon_sexp (sig, r_buf, r_buflen);
gcry_sexp_release (sig);
if (rc)
return rc;
log_assert (gcry_sexp_canon_len (*r_buf, *r_buflen, NULL, NULL));
return 0;
}
/* Handle a CIPHERTEXT inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_ciphertext_cb (void *opaque, const char *line)
{
struct cipher_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "CIPHERTEXT"))
{
assuan_begin_confidential (parm->ctx);
rc = assuan_send_data (parm->ctx, parm->ciphertext, parm->ciphertextlen);
assuan_end_confidential (parm->ctx);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
rc = default_inq_cb (&inq_parm, line);
}
return rc;
}
/* Call the agent to do a decrypt operation using the key identified by
the hex string KEYGRIP. */
int
gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
ksba_const_sexp_t ciphertext,
char **r_buf, size_t *r_buflen )
{
int rc;
char line[ASSUAN_LINELENGTH];
membuf_t data;
struct cipher_parm_s cipher_parm;
size_t n, len;
char *p, *buf, *endp;
size_t ciphertextlen;
if (!keygrip || strlen(keygrip) != 40 || !ciphertext || !r_buf || !r_buflen)
return gpg_error (GPG_ERR_INV_VALUE);
*r_buf = NULL;
ciphertextlen = gcry_sexp_canon_len (ciphertext, 0, NULL, NULL);
if (!ciphertextlen)
return gpg_error (GPG_ERR_INV_VALUE);
rc = start_agent (ctrl);
if (rc)
return rc;
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
log_assert ( DIM(line) >= 50 );
snprintf (line, DIM(line), "SETKEY %s", keygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
init_membuf (&data, 1024);
cipher_parm.ctrl = ctrl;
cipher_parm.ctx = agent_ctx;
cipher_parm.ciphertext = ciphertext;
cipher_parm.ciphertextlen = ciphertextlen;
rc = assuan_transact (agent_ctx, "PKDECRYPT",
put_membuf_cb, &data,
inq_ciphertext_cb, &cipher_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
/* Make sure it is 0 terminated so we can invoke strtoul safely. */
put_membuf (&data, "", 1);
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
log_assert (len); /* (we forced Nul termination.) */
if (*buf == '(')
{
if (len < 13 || memcmp (buf, "(5:value", 8) ) /* "(5:valueN:D)\0" */
return gpg_error (GPG_ERR_INV_SEXP);
/* Trim any spurious trailing Nuls: */
while (buf[len-1] == 0)
len--;
if (buf[len-1] != ')')
return gpg_error (GPG_ERR_INV_SEXP);
len--; /* Drop the final close-paren: */
p = buf + 8; /* Skip leading parenthesis and the value tag. */
len -= 8; /* Count only the data of the second part. */
}
else
{
/* For compatibility with older gpg-agents handle the old style
incomplete S-exps. */
len--; /* Do not count the Nul. */
p = buf;
}
n = strtoul (p, &endp, 10);
if (!n || *endp != ':')
return gpg_error (GPG_ERR_INV_SEXP);
endp++;
if (endp-p+n != len)
return gpg_error (GPG_ERR_INV_SEXP); /* Oops: Inconsistent S-Exp. */
memmove (buf, endp, n);
*r_buflen = n;
*r_buf = buf;
return 0;
}
/* Handle a KEYPARMS inquiry. Note, we only send the data,
assuan_transact takes care of flushing and writing the end */
static gpg_error_t
inq_genkey_parms (void *opaque, const char *line)
{
struct genkey_parm_s *parm = opaque;
int rc;
if (has_leading_keyword (line, "KEYPARAM"))
{
rc = assuan_send_data (parm->ctx, parm->sexp, parm->sexplen);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
rc = default_inq_cb (&inq_parm, line);
}
return rc;
}
/* Call the agent to generate a new key */
int
gpgsm_agent_genkey (ctrl_t ctrl,
ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey)
{
int rc;
struct genkey_parm_s gk_parm;
membuf_t data;
size_t len;
unsigned char *buf;
gnupg_isotime_t timebuf;
char line[ASSUAN_LINELENGTH];
*r_pubkey = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
rc = assuan_transact (agent_ctx, "RESET", NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
init_membuf (&data, 1024);
gk_parm.ctrl = ctrl;
gk_parm.ctx = agent_ctx;
gk_parm.sexp = keyparms;
gk_parm.sexplen = gcry_sexp_canon_len (keyparms, 0, NULL, NULL);
if (!gk_parm.sexplen)
return gpg_error (GPG_ERR_INV_VALUE);
gnupg_get_isotime (timebuf);
snprintf (line, sizeof line, "GENKEY --timestamp=%s", timebuf);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
inq_genkey_parms, &gk_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Call the agent to read the public key part for a given keygrip. If
FROMCARD is true, the key is directly read from the current
smartcard. In this case HEXKEYGRIP should be the keyID
(e.g. OPENPGP.3). */
int
gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
ksba_sexp_t *r_pubkey)
{
int rc;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_pubkey = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "RESET",NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
snprintf (line, DIM(line), "%sREADKEY %s",
fromcard? "SCD ":"", hexkeygrip);
init_membuf (&data, 1024);
rc = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (rc)
{
xfree (get_membuf (&data, &len));
return rc;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error (GPG_ERR_ENOMEM);
if (!gcry_sexp_canon_len (buf, len, NULL, NULL))
{
xfree (buf);
return gpg_error (GPG_ERR_INV_SEXP);
}
*r_pubkey = buf;
return 0;
}
/* Take the serial number from LINE and return it verbatim in a newly
allocated string. We make sure that only hex characters are
returned. */
static char *
store_serialno (const char *line)
{
const char *s;
char *p;
for (s=line; hexdigitp (s); s++)
;
p = xtrymalloc (s + 1 - line);
if (p)
{
memcpy (p, line, s-line);
p[s-line] = 0;
}
return p;
}
/* Callback for the gpgsm_agent_serialno function. */
static gpg_error_t
scd_serialno_status_cb (void *opaque, const char *line)
{
char **r_serialno = opaque;
const char *keyword = line;
int keywordlen;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 8 && !memcmp (keyword, "SERIALNO", keywordlen))
{
xfree (*r_serialno);
*r_serialno = store_serialno (line);
}
return 0;
}
/* Call the agent to read the serial number of the current card. */
int
gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno)
{
int rc;
char *serialno = NULL;
struct default_inq_parm_s inq_parm;
*r_serialno = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "SCD SERIALNO",
NULL, NULL,
default_inq_cb, &inq_parm,
scd_serialno_status_cb, &serialno);
if (!rc && !serialno)
rc = gpg_error (GPG_ERR_INTERNAL);
if (rc)
{
xfree (serialno);
return rc;
}
*r_serialno = serialno;
return 0;
}
/* Callback for the gpgsm_agent_serialno function. */
static gpg_error_t
scd_keypairinfo_status_cb (void *opaque, const char *line)
{
strlist_t *listaddr = opaque;
const char *keyword = line;
int keywordlen;
strlist_t sl;
char *p;
for (keywordlen=0; *line && !spacep (line); line++, keywordlen++)
;
while (spacep (line))
line++;
if (keywordlen == 11 && !memcmp (keyword, "KEYPAIRINFO", keywordlen))
{
sl = append_to_strlist (listaddr, line);
p = sl->d;
/* Make sure that we only have two tokens so that future
* extensions of the format won't change the format expected by
* the caller. */
while (*p && !spacep (p))
p++;
if (*p)
{
while (spacep (p))
p++;
while (*p && !spacep (p))
p++;
if (*p)
{
*p++ = 0;
while (spacep (p))
p++;
while (*p && !spacep (p))
{
switch (*p++)
{
case 'c': sl->flags |= GCRY_PK_USAGE_CERT; break;
case 's': sl->flags |= GCRY_PK_USAGE_SIGN; break;
case 'e': sl->flags |= GCRY_PK_USAGE_ENCR; break;
case 'a': sl->flags |= GCRY_PK_USAGE_AUTH; break;
}
}
}
}
}
return 0;
}
/* Call the agent to read the keypairinfo lines of the current card.
The list is returned as a string made up of the keygrip, a space
and the keyid. The flags of the string carry the usage bits. */
int
gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list)
{
int rc;
strlist_t list = NULL;
struct default_inq_parm_s inq_parm;
*r_list = NULL;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
rc = assuan_transact (agent_ctx, "SCD LEARN --keypairinfo",
NULL, NULL,
default_inq_cb, &inq_parm,
scd_keypairinfo_status_cb, &list);
if (!rc && !list)
rc = gpg_error (GPG_ERR_NO_DATA);
if (rc)
{
free_strlist (list);
return rc;
}
*r_list = list;
return 0;
}
static gpg_error_t
istrusted_status_cb (void *opaque, const char *line)
{
struct rootca_flags_s *flags = opaque;
const char *s;
if ((s = has_leading_keyword (line, "TRUSTLISTFLAG")))
{
line = s;
if (has_leading_keyword (line, "relax"))
flags->relax = 1;
else if (has_leading_keyword (line, "cm"))
flags->chain_model = 1;
else if (has_leading_keyword (line, "qual"))
flags->qualified = 1;
else if (has_leading_keyword (line, "de-vs"))
flags->de_vs = 1;
}
return 0;
}
/* Ask the agent whether the certificate is in the list of trusted
keys. The certificate is either specified by the CERT object or by
the fingerprint HEXFPR. ROOTCA_FLAGS is guaranteed to be cleared
on error. */
int
gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr,
struct rootca_flags_s *rootca_flags)
{
int rc;
char line[ASSUAN_LINELENGTH];
memset (rootca_flags, 0, sizeof *rootca_flags);
if (cert && hexfpr)
return gpg_error (GPG_ERR_INV_ARG);
rc = start_agent (ctrl);
if (rc)
return rc;
if (hexfpr)
{
snprintf (line, DIM(line), "ISTRUSTED %s", hexfpr);
}
else
{
char *fpr;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (!fpr)
{
log_error ("error getting the fingerprint\n");
return gpg_error (GPG_ERR_GENERAL);
}
snprintf (line, DIM(line), "ISTRUSTED %s", fpr);
xfree (fpr);
}
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
istrusted_status_cb, rootca_flags);
if (!rc)
rootca_flags->valid = 1;
return rc;
}
/* Ask the agent to mark CERT as a trusted Root-CA one */
int
gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert)
{
int rc;
char *fpr, *dn, *dnfmt;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (!fpr)
{
log_error ("error getting the fingerprint\n");
return gpg_error (GPG_ERR_GENERAL);
}
dn = ksba_cert_get_issuer (cert, 0);
if (!dn)
{
xfree (fpr);
return gpg_error (GPG_ERR_GENERAL);
}
dnfmt = gpgsm_format_name2 (dn, 0);
xfree (dn);
if (!dnfmt)
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "MARKTRUSTED %s S %s", fpr, dnfmt);
ksba_free (dnfmt);
xfree (fpr);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Ask the agent whether the a corresponding secret key is available
for the given keygrip */
int
gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip)
{
int rc;
char line[ASSUAN_LINELENGTH];
rc = start_agent (ctrl);
if (rc)
return rc;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "HAVEKEY %s", hexkeygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL, NULL, NULL);
return rc;
}
static gpg_error_t
learn_status_cb (void *opaque, const char *line)
{
struct learn_parm_s *parm = opaque;
const char *s;
/* Pass progress data to the caller. */
if ((s = has_leading_keyword (line, "PROGRESS")))
{
line = s;
if (parm->ctrl)
{
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, line))
return gpg_error (GPG_ERR_ASS_CANCELED);
}
}
return 0;
}
static gpg_error_t
learn_cb (void *opaque, const void *buffer, size_t length)
{
struct learn_parm_s *parm = opaque;
size_t len;
char *buf;
ksba_cert_t cert;
int rc;
char *string, *p, *pend;
strlist_t sl;
if (parm->error)
return 0;
if (buffer)
{
put_membuf (parm->data, buffer, length);
return 0;
}
/* END encountered - process what we have */
buf = get_membuf (parm->data, &len);
if (!buf)
{
parm->error = gpg_error (GPG_ERR_ENOMEM);
return 0;
}
if (gpgsm_status (parm->ctrl, STATUS_PROGRESS, "learncard C 0 0"))
return gpg_error (GPG_ERR_ASS_CANCELED);
/* FIXME: this should go into import.c */
rc = ksba_cert_new (&cert);
if (rc)
{
parm->error = rc;
return 0;
}
rc = ksba_cert_init_from_mem (cert, buf, len);
if (rc)
{
log_error ("failed to parse a certificate: %s\n", gpg_strerror (rc));
ksba_cert_release (cert);
parm->error = rc;
return 0;
}
/* Ignore certificates matching certain extended usage flags. */
rc = ksba_cert_get_ext_key_usages (cert, &string);
if (!rc)
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
for (sl=opt.ignore_cert_with_oid;
sl && strcmp (sl->d, p); sl = sl->next)
;
if (sl)
{
if (opt.verbose)
log_info ("certificate ignored due to OID %s\n", sl->d);
goto leave;
}
p = pend;
if ((p = strchr (p, '\n')))
p++;
}
}
else if (gpg_err_code (rc) != GPG_ERR_NO_DATA)
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (rc));
xfree (string);
string = NULL;
- /* We do not store a certifciate with missing issuers as ephemeral
+ /* We do not store a certificate with missing issuers as ephemeral
because we can assume that the --learn-card command has been used
on purpose. */
rc = gpgsm_basic_cert_check (parm->ctrl, cert);
if (rc && gpg_err_code (rc) != GPG_ERR_MISSING_CERT
&& gpg_err_code (rc) != GPG_ERR_MISSING_ISSUER_CERT)
log_error ("invalid certificate: %s\n", gpg_strerror (rc));
else
{
int existed;
if (!keydb_store_cert (parm->ctrl, cert, 0, &existed))
{
if (opt.verbose > 1 && existed)
log_info ("certificate already in DB\n");
else if (opt.verbose && !existed)
log_info ("certificate imported\n");
}
}
leave:
xfree (string);
string = NULL;
ksba_cert_release (cert);
init_membuf (parm->data, 4096);
return 0;
}
/* Call the agent to learn about a smartcard */
int
gpgsm_agent_learn (ctrl_t ctrl)
{
int rc;
struct learn_parm_s learn_parm;
membuf_t data;
size_t len;
rc = start_agent (ctrl);
if (rc)
return rc;
rc = warn_version_mismatch (ctrl, agent_ctx, SCDAEMON_NAME, 2);
if (rc)
return rc;
init_membuf (&data, 4096);
learn_parm.error = 0;
learn_parm.ctrl = ctrl;
learn_parm.ctx = agent_ctx;
learn_parm.data = &data;
rc = assuan_transact (agent_ctx, "LEARN --send",
learn_cb, &learn_parm,
NULL, NULL,
learn_status_cb, &learn_parm);
xfree (get_membuf (&data, &len));
if (rc)
return rc;
return learn_parm.error;
}
/* Ask the agent to change the passphrase of the key identified by
HEXKEYGRIP. If DESC is not NULL, display instead of the default
description message. */
int
gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
rc = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (rc)
return rc;
}
snprintf (line, DIM(line), "PASSWD %s", hexkeygrip);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Ask the agent to pop up a confirmation dialog with the text DESC
and an okay and cancel button. */
gpg_error_t
gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc)
{
int rc;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
rc = start_agent (ctrl);
if (rc)
return rc;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
snprintf (line, DIM(line), "GET_CONFIRMATION %s", desc);
rc = assuan_transact (agent_ctx, line, NULL, NULL,
default_inq_cb, &inq_parm, NULL, NULL);
return rc;
}
/* Return 0 if the agent is alive. This is useful to make sure that
an agent has been started. */
gpg_error_t
gpgsm_agent_send_nop (ctrl_t ctrl)
{
int rc;
rc = start_agent (ctrl);
if (!rc)
rc = assuan_transact (agent_ctx, "NOP",
NULL, NULL, NULL, NULL, NULL, NULL);
return rc;
}
static gpg_error_t
keyinfo_status_cb (void *opaque, const char *line)
{
char **serialno = opaque;
const char *s, *s2;
if ((s = has_leading_keyword (line, "KEYINFO")) && !*serialno)
{
s = strchr (s, ' ');
if (s && s[1] == 'T' && s[2] == ' ' && s[3])
{
s += 3;
s2 = strchr (s, ' ');
if ( s2 > s )
{
*serialno = xtrymalloc ((s2 - s)+1);
if (*serialno)
{
memcpy (*serialno, s, s2 - s);
(*serialno)[s2 - s] = 0;
}
}
}
}
return 0;
}
/* Return the serial number for a secret key. If the returned serial
number is NULL, the key is not stored on a smartcard. Caller needs
to free R_SERIALNO. */
gpg_error_t
gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip, char **r_serialno)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *serialno = NULL;
*r_serialno = NULL;
err = start_agent (ctrl);
if (err)
return err;
if (!hexkeygrip || strlen (hexkeygrip) != 40)
return gpg_error (GPG_ERR_INV_VALUE);
snprintf (line, DIM(line), "KEYINFO %s", hexkeygrip);
err = assuan_transact (agent_ctx, line, NULL, NULL, NULL, NULL,
keyinfo_status_cb, &serialno);
if (!err && serialno)
{
/* Sanity check for bad characters. */
if (strpbrk (serialno, ":\n\r"))
err = GPG_ERR_INV_VALUE;
}
if (err)
xfree (serialno);
else
*r_serialno = serialno;
return err;
}
/* Ask for the passphrase (this is used for pkcs#12 import/export. On
success the caller needs to free the string stored at R_PASSPHRASE.
On error NULL will be stored at R_PASSPHRASE and an appropriate
error code returned. If REPEAT is true the agent tries to get a
new passphrase (i.e. asks the user to confirm it). */
gpg_error_t
gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg, int repeat,
char **r_passphrase)
{
gpg_error_t err;
char line[ASSUAN_LINELENGTH];
char *arg4 = NULL;
membuf_t data;
struct default_inq_parm_s inq_parm;
int wasconf;
*r_passphrase = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (desc_msg && *desc_msg && !(arg4 = percent_plus_escape (desc_msg)))
return gpg_error_from_syserror ();
snprintf (line, DIM(line), "GET_PASSPHRASE --data%s -- X X X %s",
repeat? " --repeat=1 --check":"",
arg4);
xfree (arg4);
init_membuf_secure (&data, 64);
wasconf = assuan_get_flag (agent_ctx, ASSUAN_CONFIDENTIAL);
assuan_begin_confidential (agent_ctx);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (!wasconf)
assuan_end_confidential (agent_ctx);
if (err)
xfree (get_membuf (&data, NULL));
else
{
put_membuf (&data, "", 1);
*r_passphrase = get_membuf (&data, NULL);
if (!*r_passphrase)
err = gpg_error_from_syserror ();
}
return err;
}
/* Retrieve a key encryption key from the agent. With FOREXPORT true
the key shall be use for export, with false for import. On success
the new key is stored at R_KEY and its length at R_KEKLEN. */
gpg_error_t
gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_kek = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
snprintf (line, DIM(line), "KEYWRAP_KEY %s",
forexport? "--export":"--import");
init_membuf_secure (&data, 64);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_kek = buf;
*r_keklen = len;
return 0;
}
/* Handle the inquiry for an IMPORT_KEY command. */
static gpg_error_t
inq_import_key_parms (void *opaque, const char *line)
{
struct import_key_parm_s *parm = opaque;
gpg_error_t err;
if (has_leading_keyword (line, "KEYDATA"))
{
assuan_begin_confidential (parm->ctx);
err = assuan_send_data (parm->ctx, parm->key, parm->keylen);
assuan_end_confidential (parm->ctx);
}
else
{
struct default_inq_parm_s inq_parm = { parm->ctrl, parm->ctx };
err = default_inq_cb (&inq_parm, line);
}
return err;
}
/* Call the agent to import a key into the agent. */
gpg_error_t
gpgsm_agent_import_key (ctrl_t ctrl, const void *key, size_t keylen)
{
gpg_error_t err;
struct import_key_parm_s parm;
gnupg_isotime_t timebuf;
char line[ASSUAN_LINELENGTH];
err = start_agent (ctrl);
if (err)
return err;
parm.ctrl = ctrl;
parm.ctx = agent_ctx;
parm.key = key;
parm.keylen = keylen;
gnupg_get_isotime (timebuf);
snprintf (line, sizeof line, "IMPORT_KEY --timestamp=%s", timebuf);
err = assuan_transact (agent_ctx, line,
NULL, NULL, inq_import_key_parms, &parm, NULL, NULL);
return err;
}
/* Receive a secret key from the agent. KEYGRIP is the hexified
keygrip, DESC a prompt to be displayed with the agent's passphrase
question (needs to be plus+percent escaped). On success the key is
stored as a canonical S-expression at R_RESULT and R_RESULTLEN. */
gpg_error_t
gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char **r_result, size_t *r_resultlen)
{
gpg_error_t err;
membuf_t data;
size_t len;
unsigned char *buf;
char line[ASSUAN_LINELENGTH];
struct default_inq_parm_s inq_parm;
*r_result = NULL;
err = start_agent (ctrl);
if (err)
return err;
inq_parm.ctrl = ctrl;
inq_parm.ctx = agent_ctx;
if (desc)
{
snprintf (line, DIM(line), "SETKEYDESC %s", desc);
err = assuan_transact (agent_ctx, line,
NULL, NULL, NULL, NULL, NULL, NULL);
if (err)
return err;
}
snprintf (line, DIM(line), "EXPORT_KEY %s", keygrip);
init_membuf_secure (&data, 1024);
err = assuan_transact (agent_ctx, line,
put_membuf_cb, &data,
default_inq_cb, &inq_parm, NULL, NULL);
if (err)
{
xfree (get_membuf (&data, &len));
return err;
}
buf = get_membuf (&data, &len);
if (!buf)
return gpg_error_from_syserror ();
*r_result = buf;
*r_resultlen = len;
return 0;
}
diff --git a/sm/certchain.c b/sm/certchain.c
index 9d0fe684b..539280ed1 100644
--- a/sm/certchain.c
+++ b/sm/certchain.c
@@ -1,2459 +1,2459 @@
/* certchain.c - certificate chain validation
* Copyright (C) 2001, 2002, 2003, 2004, 2005,
* 2006, 2007, 2008, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include <stdarg.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
#include "../common/i18n.h"
#include "../common/tlv.h"
/* The OID for the authorityInfoAccess's caIssuers. */
static const char oidstr_caIssuers[] = "1.3.6.1.5.5.7.48.2";
/* Object to keep track of certain root certificates. */
struct marktrusted_info_s
{
struct marktrusted_info_s *next;
unsigned char fpr[20];
};
static struct marktrusted_info_s *marktrusted_info;
/* While running the validation function we want to keep track of the
certificates in the chain. This type is used for that. */
struct chain_item_s
{
struct chain_item_s *next;
ksba_cert_t cert; /* The certificate. */
int is_root; /* The certificate is the root certificate. */
};
typedef struct chain_item_s *chain_item_t;
static int is_root_cert (ksba_cert_t cert,
const char *issuerdn, const char *subjectdn);
static int get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen);
/* This function returns true if we already asked during this session
whether the root certificate CERT shall be marked as trusted. */
static int
already_asked_marktrusted (ksba_cert_t cert)
{
unsigned char fpr[20];
struct marktrusted_info_s *r;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
/* No context switches in the loop! */
for (r=marktrusted_info; r; r= r->next)
if (!memcmp (r->fpr, fpr, 20))
return 1;
return 0;
}
/* Flag certificate CERT as already asked whether it shall be marked
as trusted. */
static void
set_already_asked_marktrusted (ksba_cert_t cert)
{
unsigned char fpr[20];
struct marktrusted_info_s *r;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, fpr, NULL);
for (r=marktrusted_info; r; r= r->next)
if (!memcmp (r->fpr, fpr, 20))
return; /* Already marked. */
r = xtrycalloc (1, sizeof *r);
if (!r)
return;
memcpy (r->fpr, fpr, 20);
r->next = marktrusted_info;
marktrusted_info = r;
}
/* If LISTMODE is true, print FORMAT using LISTMODE to FP. If
LISTMODE is false, use the string to print an log_info or, if
IS_ERROR is true, and log_error. */
static void
do_list (int is_error, int listmode, estream_t fp, const char *format, ...)
{
va_list arg_ptr;
va_start (arg_ptr, format) ;
if (listmode)
{
if (fp)
{
es_fputs (" [", fp);
es_vfprintf (fp, format, arg_ptr);
es_fputs ("]\n", fp);
}
}
else
{
es_fflush (es_stdout);
log_logv (is_error? GPGRT_LOGLVL_ERROR: GPGRT_LOGLVL_INFO,
format, arg_ptr);
log_printf ("\n");
}
va_end (arg_ptr);
}
/* Return 0 if A and B are equal. */
static int
compare_certs (ksba_cert_t a, ksba_cert_t b)
{
const unsigned char *img_a, *img_b;
size_t len_a, len_b;
img_a = ksba_cert_get_image (a, &len_a);
if (!img_a)
return 1;
img_b = ksba_cert_get_image (b, &len_b);
if (!img_b)
return 1;
return !(len_a == len_b && !memcmp (img_a, img_b, len_a));
}
/* Return true if CERT has the validityModel extensions and defines
the use of the chain model. */
static int
has_validation_model_chain (ksba_cert_t cert, int listmode, estream_t listfp)
{
gpg_error_t err;
int idx, yes;
const char *oid;
size_t off, derlen, objlen, hdrlen;
const unsigned char *der;
int class, tag, constructed, ndef;
char *oidbuf;
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, NULL, &off, &derlen));idx++)
if (!strcmp (oid, "1.3.6.1.4.1.8301.3.5") )
break;
if (err)
return 0; /* Not found. */
der = ksba_cert_get_image (cert, NULL);
if (!der)
{
err = gpg_error (GPG_ERR_INV_OBJ); /* Oops */
goto leave;
}
der += off;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_SEQUENCE))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
derlen = objlen;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_OBJECT_ID))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
goto leave;
oidbuf = ksba_oid_to_str (der, objlen);
if (!oidbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (opt.verbose)
do_list (0, listmode, listfp,
_("validation model requested by certificate: %s"),
!strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1")? _("chain") :
!strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.2")? _("shell") :
/* */ oidbuf);
yes = !strcmp (oidbuf, "1.3.6.1.4.1.8301.3.5.1");
ksba_free (oidbuf);
return yes;
leave:
log_error ("error parsing validityModel: %s\n", gpg_strerror (err));
return 0;
}
static int
unknown_criticals (ksba_cert_t cert, int listmode, estream_t fp)
{
static const char *known[] = {
"2.5.29.15", /* keyUsage */
"2.5.29.17", /* subjectAltName
Japanese DoCoMo certs mark them as critical. PKIX
only requires them as critical if subjectName is
empty. I don't know whether our code gracefully
handles such empry subjectNames but that is
another story. */
"2.5.29.19", /* basic Constraints */
"2.5.29.32", /* certificatePolicies */
"2.5.29.37", /* extendedKeyUsage - handled by certlist.c */
"1.3.6.1.4.1.8301.3.5", /* validityModel - handled here. */
NULL
};
int rc = 0, i, idx, crit;
const char *oid;
gpg_error_t err;
int unsupported;
strlist_t sl;
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, &crit, NULL, NULL));idx++)
{
if (!crit)
continue;
for (i=0; known[i] && strcmp (known[i],oid); i++)
;
unsupported = !known[i];
/* If this critical extension is not supported. Check the list
of to be ignored extensions to see whether we claim that it
is supported. */
if (unsupported && opt.ignored_cert_extensions)
{
for (sl=opt.ignored_cert_extensions;
sl && strcmp (sl->d, oid); sl = sl->next)
;
if (sl)
unsupported = 0;
}
if (unsupported)
{
do_list (1, listmode, fp,
_("critical certificate extension %s is not supported"),
oid);
rc = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
}
}
/* We ignore the error codes EOF as well as no-value. The later will
occur for certificates with no extensions at all. */
if (err
&& gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
rc = err;
return rc;
}
/* Check whether CERT is an allowed certificate. This requires that
CERT matches all requirements for such a CA, i.e. the
BasicConstraints extension. The function returns 0 on success and
the allowed length of the chain at CHAINLEN. */
static int
allowed_ca (ctrl_t ctrl,
ksba_cert_t cert, int *chainlen, int listmode, estream_t fp)
{
gpg_error_t err;
int flag;
err = ksba_cert_is_ca (cert, &flag, chainlen);
if (err)
return err;
if (!flag)
{
if (get_regtp_ca_info (ctrl, cert, chainlen))
{
/* Note that dirmngr takes a different way to cope with such
certs. */
return 0; /* RegTP issued certificate. */
}
do_list (1, listmode, fp,_("issuer certificate is not marked as a CA"));
return gpg_error (GPG_ERR_BAD_CA_CERT);
}
return 0;
}
static int
check_cert_policy (ksba_cert_t cert, int listmode, estream_t fplist)
{
static int no_policy_file;
gpg_error_t err;
char *policies;
estream_t fp;
int any_critical;
err = ksba_cert_get_cert_policies (cert, &policies);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 0; /* No policy given. */
if (err)
return err;
/* STRING is a line delimited list of certificate policies as stored
in the certificate. The line itself is colon delimited where the
first field is the OID of the policy and the second field either
N or C for normal or critical extension */
if (opt.verbose > 1 && !listmode)
log_info ("certificate's policy list: %s\n", policies);
/* The check is very minimal but won't give false positives */
any_critical = !!strstr (policies, ":C");
if (!opt.policy_file)
{
xfree (policies);
if (any_critical)
{
do_list (1, listmode, fplist,
_("critical marked policy without configured policies"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
return 0;
}
if (no_policy_file)
{
/* Avoid trying to open the policy file if we already know that
* it does not exist. */
fp = NULL;
gpg_err_set_errno (ENOENT);
}
else
fp = es_fopen (opt.policy_file, "r");
if (!fp)
{
if ((opt.verbose || errno != ENOENT) && !no_policy_file)
log_info (_("failed to open '%s': %s\n"),
opt.policy_file, strerror (errno));
if (errno == ENOENT)
no_policy_file = 1;
xfree (policies);
/* With no critical policies this is only a warning */
if (!any_critical)
{
if (opt.verbose)
do_list (0, listmode, fplist,
_("Note: non-critical certificate policy not allowed"));
return 0;
}
do_list (1, listmode, fplist,
_("certificate policy not allowed"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
/* FIXME: Cache the policy file content. */
for (;;)
{
int c;
char *p, line[256];
char *haystack, *allowed;
/* read line */
do
{
if (!es_fgets (line, DIM(line)-1, fp) )
{
gpg_error_t tmperr = gpg_error_from_syserror ();
xfree (policies);
if (es_feof (fp))
{
es_fclose (fp);
/* With no critical policies this is only a warning */
if (!any_critical)
{
if (opt.verbose)
do_list (0, listmode, fplist,
_("Note: non-critical certificate policy not allowed"));
return 0;
}
do_list (1, listmode, fplist,
_("certificate policy not allowed"));
return gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
es_fclose (fp);
return tmperr;
}
if (!*line || line[strlen(line)-1] != '\n')
{
/* eat until end of line */
while ((c = es_getc (fp)) != EOF && c != '\n')
;
es_fclose (fp);
xfree (policies);
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 == '#');
/* Parse line. Note that the line has always a LF and spacep
does not consider a LF a space. Thus strpbrk will always
succeed. */
for (allowed=line; spacep (allowed); allowed++)
;
p = strpbrk (allowed, " :\n");
if (!*p || p == allowed)
{
es_fclose (fp);
xfree (policies);
return gpg_error (GPG_ERR_CONFIGURATION);
}
*p = 0; /* strip the rest of the line */
/* See whether we find ALLOWED (which is an OID) in POLICIES */
for (haystack=policies; (p=strstr (haystack, allowed)); haystack = p+1)
{
if ( !(p == policies || p[-1] == '\n') )
continue; /* Does not match the begin of a line. */
if (p[strlen (allowed)] != ':')
continue; /* The length does not match. */
/* Yep - it does match so return okay. */
es_fclose (fp);
xfree (policies);
return 0;
}
}
}
/* Helper function for find_up. This resets the key handle and search
for an issuer ISSUER with a subjectKeyIdentifier of KEYID. Returns
0 on success or GPG_ERR_NOT_FOUND when not found. */
static int
find_up_search_by_keyid (ctrl_t ctrl, KEYDB_HANDLE kh,
const char *issuer, ksba_sexp_t keyid)
{
int rc;
ksba_cert_t cert = NULL;
ksba_sexp_t subj = NULL;
ksba_isotime_t not_before, not_after, last_not_before, ne_last_not_before;
ksba_cert_t found_cert = NULL;
ksba_cert_t ne_found_cert = NULL;
keydb_search_reset (kh);
while (!(rc = keydb_search_subject (ctrl, kh, issuer)))
{
ksba_cert_release (cert); cert = NULL;
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_error ("keydb_get_cert failed in %s: %s <%s>\n",
__func__, gpg_strerror (rc), gpg_strsource (rc));
rc = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
xfree (subj);
if (!ksba_cert_get_subj_key_id (cert, NULL, &subj))
{
if (!cmp_simple_canon_sexp (keyid, subj))
{
/* Found matching cert. */
rc = ksba_cert_get_validity (cert, 0, not_before);
if (!rc)
rc = ksba_cert_get_validity (cert, 1, not_after);
if (rc)
{
log_error ("keydb_get_validity() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
if (!found_cert
|| strcmp (last_not_before, not_before) < 0)
{
/* This certificate is the first one found or newer
* than the previous one. This copes with
* re-issuing CA certificates while keeping the same
* key information. */
gnupg_copy_time (last_not_before, not_before);
ksba_cert_release (found_cert);
ksba_cert_ref ((found_cert = cert));
keydb_push_found_state (kh);
}
if (*not_after && strcmp (ctrl->current_time, not_after) > 0 )
; /* CERT has expired - don't consider it. */
else if (!ne_found_cert
|| strcmp (ne_last_not_before, not_before) < 0)
{
/* This certificate is the first non-expired one
* found or newer than the previous non-expired one. */
gnupg_copy_time (ne_last_not_before, not_before);
ksba_cert_release (ne_found_cert);
ksba_cert_ref ((ne_found_cert = cert));
}
}
}
}
if (!found_cert)
goto leave;
/* Take the last saved one. Note that push/pop_found_state are
* misnomers because there is no stack of states. Renaming them to
* save/restore_found_state would be better. */
keydb_pop_found_state (kh);
rc = 0; /* Ignore EOF or other error after the first cert. */
/* We need to consider some corner cases. It is possible that we
* have a long term certificate (e.g. valid from 2008 to 2033) as
* well as a re-issued (i.e. using the same key material) short term
* certificate (say from 2016 to 2019). Using the short term
* certificate is the proper solution. But we need to take care if
* there is no re-issued new short term certificate (e.g. from 2020
* to 2023) available. In that case it is better to use the long
* term certificate which is still valid. The code may run into
* minor problems in the case of the chain validation mode. Given
* that this corner case is due to non-diligent PKI management we
* ignore this problem. */
/* The most common case is that the found certificate is not expired
* and thus identical to the one found from the list of non-expired
* certs. We can stop here. */
if (found_cert == ne_found_cert)
goto leave;
/* If we do not have a non expired certificate the actual cert is
* expired and we can also stop here. */
if (!ne_found_cert)
goto leave;
/* Now we need to see whether the found certificate is expired and
* only in this case we return the certificate found in the list of
* non-expired certs. */
rc = ksba_cert_get_validity (found_cert, 1, not_after);
if (rc)
{
log_error ("keydb_get_validity() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
if (*not_after && strcmp (ctrl->current_time, not_after) > 0 )
{ /* CERT has expired. Use the NE_FOUND_CERT. Because we have no
* found state for this we need to search for it again. */
unsigned char fpr[20];
gpgsm_get_fingerprint (ne_found_cert, GCRY_MD_SHA1, fpr, NULL);
keydb_search_reset (kh);
rc = keydb_search_fpr (ctrl, kh, fpr);
if (rc)
{
log_error ("keydb_search_fpr() failed: rc=%d\n", rc);
rc = gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
/* Ready. The NE_FOUND_CERT is available via keydb_get_cert. */
}
leave:
ksba_cert_release (found_cert);
ksba_cert_release (ne_found_cert);
ksba_cert_release (cert);
xfree (subj);
return rc? gpg_error (GPG_ERR_NOT_FOUND) : 0;
}
struct find_up_store_certs_s
{
ctrl_t ctrl;
int count;
unsigned int want_fpr:1;
unsigned int got_fpr:1;
unsigned char fpr[20];
};
static void
find_up_store_certs_cb (void *cb_value, ksba_cert_t cert)
{
struct find_up_store_certs_s *parm = cb_value;
if (keydb_store_cert (parm->ctrl, cert, 1, NULL))
log_error ("error storing issuer certificate as ephemeral\n");
else if (parm->want_fpr && !parm->got_fpr)
{
if (!gpgsm_get_fingerprint (cert, 0, parm->fpr, NULL))
log_error (_("failed to get the fingerprint\n"));
else
parm->got_fpr = 1;
}
parm->count++;
}
/* Helper for find_up(). Locate the certificate for ISSUER using an
external lookup. KH is the keydb context we are currently using.
On success 0 is returned and the certificate may be retrieved from
the keydb using keydb_get_cert(). KEYID is the keyIdentifier from
the AKI or NULL. */
static int
find_up_external (ctrl_t ctrl, KEYDB_HANDLE kh,
const char *issuer, ksba_sexp_t keyid)
{
int rc;
strlist_t names = NULL;
struct find_up_store_certs_s find_up_store_certs_parm;
char *pattern;
const char *s;
find_up_store_certs_parm.ctrl = ctrl;
find_up_store_certs_parm.want_fpr = 0;
find_up_store_certs_parm.got_fpr = 0;
find_up_store_certs_parm.count = 0;
if (opt.verbose)
log_info (_("looking up issuer at external location\n"));
/* The Dirmngr process is confused about unknown attributes. As a
quick and ugly hack we locate the CN and use the issuer string
starting at this attribite. Fixme: we should have far better
parsing for external lookups in the Dirmngr. */
s = strstr (issuer, "CN=");
if (!s || s == issuer || s[-1] != ',')
s = issuer;
pattern = xtrymalloc (strlen (s)+2);
if (!pattern)
return gpg_error_from_syserror ();
strcpy (stpcpy (pattern, "/"), s);
add_to_strlist (&names, pattern);
xfree (pattern);
rc = gpgsm_dirmngr_lookup (ctrl, names, NULL, 0, find_up_store_certs_cb,
&find_up_store_certs_parm);
free_strlist (names);
if (opt.verbose)
log_info (_("number of issuers matching: %d\n"),
find_up_store_certs_parm.count);
if (rc)
{
log_error ("external key lookup failed: %s\n", gpg_strerror (rc));
rc = gpg_error (GPG_ERR_NOT_FOUND);
}
else if (!find_up_store_certs_parm.count)
rc = gpg_err_code (rc) == GPG_ERR_NOT_FOUND;
else
{
int old;
/* The issuers are currently stored in the ephemeral key DB, so
we temporary switch to ephemeral mode. */
old = keydb_set_ephemeral (kh, 1);
if (keyid)
rc = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
else
{
keydb_search_reset (kh);
rc = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
}
return rc;
}
/* Helper for find_up(). Locate the certificate for CERT using the
* caIssuer from the authorityInfoAccess. KH is the keydb context we
* are currently using. On success 0 is returned and the certificate
* may be retrieved from the keydb using keydb_get_cert(). If no
* suitable authorityInfoAccess is encoded in the certificate
* GPG_ERR_NOT_FOUND is returned. */
static gpg_error_t
find_up_via_auth_info_access (ctrl_t ctrl, KEYDB_HANDLE kh, ksba_cert_t cert)
{
gpg_error_t err;
struct find_up_store_certs_s find_up_store_certs_parm;
char *url, *ldapurl;
int idx, i;
char *oid;
ksba_name_t name;
find_up_store_certs_parm.ctrl = ctrl;
find_up_store_certs_parm.want_fpr = 1;
find_up_store_certs_parm.got_fpr = 0;
find_up_store_certs_parm.count = 0;
/* Find suitable URLs; if there is a http scheme we prefer that. */
url = ldapurl = NULL;
for (idx=0;
!url && !(err = ksba_cert_get_authority_info_access (cert, idx,
&oid, &name));
idx++)
{
if (!strcmp (oid, oidstr_caIssuers))
{
for (i=0; !url && ksba_name_enum (name, i); i++)
{
char *p = ksba_name_get_uri (name, i);
if (p)
{
if (!strncmp (p, "http:", 5) || !strncmp (p, "https:", 6))
url = p;
else if (ldapurl)
xfree (p); /* We already got one. */
else if (!strncmp (p, "ldap:",5) || !strncmp (p, "ldaps:",6))
ldapurl = p;
}
else
xfree (p);
}
}
ksba_name_release (name);
ksba_free (oid);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
log_error (_("can't get authorityInfoAccess: %s\n"), gpg_strerror (err));
return err;
}
if (!url && ldapurl)
{
/* No HTTP scheme; fallback to LDAP if available. */
url = ldapurl;
ldapurl = NULL;
}
xfree (ldapurl);
if (!url)
return gpg_error (GPG_ERR_NOT_FOUND);
if (opt.verbose)
log_info ("looking up issuer via authorityInfoAccess.caIssuers\n");
err = gpgsm_dirmngr_lookup (ctrl, NULL, url, 0, find_up_store_certs_cb,
&find_up_store_certs_parm);
/* Although we might receive several certificates we use only the
- * first one. Or more exacty the first one for which we retrieved
+ * first one. Or more exactly the first one for which we retrieved
* the fingerprint. */
if (opt.verbose)
log_info ("number of caIssuers found: %d\n",
find_up_store_certs_parm.count);
if (err)
{
log_error ("external URL lookup failed: %s\n", gpg_strerror (err));
err = gpg_error (GPG_ERR_NOT_FOUND);
}
else if (!find_up_store_certs_parm.got_fpr)
err = gpg_error (GPG_ERR_NOT_FOUND);
else
{
int old;
/* The retrieved certificates are currently stored in the
* ephemeral key DB, so we temporary switch to ephemeral
* mode. */
old = keydb_set_ephemeral (kh, 1);
keydb_search_reset (kh);
err = keydb_search_fpr (ctrl, kh, find_up_store_certs_parm.fpr);
keydb_set_ephemeral (kh, old);
}
return err;
}
/* Helper for find_up(). Ask the dirmngr for the certificate for
ISSUER with optional SERIALNO. KH is the keydb context we are
currently using. With SUBJECT_MODE set, ISSUER is searched as the
subject. On success 0 is returned and the certificate is available
in the ephemeral DB. */
static int
find_up_dirmngr (ctrl_t ctrl, KEYDB_HANDLE kh,
ksba_sexp_t serialno, const char *issuer, int subject_mode)
{
int rc;
strlist_t names = NULL;
struct find_up_store_certs_s find_up_store_certs_parm;
char *pattern;
(void)kh;
find_up_store_certs_parm.ctrl = ctrl;
find_up_store_certs_parm.count = 0;
if (opt.verbose)
log_info (_("looking up issuer from the Dirmngr cache\n"));
if (subject_mode)
{
pattern = xtrymalloc (strlen (issuer)+2);
if (pattern)
strcpy (stpcpy (pattern, "/"), issuer);
}
else if (serialno)
pattern = gpgsm_format_sn_issuer (serialno, issuer);
else
{
pattern = xtrymalloc (strlen (issuer)+3);
if (pattern)
strcpy (stpcpy (pattern, "#/"), issuer);
}
if (!pattern)
return gpg_error_from_syserror ();
add_to_strlist (&names, pattern);
xfree (pattern);
rc = gpgsm_dirmngr_lookup (ctrl, names, NULL, 1, find_up_store_certs_cb,
&find_up_store_certs_parm);
free_strlist (names);
if (opt.verbose)
log_info (_("number of matching certificates: %d\n"),
find_up_store_certs_parm.count);
if (rc && opt.verbose)
log_info (_("dirmngr cache-only key lookup failed: %s\n"),
gpg_strerror (rc));
return ((!rc && find_up_store_certs_parm.count)
? 0 : gpg_error (GPG_ERR_NOT_FOUND));
}
/* Locate issuing certificate for CERT. ISSUER is the name of the
issuer used as a fallback if the other methods don't work. If
FIND_NEXT is true, the function shall return the next possible
issuer. The certificate itself is not directly returned but a
keydb_get_cert on the keydb context KH will return it. Returns 0
on success, GPG_ERR_NOT_FOUND if not found or another error code. */
static gpg_error_t
find_up (ctrl_t ctrl, KEYDB_HANDLE kh,
ksba_cert_t cert, const char *issuer, int find_next)
{
ksba_name_t authid;
ksba_sexp_t authidno;
ksba_sexp_t keyid;
gpg_error_t err = gpg_error (GPG_ERR_NOT_FOUND);
if (DBG_X509)
log_debug ("looking for parent certificate\n");
if (!ksba_cert_get_auth_key_id (cert, &keyid, &authid, &authidno))
{
const char *s = ksba_name_enum (authid, 0);
if (s && *authidno)
{
err = keydb_search_issuer_sn (ctrl, kh, s, authidno);
if (err)
keydb_search_reset (kh);
if (!err && DBG_X509)
log_debug (" found via authid and sn+issuer\n");
/* In case of an error, try to get the certificate from the
dirmngr. That is done by trying to put that certificate
into the ephemeral DB and let the code below do the
actual retrieve. Thus there is no error checking.
Skipped in find_next mode as usual. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next)
find_up_dirmngr (ctrl, kh, authidno, s, 0);
/* In case of an error try the ephemeral DB. We can't do
that in find_next mode because we can't keep the search
state then. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next)
{
int old = keydb_set_ephemeral (kh, 1);
if (!old)
{
err = keydb_search_issuer_sn (ctrl, kh, s, authidno);
if (err)
keydb_search_reset (kh);
if (!err && DBG_X509)
log_debug (" found via authid and sn+issuer (ephem)\n");
}
keydb_set_ephemeral (kh, old);
}
if (err) /* Need to make sure to have this error code. */
err = gpg_error (GPG_ERR_NOT_FOUND);
}
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && keyid && !find_next)
{
/* Not found by AKI.issuer_sn. Lets try the AKI.ki
instead. Loop over all certificates with that issuer as
subject and stop for the one with a matching
subjectKeyIdentifier. */
/* Fixme: Should we also search in the dirmngr? */
err = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
if (!err && DBG_X509)
log_debug (" found via authid and keyid\n");
if (err)
{
int old = keydb_set_ephemeral (kh, 1);
if (!old)
err = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
if (!err && DBG_X509)
log_debug (" found via authid and keyid (ephem)\n");
keydb_set_ephemeral (kh, old);
}
if (err) /* Need to make sure to have this error code. */
err = gpg_error (GPG_ERR_NOT_FOUND);
}
/* If we still didn't found it, try to find it via the subject
from the dirmngr-cache. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next)
{
if (!find_up_dirmngr (ctrl, kh, NULL, issuer, 1))
{
int old = keydb_set_ephemeral (kh, 1);
if (keyid)
err = find_up_search_by_keyid (ctrl, kh, issuer, keyid);
else
{
keydb_search_reset (kh);
err = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
}
if (err) /* Need to make sure to have this error code. */
err = gpg_error (GPG_ERR_NOT_FOUND);
if (!err && DBG_X509)
log_debug (" found via authid and issuer from dirmngr cache\n");
}
/* If we still didn't found it, try an external lookup. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND
&& !find_next && !ctrl->offline)
{
/* We allow AIA also if CRLs are enabled; both can be used
* as a web bug so it does not make sense to not use AIA if
* CRL checks are enabled. */
if ((opt.auto_issuer_key_retrieve || !opt.no_crl_check)
&& !find_up_via_auth_info_access (ctrl, kh, cert))
{
if (DBG_X509)
log_debug (" found via authorityInfoAccess.caIssuers\n");
err = 0;
}
else if (opt.auto_issuer_key_retrieve)
{
err = find_up_external (ctrl, kh, issuer, keyid);
if (!err && DBG_X509)
log_debug (" found via authid and external lookup\n");
}
}
/* Print a note so that the user does not feel too helpless when
an issuer certificate was found and gpgsm prints BAD
signature because it is not the correct one. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && opt.quiet)
;
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
if (!opt.quiet)
{
log_info ("%sissuer certificate ", find_next?"next ":"");
if (keyid)
{
log_printf ("{");
gpgsm_dump_serial (keyid);
log_printf ("} ");
}
if (authidno)
{
log_printf ("(#");
gpgsm_dump_serial (authidno);
log_printf ("/");
gpgsm_dump_string (s);
log_printf (") ");
}
log_printf ("not found using authorityKeyIdentifier\n");
}
}
else if (err)
log_error ("failed to find authorityKeyIdentifier: err=%d\n", err);
xfree (keyid);
ksba_name_release (authid);
xfree (authidno);
}
if (err) /* Not found via authorithyKeyIdentifier, try regular issuer name. */
err = keydb_search_subject (ctrl, kh, issuer);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next)
{
int old;
/* Also try to get it from the Dirmngr cache. The function
merely puts it into the ephemeral database. */
find_up_dirmngr (ctrl, kh, NULL, issuer, 0);
/* Not found, let us see whether we have one in the ephemeral key DB. */
old = keydb_set_ephemeral (kh, 1);
if (!old)
{
keydb_search_reset (kh);
err = keydb_search_subject (ctrl, kh, issuer);
}
keydb_set_ephemeral (kh, old);
if (!err && DBG_X509)
log_debug (" found via issuer\n");
}
/* Still not found. If enabled, try an external lookup. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND && !find_next && !ctrl->offline)
{
if ((opt.auto_issuer_key_retrieve || !opt.no_crl_check)
&& !find_up_via_auth_info_access (ctrl, kh, cert))
{
if (DBG_X509)
log_debug (" found via authorityInfoAccess.caIssuers\n");
err = 0;
}
else if (opt.auto_issuer_key_retrieve)
{
err = find_up_external (ctrl, kh, issuer, NULL);
if (!err && DBG_X509)
log_debug (" found via issuer and external lookup\n");
}
}
return err;
}
/* Return the next certificate up in the chain starting at START.
Returns GPG_ERR_NOT_FOUND when there are no more certificates. */
gpg_error_t
gpgsm_walk_cert_chain (ctrl_t ctrl, ksba_cert_t start, ksba_cert_t *r_next)
{
gpg_error_t err = 0;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh = keydb_new (ctrl);
*r_next = NULL;
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer = ksba_cert_get_issuer (start, 0);
subject = ksba_cert_get_subject (start, 0);
if (!issuer)
{
log_error ("no issuer found in certificate\n");
err = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (!subject)
{
log_error ("no subject found in certificate\n");
err = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (is_root_cert (start, issuer, subject))
{
err = gpg_error (GPG_ERR_NOT_FOUND); /* we are at the root */
goto leave;
}
err = find_up (ctrl, kh, start, issuer, 0);
if (err)
{
/* It is quite common not to have a certificate, so better don't
print an error here. */
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND && opt.verbose > 1)
log_error ("failed to find issuer's certificate: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
err = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
err = keydb_get_cert (kh, r_next);
if (err)
{
log_error ("keydb_get_cert failed in %s: %s <%s>\n",
__func__, gpg_strerror (err), gpg_strsource (err));
err = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (issuer);
xfree (subject);
keydb_release (kh);
return err;
}
/* Helper for gpgsm_is_root_cert. This one is used if the subject and
issuer DNs are already known. */
static int
is_root_cert (ksba_cert_t cert, const char *issuerdn, const char *subjectdn)
{
gpg_error_t err;
int result = 0;
ksba_sexp_t serialno;
ksba_sexp_t ak_keyid;
ksba_name_t ak_name;
ksba_sexp_t ak_sn;
const char *ak_name_str;
ksba_sexp_t subj_keyid = NULL;
if (!issuerdn || !subjectdn)
return 0; /* No. */
if (strcmp (issuerdn, subjectdn))
return 0; /* No. */
err = ksba_cert_get_auth_key_id (cert, &ak_keyid, &ak_name, &ak_sn);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
return 1; /* Yes. Without a authorityKeyIdentifier this needs
to be the Root certificate (our trust anchor). */
log_error ("error getting authorityKeyIdentifier: %s\n",
gpg_strerror (err));
return 0; /* Well, it is broken anyway. Return No. */
}
serialno = ksba_cert_get_serial (cert);
if (!serialno)
{
log_error ("error getting serialno: %s\n", gpg_strerror (err));
goto leave;
}
/* Check whether the auth name's matches the issuer name+sn. If
that is the case this is a root certificate. */
ak_name_str = ksba_name_enum (ak_name, 0);
if (ak_name_str
&& !strcmp (ak_name_str, issuerdn)
&& !cmp_simple_canon_sexp (ak_sn, serialno))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
/* Similar for the ak_keyid. */
if (ak_keyid && !ksba_cert_get_subj_key_id (cert, NULL, &subj_keyid)
&& !cmp_simple_canon_sexp (ak_keyid, subj_keyid))
{
result = 1; /* Right, CERT is self-signed. */
goto leave;
}
leave:
ksba_free (subj_keyid);
ksba_free (ak_keyid);
ksba_name_release (ak_name);
ksba_free (ak_sn);
ksba_free (serialno);
return result;
}
/* Check whether the CERT is a root certificate. Returns True if this
is the case. */
int
gpgsm_is_root_cert (ksba_cert_t cert)
{
char *issuer;
char *subject;
int yes;
issuer = ksba_cert_get_issuer (cert, 0);
subject = ksba_cert_get_subject (cert, 0);
yes = is_root_cert (cert, issuer, subject);
xfree (issuer);
xfree (subject);
return yes;
}
/* This is a helper for gpgsm_validate_chain. */
static gpg_error_t
is_cert_still_valid (ctrl_t ctrl, int chain_model, int lm, estream_t fp,
ksba_cert_t subject_cert, ksba_cert_t issuer_cert,
int *any_revoked, int *any_no_crl, int *any_crl_too_old)
{
gpg_error_t err;
gnupg_isotime_t revoked_at;
char *reason;
if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp))
{
audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK,
gpg_error (GPG_ERR_NOT_ENABLED));
return 0;
}
if (!(chain_model || ctrl->use_ocsp)
&& !opt.enable_issuer_based_crl_check)
{
err = ksba_cert_get_crl_dist_point (subject_cert, 0, NULL, NULL, NULL);
if (gpg_err_code (err) == GPG_ERR_EOF)
{
/* No DP specified in the certificate. Thus the CA does not
* consider a CRL useful and the user of the certificate
* also does not consider this to be a critical thing. In
* this case we can conclude that the certificate shall not
* be revocable. Note that we reach this point here only if
* no OCSP responder shall be used. */
audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, gpg_error (GPG_ERR_TRUE));
return 0;
}
}
err = gpgsm_dirmngr_isvalid (ctrl,
subject_cert, issuer_cert,
chain_model? 2 : !!ctrl->use_ocsp,
revoked_at, &reason);
if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED)
{
gnupg_copy_time (ctrl->revoked_at, revoked_at);
xfree (ctrl->revocation_reason);
ctrl->revocation_reason = reason;
reason = NULL;
}
else
{
xfree (reason);
reason = (NULL);
}
audit_log_ok (ctrl->audit, AUDIT_CRL_CHECK, err);
if (err)
{
if (!lm)
gpgsm_cert_log_name (NULL, subject_cert);
switch (gpg_err_code (err))
{
case GPG_ERR_CERT_REVOKED:
if (!check_isotime (ctrl->revoked_at))
{
char *tmpstr;
const unsigned char *t = ctrl->revoked_at;
tmpstr = xtryasprintf ("%.4s-%.2s-%.2s %.2s:%.2s:%s (%s)",
t, t+4, t+6, t+9, t+11, t+13,
ctrl->revocation_reason?
ctrl->revocation_reason : "");
do_list (1, lm, fp, "%s: %s",
_("certificate has been revoked"), tmpstr);
xfree (tmpstr);
}
else
do_list (1, lm, fp, _("certificate has been revoked"));
*any_revoked = 1;
/* Store that in the keybox so that key listings are able to
return the revoked flag. We don't care about error,
though. */
keydb_set_cert_flags (ctrl, subject_cert, 1, KEYBOX_FLAG_VALIDITY, 0,
~0, VALIDITY_REVOKED);
break;
case GPG_ERR_NO_CRL_KNOWN:
do_list (1, lm, fp, _("no CRL found for certificate"));
*any_no_crl = 1;
break;
case GPG_ERR_NO_DATA:
do_list (1, lm, fp, _("the status of the certificate is unknown"));
*any_no_crl = 1;
break;
case GPG_ERR_CRL_TOO_OLD:
do_list (1, lm, fp, _("the available CRL is too old"));
if (!lm)
log_info (_("please make sure that the "
"\"dirmngr\" is properly installed\n"));
*any_crl_too_old = 1;
break;
default:
do_list (1, lm, fp, _("checking the CRL failed: %s"),
gpg_strerror (err));
return err;
}
}
return 0;
}
/* Helper for gpgsm_validate_chain to check the validity period of
SUBJECT_CERT. The caller needs to pass EXPTIME which will be
updated to the nearest expiration time seen. A DEPTH of 0 indicates
the target certificate, -1 the final root certificate and other
values intermediate certificates. */
static gpg_error_t
check_validity_period (ksba_isotime_t current_time,
ksba_cert_t subject_cert,
ksba_isotime_t exptime,
int listmode, estream_t listfp, int depth)
{
gpg_error_t err;
ksba_isotime_t not_before, not_after;
err = ksba_cert_get_validity (subject_cert, 0, not_before);
if (!err)
err = ksba_cert_get_validity (subject_cert, 1, not_after);
if (err)
{
do_list (1, listmode, listfp,
_("certificate with invalid validity: %s"), gpg_strerror (err));
return gpg_error (GPG_ERR_BAD_CERT);
}
if (*not_after)
{
if (!*exptime)
gnupg_copy_time (exptime, not_after);
else if (strcmp (not_after, exptime) < 0 )
gnupg_copy_time (exptime, not_after);
}
if (*not_before && strcmp (current_time, not_before) < 0 )
{
do_list (1, listmode, listfp,
depth == 0 ? _("certificate not yet valid") :
depth == -1 ? _("root certificate not yet valid") :
/* other */ _("intermediate certificate not yet valid"));
if (!listmode)
{
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (")\n");
}
return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
}
if (*not_after && strcmp (current_time, not_after) > 0 )
{
do_list (opt.ignore_expiration?0:1, listmode, listfp,
depth == 0 ? _("certificate has expired") :
depth == -1 ? _("root certificate has expired") :
/* other */ _("intermediate certificate has expired"));
if (!listmode)
{
log_info (" (expired at ");
dump_isotime (not_after);
log_printf (")\n");
}
if (opt.ignore_expiration)
log_info ("WARNING: ignoring expiration\n");
else
return gpg_error (GPG_ERR_CERT_EXPIRED);
}
return 0;
}
/* This is a variant of check_validity_period used with the chain
model. The extra constraint here is that notBefore and notAfter
must exists and if the additional argument CHECK_TIME is given this
time is used to check the validity period of SUBJECT_CERT. */
static gpg_error_t
check_validity_period_cm (ksba_isotime_t current_time,
ksba_isotime_t check_time,
ksba_cert_t subject_cert,
ksba_isotime_t exptime,
int listmode, estream_t listfp, int depth)
{
gpg_error_t err;
ksba_isotime_t not_before, not_after;
err = ksba_cert_get_validity (subject_cert, 0, not_before);
if (!err)
err = ksba_cert_get_validity (subject_cert, 1, not_after);
if (err)
{
do_list (1, listmode, listfp,
_("certificate with invalid validity: %s"), gpg_strerror (err));
return gpg_error (GPG_ERR_BAD_CERT);
}
if (!*not_before || !*not_after)
{
do_list (1, listmode, listfp,
_("required certificate attributes missing: %s%s%s"),
!*not_before? "notBefore":"",
(!*not_before && !*not_after)? ", ":"",
!*not_before? "notAfter":"");
return gpg_error (GPG_ERR_BAD_CERT);
}
if (strcmp (not_before, not_after) > 0 )
{
do_list (1, listmode, listfp,
_("certificate with invalid validity"));
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (" expired at ");
dump_isotime (not_after);
log_printf (")\n");
return gpg_error (GPG_ERR_BAD_CERT);
}
if (!*exptime)
gnupg_copy_time (exptime, not_after);
else if (strcmp (not_after, exptime) < 0 )
gnupg_copy_time (exptime, not_after);
if (strcmp (current_time, not_before) < 0 )
{
do_list (1, listmode, listfp,
depth == 0 ? _("certificate not yet valid") :
depth == -1 ? _("root certificate not yet valid") :
/* other */ _("intermediate certificate not yet valid"));
if (!listmode)
{
log_info (" (valid from ");
dump_isotime (not_before);
log_printf (")\n");
}
return gpg_error (GPG_ERR_CERT_TOO_YOUNG);
}
if (*check_time
&& (strcmp (check_time, not_before) < 0
|| strcmp (check_time, not_after) > 0))
{
/* Note that we don't need a case for the root certificate
because its own consistency has already been checked. */
do_list(opt.ignore_expiration?0:1, listmode, listfp,
depth == 0 ?
_("signature not created during lifetime of certificate") :
depth == 1 ?
_("certificate not created during lifetime of issuer") :
_("intermediate certificate not created during lifetime "
"of issuer"));
if (!listmode)
{
log_info (depth== 0? _(" ( signature created at ") :
/* */ _(" (certificate created at ") );
dump_isotime (check_time);
log_printf (")\n");
log_info (depth==0? _(" (certificate valid from ") :
/* */ _(" ( issuer valid from ") );
dump_isotime (not_before);
log_info (" to ");
dump_isotime (not_after);
log_printf (")\n");
}
if (opt.ignore_expiration)
log_info ("WARNING: ignoring expiration\n");
else
return gpg_error (GPG_ERR_CERT_EXPIRED);
}
return 0;
}
/* Ask the user whether he wants to mark the certificate CERT trusted.
Returns true if the CERT is the trusted. We also check whether the
agent is at all enabled to allow marktrusted and don't call it in
this session again if it is not. */
static int
ask_marktrusted (ctrl_t ctrl, ksba_cert_t cert, int listmode)
{
static int no_more_questions;
int rc;
char *fpr;
int success = 0;
fpr = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA1);
es_fflush (es_stdout);
log_info (_("fingerprint=%s\n"), fpr? fpr : "?");
xfree (fpr);
if (no_more_questions)
rc = gpg_error (GPG_ERR_NOT_SUPPORTED);
else
rc = gpgsm_agent_marktrusted (ctrl, cert);
if (!rc)
{
log_info (_("root certificate has now been marked as trusted\n"));
success = 1;
}
else if (!listmode)
{
gpgsm_dump_cert ("issuer", cert);
log_info ("after checking the fingerprint, you may want "
"to add it manually to the list of trusted certificates.\n");
}
if (gpg_err_code (rc) == GPG_ERR_NOT_SUPPORTED)
{
if (!no_more_questions)
log_info (_("interactive marking as trusted "
"not enabled in gpg-agent\n"));
no_more_questions = 1;
}
else if (gpg_err_code (rc) == GPG_ERR_CANCELED)
{
log_info (_("interactive marking as trusted "
"disabled for this session\n"));
no_more_questions = 1;
}
else
set_already_asked_marktrusted (cert);
return success;
}
/* Validate a chain and optionally return the nearest expiration time
in R_EXPTIME. With LISTMODE set to 1 a special listmode is
activated where only information about the certificate is printed
to LISTFP and no output is send to the usual log stream. If
CHECKTIME_ARG is set, it is used only in the chain model instead of the
current time.
Defined flag bits
VALIDATE_FLAG_NO_DIRMNGR - Do not do any dirmngr isvalid checks.
VALIDATE_FLAG_CHAIN_MODEL - Check according to chain model.
VALIDATE_FLAG_STEED - Check according to the STEED model.
*/
static int
do_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime_arg,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp, unsigned int flags,
struct rootca_flags_s *rootca_flags)
{
int rc = 0, depth, maxdepth;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh = NULL;
ksba_cert_t subject_cert = NULL, issuer_cert = NULL;
ksba_isotime_t current_time;
ksba_isotime_t check_time;
ksba_isotime_t exptime;
int any_expired = 0;
int any_revoked = 0;
int any_no_crl = 0;
int any_crl_too_old = 0;
int any_no_policy_match = 0;
int is_qualified = -1; /* Indicates whether the certificate stems
from a qualified root certificate.
-1 = unknown, 0 = no, 1 = yes. */
chain_item_t chain = NULL; /* A list of all certificates in the chain. */
gnupg_get_isotime (current_time);
gnupg_copy_time (ctrl->current_time, current_time);
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
{
if (!strcmp (checktime_arg, "19700101T000000"))
{
do_list (1, listmode, listfp,
_("WARNING: creation time of signature not known - "
"assuming current time"));
gnupg_copy_time (check_time, current_time);
}
else
gnupg_copy_time (check_time, checktime_arg);
}
else
*check_time = 0;
if (r_exptime)
*r_exptime = 0;
*exptime = 0;
if (opt.no_chain_validation && !listmode)
{
log_info ("WARNING: bypassing certificate chain validation\n");
return 0;
}
kh = keydb_new (ctrl);
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (DBG_X509 && !listmode)
gpgsm_dump_cert ("target", cert);
subject_cert = cert;
ksba_cert_ref (subject_cert);
maxdepth = 50;
depth = 0;
for (;;)
{
int is_root;
gpg_error_t istrusted_rc = gpg_error (GPG_ERR_NOT_TRUSTED);
/* Put the certificate on our list. */
{
chain_item_t ci;
ci = xtrycalloc (1, sizeof *ci);
if (!ci)
{
rc = gpg_error_from_syserror ();
goto leave;
}
ksba_cert_ref (subject_cert);
ci->cert = subject_cert;
ci->next = chain;
chain = ci;
}
xfree (issuer);
xfree (subject);
issuer = ksba_cert_get_issuer (subject_cert, 0);
subject = ksba_cert_get_subject (subject_cert, 0);
if (!issuer)
{
do_list (1, listmode, listfp, _("no issuer found in certificate"));
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
/* Is this a self-issued certificate (i.e. the root certificate)? */
is_root = is_root_cert (subject_cert, issuer, subject);
if (is_root)
{
chain->is_root = 1;
/* Check early whether the certificate is listed as trusted.
We used to do this only later but changed it to call the
check right here so that we can access special flags
associated with that specific root certificate. */
if (gpgsm_cert_has_well_known_private_key (subject_cert))
{
memset (rootca_flags, 0, sizeof *rootca_flags);
istrusted_rc = ((flags & VALIDATE_FLAG_STEED)
? 0 : gpg_error (GPG_ERR_NOT_TRUSTED));
}
else
istrusted_rc = gpgsm_agent_istrusted (ctrl, subject_cert, NULL,
rootca_flags);
audit_log_cert (ctrl->audit, AUDIT_ROOT_TRUSTED,
subject_cert, istrusted_rc);
/* If the chain model extended attribute is used, make sure
that our chain model flag is set. */
if (!(flags & VALIDATE_FLAG_STEED)
&& has_validation_model_chain (subject_cert, listmode, listfp))
rootca_flags->chain_model = 1;
}
/* Check the validity period. */
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
rc = check_validity_period_cm (current_time, check_time, subject_cert,
exptime, listmode, listfp,
(depth && is_root)? -1: depth);
else
rc = check_validity_period (current_time, subject_cert,
exptime, listmode, listfp,
(depth && is_root)? -1: depth);
if (gpg_err_code (rc) == GPG_ERR_CERT_EXPIRED)
any_expired = 1;
else if (rc)
goto leave;
/* Assert that we understand all critical extensions. */
rc = unknown_criticals (subject_cert, listmode, listfp);
if (rc)
goto leave;
/* Do a policy check. */
if (!opt.no_policy_check)
{
rc = check_cert_policy (subject_cert, listmode, listfp);
if (gpg_err_code (rc) == GPG_ERR_NO_POLICY_MATCH)
{
any_no_policy_match = 1;
rc = 1; /* Be on the safe side and set RC. */
}
else if (rc)
goto leave;
}
/* If this is the root certificate we are at the end of the chain. */
if (is_root)
{
if (!istrusted_rc)
; /* No need to check the certificate for a trusted one. */
else if (gpgsm_check_cert_sig (subject_cert, subject_cert) )
{
/* We only check the signature if the certificate is not
trusted for better diagnostics. */
do_list (1, listmode, listfp,
_("self-signed certificate has a BAD signature"));
if (DBG_X509)
{
gpgsm_dump_cert ("self-signing cert", subject_cert);
}
rc = gpg_error (depth? GPG_ERR_BAD_CERT_CHAIN
: GPG_ERR_BAD_CERT);
goto leave;
}
if (!rootca_flags->relax)
{
rc = allowed_ca (ctrl, subject_cert, NULL, listmode, listfp);
if (rc)
goto leave;
}
/* Set the flag for qualified signatures. This flag is
deduced from a list of root certificates allowed for
qualified signatures. */
if (is_qualified == -1 && !(flags & VALIDATE_FLAG_STEED))
{
gpg_error_t err;
size_t buflen;
char buf[1];
if (!ksba_cert_get_user_data (cert, "is_qualified",
&buf, sizeof (buf),
&buflen) && buflen)
{
/* We already checked this for this certificate,
thus we simply take it from the user data. */
is_qualified = !!*buf;
}
else
{
/* Need to consult the list of root certificates for
qualified signatures. But first we check the
modern way by looking at the root ca flag. */
if (rootca_flags->qualified)
err = 0;
else
err = gpgsm_is_in_qualified_list (ctrl, subject_cert, NULL);
if (!err)
is_qualified = 1;
else if ( gpg_err_code (err) == GPG_ERR_NOT_FOUND)
is_qualified = 0;
else
log_error ("checking the list of qualified "
"root certificates failed: %s\n",
gpg_strerror (err));
if ( is_qualified != -1 )
{
/* Cache the result but don't care too much
about an error. */
buf[0] = !!is_qualified;
err = ksba_cert_set_user_data (subject_cert,
"is_qualified", buf, 1);
if (err)
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
}
}
}
/* Act on the check for a trusted root certificates. */
rc = istrusted_rc;
if (!rc)
;
else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
{
do_list (0, listmode, listfp,
_("root certificate is not marked trusted"));
/* If we already figured out that the certificate is
expired it does not make much sense to ask the user
whether they want to trust the root certificate. We
should do this only if the certificate under question
will then be usable. If the certificate has a well
known private key asking the user does not make any
sense. */
if ( !any_expired
&& !gpgsm_cert_has_well_known_private_key (subject_cert)
&& (!listmode || !already_asked_marktrusted (subject_cert))
&& ask_marktrusted (ctrl, subject_cert, listmode) )
rc = 0;
}
else
{
log_error (_("checking the trust list failed: %s\n"),
gpg_strerror (rc));
}
if (rc)
goto leave;
/* Check for revocations etc. */
if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
;
else if ((flags & VALIDATE_FLAG_STEED))
; /* Fixme: check revocations via DNS. */
else if (opt.no_trusted_cert_crl_check || rootca_flags->relax)
;
else
rc = is_cert_still_valid (ctrl,
(flags & VALIDATE_FLAG_CHAIN_MODEL),
listmode, listfp,
subject_cert, subject_cert,
&any_revoked, &any_no_crl,
&any_crl_too_old);
if (rc)
goto leave;
break; /* Okay: a self-signed certificate is an end-point. */
} /* End is_root. */
/* Take care that the chain does not get too long. */
if ((depth+1) > maxdepth)
{
do_list (1, listmode, listfp, _("certificate chain too long\n"));
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
/* Find the next cert up the tree. */
keydb_search_reset (kh);
rc = find_up (ctrl, kh, subject_cert, issuer, 0);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
do_list (0, listmode, listfp, _("issuer certificate not found"));
if (!listmode && !opt.quiet)
{
log_info ("issuer certificate: #/");
gpgsm_dump_string (issuer);
log_printf ("\n");
}
}
else
log_error ("failed to find issuer's certificate: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
ksba_cert_release (issuer_cert); issuer_cert = NULL;
rc = keydb_get_cert (kh, &issuer_cert);
if (rc)
{
log_error ("keydb_get_cert failed in %s: %s <%s>\n",
__func__, gpg_strerror (rc), gpg_strsource (rc));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
try_another_cert:
if (DBG_X509)
{
log_debug ("got issuer's certificate:\n");
gpgsm_dump_cert ("issuer", issuer_cert);
}
rc = gpgsm_check_cert_sig (issuer_cert, subject_cert);
if (rc)
{
do_list (0, listmode, listfp, _("certificate has a BAD signature"));
if (DBG_X509)
{
gpgsm_dump_cert ("signing issuer", issuer_cert);
gpgsm_dump_cert ("signed subject", subject_cert);
}
if (gpg_err_code (rc) == GPG_ERR_BAD_SIGNATURE)
{
/* We now try to find other issuer certificates which
might have been used. This is required because some
CAs are reusing the issuer and subject DN for new
root certificates. */
/* FIXME: Do this only if we don't have an
AKI.keyIdentifier */
rc = find_up (ctrl, kh, subject_cert, issuer, 1);
if (!rc)
{
ksba_cert_t tmp_cert;
rc = keydb_get_cert (kh, &tmp_cert);
if (rc || !compare_certs (issuer_cert, tmp_cert))
{
/* The find next did not work or returned an
identical certificate. We better stop here
to avoid infinite checks. */
/* No need to set RC because it is not used:
rc = gpg_error (GPG_ERR_BAD_SIGNATURE); */
ksba_cert_release (tmp_cert);
}
else
{
do_list (0, listmode, listfp,
_("found another possible matching "
"CA certificate - trying again"));
ksba_cert_release (issuer_cert);
issuer_cert = tmp_cert;
goto try_another_cert;
}
}
}
/* We give a more descriptive error code than the one
returned from the signature checking. */
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
is_root = gpgsm_is_root_cert (issuer_cert);
istrusted_rc = gpg_error (GPG_ERR_NOT_TRUSTED);
/* Check that a CA is allowed to issue certificates. */
{
int chainlen;
rc = allowed_ca (ctrl, issuer_cert, &chainlen, listmode, listfp);
if (rc)
{
/* Not allowed. Check whether this is a trusted root
certificate and whether we allow special exceptions.
We could carry the result of the test over to the
regular root check at the top of the loop but for
clarity we won't do that. Given that the majority of
certificates carry proper BasicContraints our way of
overriding an error in the way is justified for
performance reasons. */
if (is_root)
{
if (gpgsm_cert_has_well_known_private_key (issuer_cert))
{
memset (rootca_flags, 0, sizeof *rootca_flags);
istrusted_rc = ((flags & VALIDATE_FLAG_STEED)
? 0 : gpg_error (GPG_ERR_NOT_TRUSTED));
}
else
istrusted_rc = gpgsm_agent_istrusted
(ctrl, issuer_cert, NULL, rootca_flags);
if (!istrusted_rc && rootca_flags->relax)
{
/* Ignore the error due to the relax flag. */
rc = 0;
chainlen = -1;
}
}
}
if (rc)
goto leave;
if (chainlen >= 0 && depth > chainlen)
{
do_list (1, listmode, listfp,
_("certificate chain longer than allowed by CA (%d)"),
chainlen);
rc = gpg_error (GPG_ERR_BAD_CERT_CHAIN);
goto leave;
}
}
/* Is the certificate allowed to sign other certificates. */
if (!listmode)
{
rc = gpgsm_cert_use_cert_p (issuer_cert);
if (rc)
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "certcert.issuer.keyusage",
numbuf, NULL);
goto leave;
}
}
/* Check for revocations etc. Note that for a root certificate
this test is done a second time later. This should eventually
be fixed. */
if ((flags & VALIDATE_FLAG_NO_DIRMNGR))
rc = 0;
else if ((flags & VALIDATE_FLAG_STEED))
rc = 0; /* Fixme: XXX */
else if (is_root && (opt.no_trusted_cert_crl_check
|| (!istrusted_rc && rootca_flags->relax)))
rc = 0;
else
rc = is_cert_still_valid (ctrl,
(flags & VALIDATE_FLAG_CHAIN_MODEL),
listmode, listfp,
subject_cert, issuer_cert,
&any_revoked, &any_no_crl, &any_crl_too_old);
if (rc)
goto leave;
if (opt.verbose && !listmode)
log_info (depth == 0 ? _("certificate is good\n") :
!is_root ? _("intermediate certificate is good\n") :
/* other */ _("root certificate is good\n"));
/* Under the chain model the next check time is the creation
time of the subject certificate. */
if ( (flags & VALIDATE_FLAG_CHAIN_MODEL) )
{
rc = ksba_cert_get_validity (subject_cert, 0, check_time);
if (rc)
{
/* That will never happen as we have already checked
this above. */
BUG ();
}
}
/* For the next round the current issuer becomes the new subject. */
keydb_search_reset (kh);
ksba_cert_release (subject_cert);
subject_cert = issuer_cert;
issuer_cert = NULL;
depth++;
} /* End chain traversal. */
if (!listmode && !opt.quiet)
{
if (opt.no_policy_check)
log_info ("policies not checked due to %s option\n",
"--disable-policy-checks");
if (ctrl->offline || (opt.no_crl_check && !ctrl->use_ocsp))
log_info ("CRLs not checked due to %s option\n",
ctrl->offline ? "offline" : "--disable-crl-checks");
}
if (!rc)
{ /* If we encountered an error somewhere during the checks, set
the error code to the most critical one */
if (any_revoked)
rc = gpg_error (GPG_ERR_CERT_REVOKED);
else if (any_expired)
rc = gpg_error (GPG_ERR_CERT_EXPIRED);
else if (any_no_crl)
rc = gpg_error (GPG_ERR_NO_CRL_KNOWN);
else if (any_crl_too_old)
rc = gpg_error (GPG_ERR_CRL_TOO_OLD);
else if (any_no_policy_match)
rc = gpg_error (GPG_ERR_NO_POLICY_MATCH);
}
leave:
/* If we have traversed a complete chain up to the root we will
reset the ephemeral flag for all these certificates. This is done
regardless of any error because those errors may only be
transient. */
if (chain && chain->is_root)
{
gpg_error_t err;
chain_item_t ci;
for (ci = chain; ci; ci = ci->next)
{
/* Note that it is possible for the last certificate in the
chain (i.e. our target certificate) that it has not yet
been stored in the keybox and thus the flag can't be set.
We ignore this error because it will later be stored
anyway. */
err = keydb_set_cert_flags (ctrl, ci->cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (!ci->next && gpg_err_code (err) == GPG_ERR_NOT_FOUND)
;
else if (err)
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (err));
}
}
/* If we have figured something about the qualified signature
capability of the certificate under question, store the result as
user data in all certificates of the chain. We do this even if the
validation itself failed. */
if (is_qualified != -1 && !(flags & VALIDATE_FLAG_STEED))
{
gpg_error_t err;
chain_item_t ci;
char buf[1];
buf[0] = !!is_qualified;
for (ci = chain; ci; ci = ci->next)
{
err = ksba_cert_set_user_data (ci->cert, "is_qualified", buf, 1);
if (err)
{
log_error ("set_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
if (!rc)
rc = err;
}
}
}
/* If auditing has been enabled, record what is in the chain. */
if (ctrl->audit)
{
chain_item_t ci;
audit_log (ctrl->audit, AUDIT_CHAIN_BEGIN);
for (ci = chain; ci; ci = ci->next)
{
audit_log_cert (ctrl->audit,
ci->is_root? AUDIT_CHAIN_ROOTCERT : AUDIT_CHAIN_CERT,
ci->cert, 0);
}
audit_log (ctrl->audit, AUDIT_CHAIN_END);
}
if (r_exptime)
gnupg_copy_time (r_exptime, exptime);
xfree (issuer);
xfree (subject);
keydb_release (kh);
while (chain)
{
chain_item_t ci_next = chain->next;
ksba_cert_release (chain->cert);
xfree (chain);
chain = ci_next;
}
ksba_cert_release (issuer_cert);
ksba_cert_release (subject_cert);
return rc;
}
/* Validate a certificate chain. For a description see
do_validate_chain. This function is a wrapper to handle a root
certificate with the chain_model flag set. If RETFLAGS is not
NULL, flags indicating now the verification was done are stored
there. The only defined bits for RETFLAGS are
VALIDATE_FLAG_CHAIN_MODEL and VALIDATE_FLAG_STEED.
If you are verifying a signature you should set CHECKTIME to the
creation time of the signature. If your are verifying a
certificate, set it nil (i.e. the empty string). If the creation
date of the signature is not known use the special date
"19700101T000000" which is treated in a special way here. */
int
gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert, ksba_isotime_t checktime,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp, unsigned int flags,
unsigned int *retflags)
{
int rc;
struct rootca_flags_s rootca_flags;
unsigned int dummy_retflags;
if (!retflags)
retflags = &dummy_retflags;
/* If the session requested a certain validation mode make sure the
corresponding flags are set. */
if (ctrl->validation_model == 1)
flags |= VALIDATE_FLAG_CHAIN_MODEL;
else if (ctrl->validation_model == 2)
flags |= VALIDATE_FLAG_STEED;
/* If the chain model was forced, set this immediately into
RETFLAGS. */
*retflags = (flags & VALIDATE_FLAG_CHAIN_MODEL);
memset (&rootca_flags, 0, sizeof rootca_flags);
if ((flags & VALIDATE_FLAG_BYPASS))
{
*retflags |= VALIDATE_FLAG_BYPASS;
rc = 0;
}
else
rc = do_validate_chain (ctrl, cert, checktime,
r_exptime, listmode, listfp, flags,
&rootca_flags);
if (!rc && (flags & VALIDATE_FLAG_STEED))
{
*retflags |= VALIDATE_FLAG_STEED;
}
else if (!(flags & VALIDATE_FLAG_CHAIN_MODEL)
&& (rootca_flags.valid && rootca_flags.chain_model))
{
/* The root CA indicated that the chain model is to be used but
* we have not yet used it. Thus do the validation again using
* the chain model. */
if (opt.verbose)
do_list (0, listmode, listfp, _("switching to chain model"));
rc = do_validate_chain (ctrl, cert, checktime,
r_exptime, listmode, listfp,
(flags |= VALIDATE_FLAG_CHAIN_MODEL),
&rootca_flags);
*retflags |= VALIDATE_FLAG_CHAIN_MODEL;
}
if (opt.verbose)
do_list (0, listmode, listfp, _("validation model used: %s"),
(*retflags & VALIDATE_FLAG_BYPASS)?
"bypass" :
(*retflags & VALIDATE_FLAG_STEED)?
"steed" :
(*retflags & VALIDATE_FLAG_CHAIN_MODEL)?
_("chain"):_("shell"));
return rc;
}
/* Check that the given certificate is valid but DO NOT check any
constraints. We assume that the issuers certificate is already in
the DB and that this one is valid; which it should be because it
has been checked using this function. */
int
gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert)
{
int rc = 0;
char *issuer = NULL;
char *subject = NULL;
KEYDB_HANDLE kh;
ksba_cert_t issuer_cert = NULL;
if (opt.no_chain_validation)
{
log_info ("WARNING: bypassing basic certificate checks\n");
return 0;
}
kh = keydb_new (ctrl);
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
issuer = ksba_cert_get_issuer (cert, 0);
subject = ksba_cert_get_subject (cert, 0);
if (!issuer)
{
log_error ("no issuer found in certificate\n");
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (is_root_cert (cert, issuer, subject))
{
rc = gpgsm_check_cert_sig (cert, cert);
if (rc)
{
log_error ("self-signed certificate has a BAD signature: %s\n",
gpg_strerror (rc));
if (DBG_X509)
{
gpgsm_dump_cert ("self-signing cert", cert);
}
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
}
else
{
/* Find the next cert up the tree. */
keydb_search_reset (kh);
rc = find_up (ctrl, kh, cert, issuer, 0);
if (rc)
{
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
if (!opt.quiet)
{
es_fflush (es_stdout);
log_info ("issuer certificate (#/");
gpgsm_dump_string (issuer);
log_printf (") not found\n");
}
}
else
log_error ("failed to find issuer's certificate: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
rc = gpg_error (GPG_ERR_MISSING_ISSUER_CERT);
goto leave;
}
ksba_cert_release (issuer_cert); issuer_cert = NULL;
rc = keydb_get_cert (kh, &issuer_cert);
if (rc)
{
log_error ("keydb_get_cert failed in %s: %s <%s>\n",
__func__, gpg_strerror (rc), gpg_strsource (rc));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gpgsm_check_cert_sig (issuer_cert, cert);
if (rc)
{
log_error ("certificate has a BAD signature: %s\n",
gpg_strerror (rc));
if (DBG_X509)
{
gpgsm_dump_cert ("signing issuer", issuer_cert);
gpgsm_dump_cert ("signed subject", cert);
}
rc = gpg_error (GPG_ERR_BAD_CERT);
goto leave;
}
if (opt.verbose)
log_info (_("certificate is good\n"));
}
leave:
xfree (issuer);
xfree (subject);
keydb_release (kh);
ksba_cert_release (issuer_cert);
return rc;
}
/* Check whether the certificate CERT has been issued by the German
authority for qualified signature. They do not set the
basicConstraints and thus we need this workaround. It works by
looking up the root certificate and checking whether that one is
listed as a qualified certificate for Germany.
We also try to cache this data but as long as don't keep a
reference to the certificate this won't be used.
Returns: True if CERT is a RegTP issued CA cert (i.e. the root
certificate itself or one of the CAs). In that case CHAINLEN will
receive the length of the chain which is either 0 or 1.
*/
static int
get_regtp_ca_info (ctrl_t ctrl, ksba_cert_t cert, int *chainlen)
{
gpg_error_t err;
ksba_cert_t next;
int rc = 0;
int i, depth;
char country[3];
ksba_cert_t array[4];
char buf[2];
size_t buflen;
int dummy_chainlen;
if (!chainlen)
chainlen = &dummy_chainlen;
*chainlen = 0;
err = ksba_cert_get_user_data (cert, "regtp_ca_chainlen",
&buf, sizeof (buf), &buflen);
if (!err)
{
/* Got info. */
if (buflen < 2 || !*buf)
return 0; /* Nothing found. */
*chainlen = buf[1];
return 1; /* This is a regtp CA. */
}
else if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
{
log_error ("ksba_cert_get_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
return 0; /* Nothing found. */
}
/* Need to gather the info. This requires to walk up the chain
until we have found the root. Because we are only interested in
German Bundesnetzagentur (former RegTP) derived certificates 3
levels are enough. (The German signature law demands a 3 tier
hierarchy; thus there is only one CA between the EE and the Root
CA.) */
memset (&array, 0, sizeof array);
depth = 0;
ksba_cert_ref (cert);
array[depth++] = cert;
ksba_cert_ref (cert);
while (depth < DIM(array) && !(rc=gpgsm_walk_cert_chain (ctrl, cert, &next)))
{
ksba_cert_release (cert);
ksba_cert_ref (next);
array[depth++] = next;
cert = next;
}
ksba_cert_release (cert);
if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND || !depth || depth == DIM(array) )
{
/* We did not reached the root. */
goto leave;
}
/* If this is a German signature law issued certificate, we store
additional information. */
if (!gpgsm_is_in_qualified_list (NULL, array[depth-1], country)
&& !strcmp (country, "de"))
{
/* Setting the pathlen for the root CA and the CA flag for the
next one is all what we need to do. */
err = ksba_cert_set_user_data (array[depth-1], "regtp_ca_chainlen",
"\x01\x01", 2);
if (!err && depth > 1)
err = ksba_cert_set_user_data (array[depth-2], "regtp_ca_chainlen",
"\x01\x00", 2);
if (err)
log_error ("ksba_set_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
for (i=0; i < depth; i++)
ksba_cert_release (array[i]);
*chainlen = (depth>1? 0:1);
return 1;
}
leave:
/* Nothing special with this certificate. Mark the target
certificate anyway to avoid duplicate lookups. */
err = ksba_cert_set_user_data (cert, "regtp_ca_chainlen", "", 1);
if (err)
log_error ("ksba_set_user_data(%s) failed: %s\n",
"regtp_ca_chainlen", gpg_strerror (err));
for (i=0; i < depth; i++)
ksba_cert_release (array[i]);
return 0;
}
diff --git a/sm/certreqgen.c b/sm/certreqgen.c
index 435333298..eb084f594 100644
--- a/sm/certreqgen.c
+++ b/sm/certreqgen.c
@@ -1,1580 +1,1580 @@
/* certreqgen.c - Generate a key and a certification [request]
* Copyright (C) 2002, 2003, 2005, 2007, 2010,
* 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/*
The format of the parameter file is described in the manual under
"Unattended Usage".
Here is an example:
$ cat >foo <<EOF
%echo Generating a standard key
Key-Type: RSA
Key-Length: 3072
Name-DN: CN=test cert 1,OU=Aegypten Project,O=g10 Code GmbH,L=Ddorf,C=DE
Name-Email: joe@foo.bar
# Do a commit here, so that we can later print a "done"
%commit
%echo done
EOF
This parameter file was used to create the STEED CA:
Key-Type: RSA
Key-Length: 1024
Key-Grip: 68A638998DFABAC510EA645CE34F9686B2EDF7EA
Key-Usage: cert
Serial: 1
Name-DN: CN=The STEED Self-Signing Nonthority
Not-Before: 2011-11-11
Not-After: 2106-02-06
Subject-Key-Id: 68A638998DFABAC510EA645CE34F9686B2EDF7EA
Extension: 2.5.29.19 c 30060101ff020101
Extension: 1.3.6.1.4.1.11591.2.2.2 n 0101ff
Signing-Key: 68A638998DFABAC510EA645CE34F9686B2EDF7EA
%commit
Commnn extensions:
ExtKeyUsage: clientAuth (suggested) serverAuth (suggested)
-> 2.5.29.37 n 301406082B0601050507030206082B06010505070301
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../common/i18n.h"
#include "../common/membuf.h"
enum para_name
{
pKEYTYPE,
pKEYLENGTH,
pKEYGRIP,
pKEYCURVE,
pKEYUSAGE,
pNAMEDN,
pNAMEEMAIL,
pNAMEDNS,
pNAMEURI,
pSERIAL,
pISSUERDN,
pNOTBEFORE,
pNOTAFTER,
pSIGNINGKEY,
pHASHALGO,
pAUTHKEYID,
pSUBJKEYID,
pEXTENSION
};
struct para_data_s
{
struct para_data_s *next;
int lnr;
enum para_name key;
union {
unsigned int usage;
char value[1];
} u;
};
struct reqgen_ctrl_s
{
int lnr;
int dryrun;
};
static const char oidstr_authorityKeyIdentifier[] = "2.5.29.35";
static const char oidstr_subjectKeyIdentifier[] = "2.5.29.14";
static const char oidstr_keyUsage[] = "2.5.29.15";
static const char oidstr_basicConstraints[] = "2.5.29.19";
static const char oidstr_standaloneCertificate[] = "1.3.6.1.4.1.11591.2.2.1";
static int proc_parameters (ctrl_t ctrl,
struct para_data_s *para,
estream_t out_fp,
struct reqgen_ctrl_s *outctrl);
static int create_request (ctrl_t ctrl,
struct para_data_s *para,
const char *carddirect,
ksba_const_sexp_t public,
ksba_const_sexp_t sigkey,
ksba_writer_t writer);
static void
release_parameter_list (struct para_data_s *r)
{
struct para_data_s *r2;
for (; r ; r = r2)
{
r2 = r->next;
xfree(r);
}
}
static struct para_data_s *
get_parameter (struct para_data_s *para, enum para_name key, int seq)
{
struct para_data_s *r;
for (r = para; r ; r = r->next)
if ( r->key == key && !seq--)
return r;
return NULL;
}
static const char *
get_parameter_value (struct para_data_s *para, enum para_name key, int seq)
{
struct para_data_s *r = get_parameter (para, key, seq);
return (r && *r->u.value)? r->u.value : NULL;
}
static int
get_parameter_algo (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key, 0);
if (!r)
return -1;
if (digitp (r->u.value))
return atoi( r->u.value );
return gcry_pk_map_name (r->u.value);
}
/* Parse the usage parameter. Returns 0 on success. Note that we
only care about sign and encrypt and don't (yet) allow all the
other X.509 usage to be specified; instead we will use a fixed
mapping to the X.509 usage flags. */
static int
parse_parameter_usage (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key, 0);
char *p, *pn;
unsigned int use;
if (!r)
return 0; /* none (this is an optional parameter)*/
use = 0;
pn = r->u.value;
while ( (p = strsep (&pn, " \t,")) )
{
if (!*p)
;
else if ( !ascii_strcasecmp (p, "sign") )
use |= GCRY_PK_USAGE_SIGN;
else if ( !ascii_strcasecmp (p, "encrypt")
|| !ascii_strcasecmp (p, "encr") )
use |= GCRY_PK_USAGE_ENCR;
else if ( !ascii_strcasecmp (p, "cert") )
use |= GCRY_PK_USAGE_CERT;
else
{
log_error ("line %d: invalid usage list\n", r?r->lnr:0);
return -1; /* error */
}
}
r->u.usage = use;
return 0;
}
static unsigned int
get_parameter_uint (struct para_data_s *para, enum para_name key)
{
struct para_data_s *r = get_parameter (para, key, 0);
if (!r)
return 0;
if (r->key == pKEYUSAGE)
return r->u.usage;
return (unsigned int)strtoul (r->u.value, NULL, 10);
}
/* Read the certificate generation parameters from FP and generate
(all) certificate requests. */
static int
read_parameters (ctrl_t ctrl, estream_t fp, estream_t out_fp)
{
static struct {
const char *name;
enum para_name key;
int allow_dups;
} keywords[] = {
{ "Key-Type", pKEYTYPE},
{ "Key-Length", pKEYLENGTH },
{ "Key-Grip", pKEYGRIP },
{ "Key-Curve", pKEYCURVE },
{ "Key-Usage", pKEYUSAGE },
{ "Name-DN", pNAMEDN },
{ "Name-Email", pNAMEEMAIL, 1 },
{ "Name-DNS", pNAMEDNS, 1 },
{ "Name-URI", pNAMEURI, 1 },
{ "Serial", pSERIAL },
{ "Issuer-DN", pISSUERDN },
{ "Creation-Date", pNOTBEFORE },
{ "Not-Before", pNOTBEFORE },
{ "Expire-Date", pNOTAFTER },
{ "Not-After", pNOTAFTER },
{ "Signing-Key", pSIGNINGKEY },
{ "Hash-Algo", pHASHALGO },
{ "Authority-Key-Id", pAUTHKEYID },
{ "Subject-Key-Id", pSUBJKEYID },
{ "Extension", pEXTENSION, 1 },
{ NULL, 0 }
};
char line[1024], *p;
const char *err = NULL;
struct para_data_s *para, *r;
int i, rc = 0, any = 0;
struct reqgen_ctrl_s outctrl;
memset (&outctrl, 0, sizeof (outctrl));
err = NULL;
para = NULL;
while (es_fgets (line, DIM(line)-1, fp) )
{
char *keyword, *value;
outctrl.lnr++;
if (*line && line[strlen(line)-1] != '\n')
{
err = "line too long";
break;
}
for (p=line; spacep (p); p++)
;
if (!*p || *p == '#')
continue;
keyword = p;
if (*keyword == '%')
{
for (; *p && !ascii_isspace (*p); p++)
;
if (*p)
*p++ = 0;
for (; ascii_isspace (*p); p++)
;
value = p;
trim_trailing_spaces (value);
if (!ascii_strcasecmp (keyword, "%echo"))
log_info ("%s\n", value);
else if (!ascii_strcasecmp (keyword, "%dry-run"))
outctrl.dryrun = 1;
else if (!ascii_strcasecmp( keyword, "%commit"))
{
rc = proc_parameters (ctrl, para, out_fp, &outctrl);
if (rc)
goto leave;
any = 1;
release_parameter_list (para);
para = NULL;
}
else
log_info ("skipping control '%s' (%s)\n", keyword, value);
continue;
}
if (!(p = strchr (p, ':')) || p == keyword)
{
err = "missing colon";
break;
}
if (*p)
*p++ = 0;
for (; spacep (p); p++)
;
if (!*p)
{
err = "missing argument";
break;
}
value = p;
trim_trailing_spaces (value);
for (i=0; (keywords[i].name
&& ascii_strcasecmp (keywords[i].name, keyword)); i++)
;
if (!keywords[i].name)
{
err = "unknown keyword";
break;
}
if (keywords[i].key != pKEYTYPE && !para)
{
err = "parameter block does not start with \"Key-Type\"";
break;
}
if (keywords[i].key == pKEYTYPE && para)
{
rc = proc_parameters (ctrl, para, out_fp, &outctrl);
if (rc)
goto leave;
any = 1;
release_parameter_list (para);
para = NULL;
}
else if (!keywords[i].allow_dups)
{
for (r = para; r && r->key != keywords[i].key; r = r->next)
;
if (r)
{
err = "duplicate keyword";
break;
}
}
r = xtrycalloc (1, sizeof *r + strlen( value ));
if (!r)
{
err = "out of core";
break;
}
r->lnr = outctrl.lnr;
r->key = keywords[i].key;
strcpy (r->u.value, value);
r->next = para;
para = r;
}
if (err)
{
log_error ("line %d: %s\n", outctrl.lnr, err);
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (es_ferror(fp))
{
log_error ("line %d: read error: %s\n", outctrl.lnr, strerror(errno) );
rc = gpg_error (GPG_ERR_GENERAL);
}
else if (para)
{
rc = proc_parameters (ctrl, para, out_fp, &outctrl);
if (rc)
goto leave;
any = 1;
}
if (!rc && !any)
rc = gpg_error (GPG_ERR_NO_DATA);
leave:
release_parameter_list (para);
return rc;
}
/* check whether there are invalid characters in the email address S */
static int
has_invalid_email_chars (const char *s)
{
int at_seen=0;
static char valid_chars[] = "01234567890_-."
"abcdefghijklmnopqrstuvwxyz"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";
for (; *s; s++)
{
if (*s & 0x80)
return 1;
if (*s == '@')
at_seen++;
else if (!at_seen && !( !!strchr (valid_chars, *s) || *s == '+'))
return 1;
else if (at_seen && !strchr (valid_chars, *s))
return 1;
}
return at_seen != 1;
}
/* Check that all required parameters are given and perform the action */
static int
proc_parameters (ctrl_t ctrl, struct para_data_s *para,
estream_t out_fp, struct reqgen_ctrl_s *outctrl)
{
gpg_error_t err;
struct para_data_s *r;
const char *s, *string;
int i;
int algo;
unsigned int nbits;
char numbuf[20];
unsigned char keyparms[100];
int rc = 0;
ksba_sexp_t public = NULL;
ksba_sexp_t sigkey = NULL;
int seq;
size_t erroff, errlen;
char *cardkeyid = NULL;
/* Check that we have all required parameters; */
log_assert (get_parameter (para, pKEYTYPE, 0));
/* There is a problem with pkcs-10 on how to use ElGamal because it
is expected that a PK algorithm can always be used for
signing. Another problem is that on-card generated encryption
keys may not be used for signing. */
algo = get_parameter_algo (para, pKEYTYPE);
if (!algo && (s = get_parameter_value (para, pKEYTYPE, 0)) && *s)
{
/* Hack to allow creation of certificates directly from a smart
card. For example: "Key-Type: card:OPENPGP.3". */
if (!strncmp (s, "card:", 5) && s[5])
cardkeyid = xtrystrdup (s+5);
}
if (algo < 1 && !cardkeyid)
{
r = get_parameter (para, pKEYTYPE, 0);
if (r)
log_error (_("line %d: invalid algorithm\n"), r?r->lnr:0);
else
log_error ("No Key-Type specified\n");
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check the keylength. NOTE: If you change this make sure that it
- macthes the gpgconflist item in gpgsm.c */
+ matches the gpgconflist item in gpgsm.c */
if (!get_parameter (para, pKEYLENGTH, 0))
nbits = 3072;
else
nbits = get_parameter_uint (para, pKEYLENGTH);
if ((nbits < 1024 || nbits > 4096) && !cardkeyid)
{
/* The BSI specs dated 2002-11-25 don't allow lengths below 1024. */
r = get_parameter (para, pKEYLENGTH, 0);
log_error (_("line %d: invalid key length %u (valid are %d to %d)\n"),
r?r->lnr:0, nbits, 1024, 4096);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check the usage. */
if (parse_parameter_usage (para, pKEYUSAGE))
{
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check that there is a subject name and that this DN fits our
requirements. */
if (!(s=get_parameter_value (para, pNAMEDN, 0)))
{
r = get_parameter (para, pNAMEDN, 0);
log_error (_("line %d: no subject name given\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
err = ksba_dn_teststr (s, 0, &erroff, &errlen);
if (err)
{
r = get_parameter (para, pNAMEDN, 0);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME)
log_error (_("line %d: invalid subject name label '%.*s'\n"),
r?r->lnr:0, (int)errlen, s+erroff);
else
log_error (_("line %d: invalid subject name '%s' at pos %d\n"),
r?r->lnr:0, s, (int)erroff);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check that the optional email address is okay. */
for (seq=0; (s=get_parameter_value (para, pNAMEEMAIL, seq)); seq++)
{
if (has_invalid_email_chars (s)
|| *s == '@'
|| s[strlen(s)-1] == '@'
|| s[strlen(s)-1] == '.'
|| strstr(s, ".."))
{
r = get_parameter (para, pNAMEEMAIL, seq);
log_error (_("line %d: not a valid email address\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional serial number. */
string = get_parameter_value (para, pSERIAL, 0);
if (string)
{
if (!strcmp (string, "random"))
; /* Okay. */
else
{
for (s=string, i=0; hexdigitp (s); s++, i++)
;
if (*s)
{
r = get_parameter (para, pSERIAL, 0);
log_error (_("line %d: invalid serial number\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
}
/* Check the optional issuer DN. */
string = get_parameter_value (para, pISSUERDN, 0);
if (string)
{
err = ksba_dn_teststr (string, 0, &erroff, &errlen);
if (err)
{
r = get_parameter (para, pISSUERDN, 0);
if (gpg_err_code (err) == GPG_ERR_UNKNOWN_NAME)
log_error (_("line %d: invalid issuer name label '%.*s'\n"),
r?r->lnr:0, (int)errlen, string+erroff);
else
log_error (_("line %d: invalid issuer name '%s' at pos %d\n"),
r?r->lnr:0, string, (int)erroff);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional creation date. */
string = get_parameter_value (para, pNOTBEFORE, 0);
if (string && !string2isotime (NULL, string))
{
r = get_parameter (para, pNOTBEFORE, 0);
log_error (_("line %d: invalid date given\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Check the optional expire date. */
string = get_parameter_value (para, pNOTAFTER, 0);
if (string && !string2isotime (NULL, string))
{
r = get_parameter (para, pNOTAFTER, 0);
log_error (_("line %d: invalid date given\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
/* Get the optional signing key. */
string = get_parameter_value (para, pSIGNINGKEY, 0);
if (string)
{
rc = gpgsm_agent_readkey (ctrl, 0, string, &sigkey);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: error getting signing key by keygrip '%s'"
": %s\n"), r?r->lnr:0, s, gpg_strerror (rc));
xfree (cardkeyid);
return rc;
}
}
/* Check the optional hash-algo. */
{
int mdalgo;
string = get_parameter_value (para, pHASHALGO, 0);
if (string && !((mdalgo = gcry_md_map_name (string))
&& (mdalgo == GCRY_MD_SHA1
|| mdalgo == GCRY_MD_SHA256
|| mdalgo == GCRY_MD_SHA384
|| mdalgo == GCRY_MD_SHA512)))
{
r = get_parameter (para, pHASHALGO, 0);
log_error (_("line %d: invalid hash algorithm given\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional AuthorityKeyId. */
string = get_parameter_value (para, pAUTHKEYID, 0);
if (string && strcmp (string, "none"))
{
for (s=string, i=0; hexdigitp (s); s++, i++)
;
if (*s || (i&1))
{
r = get_parameter (para, pAUTHKEYID, 0);
log_error (_("line %d: invalid authority-key-id\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional SubjectKeyId. */
string = get_parameter_value (para, pSUBJKEYID, 0);
if (string && strcmp (string, "none"))
{
for (s=string, i=0; hexdigitp (s); s++, i++)
;
if (*s || (i&1))
{
r = get_parameter (para, pSUBJKEYID, 0);
log_error (_("line %d: invalid subject-key-id\n"), r?r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Check the optional extensions. */
for (seq=0; (string=get_parameter_value (para, pEXTENSION, seq)); seq++)
{
int okay = 0;
s = strpbrk (string, " \t:");
if (s)
{
s++;
while (spacep (s))
s++;
if (*s && strchr ("nNcC", *s))
{
s++;
while (spacep (s))
s++;
if (*s == ':')
s++;
if (*s)
{
while (spacep (s))
s++;
for (i=0; hexdigitp (s); s++, i++)
;
if (!((*s && *s != ':') || !i || (i&1)))
okay = 1;
}
}
}
if (!okay)
{
r = get_parameter (para, pEXTENSION, seq);
log_error (_("line %d: invalid extension syntax\n"), r? r->lnr:0);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
}
/* Create or retrieve the public key. */
if (cardkeyid) /* Take the key from the current smart card. */
{
rc = gpgsm_agent_readkey (ctrl, 1, cardkeyid, &public);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: error reading key '%s' from card: %s\n"),
r?r->lnr:0, cardkeyid, gpg_strerror (rc));
xfree (sigkey);
xfree (cardkeyid);
return rc;
}
}
else if ((s=get_parameter_value (para, pKEYGRIP, 0))) /* Use existing key.*/
{
rc = gpgsm_agent_readkey (ctrl, 0, s, &public);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: error getting key by keygrip '%s': %s\n"),
r->lnr, s, gpg_strerror (rc));
xfree (sigkey);
xfree (cardkeyid);
return rc;
}
}
else if (!outctrl->dryrun) /* Generate new key. */
{
if (algo == GCRY_PK_RSA)
{
sprintf (numbuf, "%u", nbits);
snprintf ((char*)keyparms, DIM (keyparms),
"(6:genkey(3:rsa(5:nbits%d:%s)))",
(int)strlen (numbuf), numbuf);
}
else if (algo == GCRY_PK_ECC)
{
const char *curve = get_parameter_value (para, pKEYCURVE, 0);
const char *flags;
if (!strcmp (curve, "Ed25519"))
flags = "(5:flags5:eddsa)";
else if (!strcmp (curve, "Curve25519"))
flags = "(5:flags9:djb-tweak)";
else
flags = "";
snprintf ((char*)keyparms, DIM (keyparms),
"(6:genkey(3:ecc(5:curve%zu:%s)%s))",
strlen (curve), curve, flags);
}
else
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: invalid algorithm\n"), r?r->lnr:0);
xfree (sigkey);
xfree (cardkeyid);
return gpg_error (GPG_ERR_INV_PARAMETER);
}
rc = gpgsm_agent_genkey (ctrl, keyparms, &public);
if (rc)
{
r = get_parameter (para, pKEYTYPE, 0);
log_error (_("line %d: key generation failed: %s <%s>\n"),
r?r->lnr:0, gpg_strerror (rc), gpg_strsource (rc));
xfree (sigkey);
xfree (cardkeyid);
return rc;
}
}
if (!outctrl->dryrun)
{
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
int create_cert ;
create_cert = !!get_parameter_value (para, pSERIAL, 0);
ctrl->pem_name = create_cert? "CERTIFICATE" : "CERTIFICATE REQUEST";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
log_error ("can't create writer: %s\n", gpg_strerror (rc));
else
{
rc = create_request (ctrl, para, cardkeyid, public, sigkey, writer);
if (!rc)
{
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
log_error ("write failed: %s\n", gpg_strerror (rc));
else
{
gpgsm_status (ctrl, STATUS_KEY_CREATED, "P");
log_info ("certificate%s created\n",
create_cert?"":" request");
}
}
gnupg_ksba_destroy_writer (b64writer);
}
}
xfree (sigkey);
xfree (public);
xfree (cardkeyid);
return rc;
}
/* Parameters are checked, the key pair has been created. Now
generate the request and write it out */
static int
create_request (ctrl_t ctrl,
struct para_data_s *para,
const char *carddirect,
ksba_const_sexp_t public,
ksba_const_sexp_t sigkey,
ksba_writer_t writer)
{
ksba_certreq_t cr;
gpg_error_t err;
gcry_md_hd_t md;
ksba_stop_reason_t stopreason;
int rc = 0;
const char *s, *string;
unsigned int use;
int seq;
char *buf, *p;
size_t len;
char numbuf[30];
ksba_isotime_t atime;
int certmode = 0;
int mdalgo;
membuf_t tbsbuffer;
membuf_t *tbsmb = NULL;
size_t publiclen;
size_t sigkeylen;
int publicpkalgo; /* The gcrypt public key algo of the public key. */
int sigkeypkalgo; /* The gcrypt public key algo of the signing key. */
err = ksba_certreq_new (&cr);
if (err)
return err;
publiclen = gcry_sexp_canon_len (public, 0, NULL, NULL);
sigkeylen = sigkey? gcry_sexp_canon_len (sigkey, 0, NULL, NULL) : 0;
publicpkalgo = get_pk_algo_from_canon_sexp (public, publiclen);
sigkeypkalgo = sigkey? get_pk_algo_from_canon_sexp (public, publiclen) : 0;
if (publicpkalgo == GCRY_PK_EDDSA)
{
mdalgo = GCRY_MD_SHA512;
md = NULL; /* We sign the data and not a hash. */
init_membuf (&tbsbuffer, 2048);
tbsmb = &tbsbuffer;
ksba_certreq_set_hash_function
(cr, (void (*)(void *, const void*,size_t))put_membuf, tbsmb);
}
else
{
if ((string = get_parameter_value (para, pHASHALGO, 0)))
mdalgo = gcry_md_map_name (string);
else
mdalgo = GCRY_MD_SHA256;
rc = gcry_md_open (&md, mdalgo, 0);
if (rc)
{
log_error ("md_open failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_HASHING)
gcry_md_debug (md, "cr.cri");
ksba_certreq_set_hash_function (cr, HASH_FNC, md);
}
ksba_certreq_set_writer (cr, writer);
err = ksba_certreq_add_subject (cr, get_parameter_value (para, pNAMEDN, 0));
if (err)
{
log_error ("error setting the subject's name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
for (seq=0; (s = get_parameter_value (para, pNAMEEMAIL, seq)); seq++)
{
buf = xtrymalloc (strlen (s) + 3);
if (!buf)
{
rc = out_of_core ();
goto leave;
}
*buf = '<';
strcpy (buf+1, s);
strcat (buf+1, ">");
err = ksba_certreq_add_subject (cr, buf);
xfree (buf);
if (err)
{
log_error ("error setting the subject's alternate name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
for (seq=0; (s = get_parameter_value (para, pNAMEDNS, seq)); seq++)
{
len = strlen (s);
log_assert (len);
snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len);
buf = p = xtrymalloc (11 + strlen (numbuf) + len + 3);
if (!buf)
{
rc = out_of_core ();
goto leave;
}
p = stpcpy (p, "(8:dns-name");
p = stpcpy (p, numbuf);
p = stpcpy (p, s);
strcpy (p, ")");
err = ksba_certreq_add_subject (cr, buf);
xfree (buf);
if (err)
{
log_error ("error setting the subject's alternate name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
for (seq=0; (s = get_parameter_value (para, pNAMEURI, seq)); seq++)
{
len = strlen (s);
log_assert (len);
snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len);
buf = p = xtrymalloc (6 + strlen (numbuf) + len + 3);
if (!buf)
{
rc = out_of_core ();
goto leave;
}
p = stpcpy (p, "(3:uri");
p = stpcpy (p, numbuf);
p = stpcpy (p, s);
strcpy (p, ")");
err = ksba_certreq_add_subject (cr, buf);
xfree (buf);
if (err)
{
log_error ("error setting the subject's alternate name: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
err = ksba_certreq_set_public_key (cr, public);
if (err)
{
log_error ("error setting the public key: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
/* Set key usage flags. */
use = get_parameter_uint (para, pKEYUSAGE);
if (use)
{
unsigned int mask, pos;
unsigned char der[4];
der[0] = 0x03;
der[1] = 0x02;
der[2] = 0;
der[3] = 0;
if ((use & GCRY_PK_USAGE_SIGN))
{
/* For signing only we encode the bits:
KSBA_KEYUSAGE_DIGITAL_SIGNATURE
KSBA_KEYUSAGE_NON_REPUDIATION = 0b11 -> 0b11000000 */
der[3] |= 0xc0;
}
if ((use & GCRY_PK_USAGE_ENCR))
{
/* For encrypt only we encode the bits:
KSBA_KEYUSAGE_KEY_ENCIPHERMENT
KSBA_KEYUSAGE_DATA_ENCIPHERMENT = 0b1100 -> 0b00110000 */
der[3] |= 0x30;
}
if ((use & GCRY_PK_USAGE_CERT))
{
/* For certify only we encode the bits:
KSBA_KEYUSAGE_KEY_CERT_SIGN
KSBA_KEYUSAGE_CRL_SIGN = 0b1100000 -> 0b00000110 */
der[3] |= 0x06;
}
/* Count number of unused bits. */
for (mask=1, pos=0; pos < 8 * sizeof mask; pos++, mask <<= 1)
{
if ((der[3] & mask))
break;
der[2]++;
}
err = ksba_certreq_add_extension (cr, oidstr_keyUsage, 1, der, 4);
if (err)
{
log_error ("error setting the key usage: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* See whether we want to create an X.509 certificate. */
string = get_parameter_value (para, pSERIAL, 0);
if (string)
{
certmode = 1;
/* Store the serial number. */
if (!strcmp (string, "random"))
{
char snbuf[3+8+1];
memcpy (snbuf, "(8:", 3);
gcry_create_nonce (snbuf+3, 8);
/* Clear high bit to guarantee a positive integer. */
snbuf[3] &= 0x7f;
snbuf[3+8] = ')';
err = ksba_certreq_set_serial (cr, snbuf);
}
else
{
char *hexbuf;
/* Allocate a buffer large enough to prefix the string with
a '0' so to have an even number of digits. Prepend two
further '0' so that the binary result will have a leading
0 byte and thus can't be the representation of a negative
number. Note that ksba_certreq_set_serial strips all
unneeded leading 0 bytes. */
hexbuf = p = xtrymalloc (2 + 1 + strlen (string) + 1);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
if ((strlen (string) & 1))
*p++ = '0';
*p++ = '0';
*p++ = '0';
strcpy (p, string);
for (p=hexbuf, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[len++] = xtoi_2 (p);
/* Now build the S-expression. */
snprintf (numbuf, DIM(numbuf), "%u:", (unsigned int)len);
buf = p = xtrymalloc (1 + strlen (numbuf) + len + 1 + 1);
if (!buf)
{
err = gpg_error_from_syserror ();
xfree (hexbuf);
goto leave;
}
p = stpcpy (stpcpy (buf, "("), numbuf);
memcpy (p, hexbuf, len);
p += len;
strcpy (p, ")");
xfree (hexbuf);
err = ksba_certreq_set_serial (cr, buf);
xfree (buf);
}
if (err)
{
log_error ("error setting the serial number: %s\n",
gpg_strerror (err));
goto leave;
}
/* Store the issuer DN. If no issuer DN is given and no signing
key has been set we add the standalone extension and the
basic constraints to mark it as a self-signed CA
certificate. */
string = get_parameter_value (para, pISSUERDN, 0);
if (string)
{
/* Issuer DN given. Note that this may be the same as the
subject DN and thus this could as well be a self-signed
certificate. However the caller needs to explicitly
specify basicConstraints and so forth. */
err = ksba_certreq_set_issuer (cr, string);
if (err)
{
log_error ("error setting the issuer DN: %s\n",
gpg_strerror (err));
goto leave;
}
}
else if (!string && !sigkey)
{
/* Self-signed certificate requested. Add basicConstraints
and the custom GnuPG standalone extension. */
err = ksba_certreq_add_extension (cr, oidstr_basicConstraints, 1,
"\x30\x03\x01\x01\xff", 5);
if (err)
goto leave;
err = ksba_certreq_add_extension (cr, oidstr_standaloneCertificate, 0,
"\x01\x01\xff", 3);
if (err)
goto leave;
}
/* Store the creation date. */
string = get_parameter_value (para, pNOTBEFORE, 0);
if (string)
{
if (!string2isotime (atime, string))
BUG (); /* We already checked the value. */
}
else
gnupg_get_isotime (atime);
err = ksba_certreq_set_validity (cr, 0, atime);
if (err)
{
log_error ("error setting the creation date: %s\n",
gpg_strerror (err));
goto leave;
}
/* Store the expire date. If it is not given, libksba inserts a
default value. */
string = get_parameter_value (para, pNOTAFTER, 0);
if (string)
{
if (!string2isotime (atime, string))
BUG (); /* We already checked the value. */
err = ksba_certreq_set_validity (cr, 1, atime);
if (err)
{
log_error ("error setting the expire date: %s\n",
gpg_strerror (err));
goto leave;
}
}
/* Figure out the signing algorithm. If no sigkey has been
given we set it to the public key to create a self-signed
certificate. */
if (!sigkey)
{
sigkey = public;
sigkeylen = publiclen;
sigkeypkalgo = publicpkalgo;
}
/* Set the the digestinfo aka siginfo. */
{
unsigned char *siginfo;
err = transform_sigval (sigkey, sigkeylen, mdalgo, &siginfo, NULL);
if (!err)
{
err = ksba_certreq_set_siginfo (cr, siginfo);
xfree (siginfo);
}
if (err)
{
log_error ("error setting the siginfo: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
/* Insert the AuthorityKeyId. */
string = get_parameter_value (para, pAUTHKEYID, 0);
if (string && !strcmp (string, "none"))
; /* Do not issue an AKI. */
else if (string)
{
char *hexbuf;
/* Allocate a buffer for in-place conversion. We also add 4
extra bytes space for the tags and lengths fields. */
hexbuf = xtrymalloc (4 + strlen (string) + 1);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
strcpy (hexbuf+4, string);
for (p=hexbuf+4, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[4+len++] = xtoi_2 (p);
if (len > 125)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (hexbuf);
goto leave;
}
hexbuf[0] = 0x30; /* Tag for a Sequence. */
hexbuf[1] = len+2;
hexbuf[2] = 0x80; /* Context tag for an implicit Octet string. */
hexbuf[3] = len;
err = ksba_certreq_add_extension (cr, oidstr_authorityKeyIdentifier,
0, hexbuf, 4+len);
xfree (hexbuf);
if (err)
{
log_error ("error setting the AKI: %s\n", gpg_strerror (err));
goto leave;
}
}
else if (publicpkalgo == GCRY_PK_EDDSA || publicpkalgo == GCRY_PK_ECC)
{
/* For EdDSA and ECC we add the public key as default identifier. */
const unsigned char *q;
size_t qlen, derlen;
unsigned char *der;
err = get_ecc_q_from_canon_sexp (public, publiclen, &q, &qlen);
if (err)
{
log_error ("error getting Q from public key: %s\n",
gpg_strerror (err));
goto leave;
}
if (publicpkalgo == GCRY_PK_EDDSA && qlen>32 && (qlen&1) && *q==0x40)
{
/* Skip our optional native encoding octet. */
q++;
qlen--;
}
/* FIXME: For plain ECC we should better use a compressed
* point. That requires an updated Libgcrypt. Without that
* using nistp521 won't work due to the length check below. */
if (qlen > 125)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
derlen = 4 + qlen;
der = xtrymalloc (derlen);
if (!der)
{
err = gpg_error_from_syserror ();
goto leave;
}
der[0] = 0x30; /* Sequence */
der[1] = qlen + 2;
- der[2] = 0x80; /* Context tag for an implict Octet String. */
+ der[2] = 0x80; /* Context tag for an implicit Octet String. */
der[3] = qlen;
memcpy (der+4, q, qlen);
err = ksba_certreq_add_extension (cr, oidstr_authorityKeyIdentifier,
0, der, derlen);
xfree (der);
if (err)
{
log_error ("error setting the AKI: %s\n", gpg_strerror (err));
goto leave;
}
}
/* Insert the SubjectKeyId. */
string = get_parameter_value (para, pSUBJKEYID, 0);
if (string && !strcmp (string, "none"))
; /* Do not issue an SKI. */
else if (string)
{
char *hexbuf;
/* Allocate a buffer for in-place conversion. We also add 2
extra bytes space for the tag and length field. */
hexbuf = xtrymalloc (2 + strlen (string) + 1);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
goto leave;
}
strcpy (hexbuf+2, string);
for (p=hexbuf+2, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[2+len++] = xtoi_2 (p);
if (len > 127)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
xfree (hexbuf);
goto leave;
}
hexbuf[0] = 0x04; /* Tag for an Octet string. */
hexbuf[1] = len;
err = ksba_certreq_add_extension (cr, oidstr_subjectKeyIdentifier, 0,
hexbuf, 2+len);
xfree (hexbuf);
if (err)
{
log_error ("error setting SKI: %s\n", gpg_strerror (err));
goto leave;
}
}
else if (sigkeypkalgo == GCRY_PK_EDDSA || sigkeypkalgo == GCRY_PK_ECC)
{
/* For EdDSA and ECC we add the public key as default identifier. */
const unsigned char *q;
size_t qlen, derlen;
unsigned char *der;
/* FIXME: This assumes that the to-be-certified key uses the
* same algorithm as the certification key - this is not
* always the case; in fact it is common that they
* differ. */
err = get_ecc_q_from_canon_sexp (sigkey, sigkeylen, &q, &qlen);
if (err)
{
log_error ("error getting Q from signature key: %s\n",
gpg_strerror (err));
goto leave;
}
if (sigkeypkalgo == GCRY_PK_EDDSA && qlen>32 && (qlen&1) && *q==0x40)
{
/* Skip our optional native encoding octet. */
q++;
qlen--;
}
if (qlen > 127)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
derlen = 2 + qlen;
der = xtrymalloc (derlen);
if (!der)
{
err = gpg_error_from_syserror ();
goto leave;
}
der[0] = 0x04; /* Octet String */
der[1] = qlen;
memcpy (der+2, q, qlen);
err = ksba_certreq_add_extension (cr, oidstr_subjectKeyIdentifier, 0,
der, derlen);
xfree (der);
if (err)
{
log_error ("error setting the SKI: %s\n", gpg_strerror (err));
goto leave;
}
}
/* Insert additional extensions. */
for (seq=0; (string = get_parameter_value (para, pEXTENSION, seq)); seq++)
{
char *hexbuf;
char *oidstr;
int crit = 0;
s = strpbrk (string, " \t:");
if (!s)
{
err = gpg_error (GPG_ERR_INTERNAL);
goto leave;
}
oidstr = xtrymalloc (s - string + 1);
if (!oidstr)
{
err = gpg_error_from_syserror ();
goto leave;
}
memcpy (oidstr, string, (s-string));
oidstr[(s-string)] = 0;
s++;
while (spacep (s))
s++;
if (!*s)
{
err = gpg_error (GPG_ERR_INTERNAL);
xfree (oidstr);
goto leave;
}
if (strchr ("cC", *s))
crit = 1;
s++;
while (spacep (s))
s++;
if (*s == ':')
s++;
while (spacep (s))
s++;
hexbuf = xtrystrdup (s);
if (!hexbuf)
{
err = gpg_error_from_syserror ();
xfree (oidstr);
goto leave;
}
for (p=hexbuf, len=0; p[0] && p[1]; p += 2)
((unsigned char*)hexbuf)[len++] = xtoi_2 (p);
err = ksba_certreq_add_extension (cr, oidstr, crit,
hexbuf, len);
xfree (oidstr);
xfree (hexbuf);
}
}
else
{
sigkey = public;
sigkeylen = publiclen;
sigkeypkalgo = publicpkalgo;
}
do
{
err = ksba_certreq_build (cr, &stopreason);
if (err)
{
log_error ("ksba_certreq_build failed: %s\n", gpg_strerror (err));
rc = err;
goto leave;
}
if (stopreason == KSBA_SR_NEED_SIG)
{
gcry_sexp_t s_pkey;
unsigned char grip[20];
char hexgrip[41];
unsigned char *sigval, *newsigval;
size_t siglen;
void *tbsdata;
size_t tbsdatalen;
rc = gcry_sexp_sscan (&s_pkey, NULL, (const char*)sigkey, sigkeylen);
if (rc)
{
log_error ("gcry_sexp_scan failed: %s\n", gpg_strerror (rc));
goto leave;
}
if ( !gcry_pk_get_keygrip (s_pkey, grip) )
{
rc = gpg_error (GPG_ERR_GENERAL);
log_error ("can't figure out the keygrip\n");
gcry_sexp_release (s_pkey);
goto leave;
}
gcry_sexp_release (s_pkey);
bin2hex (grip, 20, hexgrip);
if (!opt.quiet)
log_info ("about to sign the %s for key: &%s\n",
certmode? "certificate":"CSR", hexgrip);
if (carddirect && !certmode)
{
if (tbsmb)
{
tbsdata = get_membuf (tbsmb, &tbsdatalen);
tbsmb = NULL;
if (!tbsdata)
rc = gpg_error_from_syserror ();
else
rc = gpgsm_scd_pksign (ctrl, carddirect, NULL,
tbsdata, tbsdatalen, 0,
&sigval, &siglen);
xfree (tbsdata);
}
else
rc = gpgsm_scd_pksign (ctrl, carddirect, NULL,
gcry_md_read (md, mdalgo),
gcry_md_get_algo_dlen (mdalgo),
mdalgo,
&sigval, &siglen);
}
else
{
char *orig_codeset;
char *desc;
orig_codeset = i18n_switchto_utf8 ();
desc = percent_plus_escape
(_("To complete this certificate request please enter"
" the passphrase for the key you just created once"
" more.\n"));
i18n_switchback (orig_codeset);
if (tbsmb)
{
tbsdata = get_membuf (tbsmb, &tbsdatalen);
tbsmb = NULL;
if (!tbsdata)
rc = gpg_error_from_syserror ();
else
rc = gpgsm_agent_pksign (ctrl, hexgrip, desc,
tbsdata, tbsdatalen, 0,
&sigval, &siglen);
xfree (tbsdata);
}
else
rc = gpgsm_agent_pksign (ctrl, hexgrip, desc,
gcry_md_read(md, mdalgo),
gcry_md_get_algo_dlen (mdalgo),
mdalgo,
&sigval, &siglen);
xfree (desc);
}
if (rc)
{
log_error ("signing failed: %s\n", gpg_strerror (rc));
goto leave;
}
err = transform_sigval (sigval, siglen, mdalgo, &newsigval, NULL);
xfree (sigval);
if (!err)
{
err = ksba_certreq_set_sig_val (cr, newsigval);
xfree (newsigval);
}
if (err)
{
log_error ("failed to store the sig_val: %s\n",
gpg_strerror (err));
rc = err;
goto leave;
}
}
}
while (stopreason != KSBA_SR_READY);
leave:
if (tbsmb)
xfree (get_membuf (tbsmb, NULL));
gcry_md_close (md);
ksba_certreq_release (cr);
return rc;
}
/* Create a new key by reading the parameters from IN_FP. Multiple
keys may be created */
int
gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream)
{
int rc;
rc = read_parameters (ctrl, in_stream, out_stream);
if (rc)
{
log_error ("error creating certificate request: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
goto leave;
}
leave:
return rc;
}
diff --git a/sm/decrypt.c b/sm/decrypt.c
index 5a947779f..42267e0de 100644
--- a/sm/decrypt.c
+++ b/sm/decrypt.c
@@ -1,1517 +1,1517 @@
/* decrypt.c - Decrypt a message
* Copyright (C) 2001, 2003, 2010 Free Software Foundation, Inc.
* Copyright (C) 2001-2019 Werner Koch
* Copyright (C) 2015-2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../common/i18n.h"
#include "../common/tlv.h"
#include "../common/compliance.h"
struct decrypt_filter_parm_s
{
int algo;
int mode;
int blklen;
gcry_cipher_hd_t hd;
char iv[16];
size_t ivlen;
int any_data; /* did we push anything through the filter at all? */
unsigned char lastblock[16]; /* to strip the padding we have to
keep this one */
char helpblock[16]; /* needed because there is no block buffering in
libgcrypt (yet) */
int helpblocklen;
int is_de_vs; /* Helper to track CO_DE_VS state. */
};
/* Return the hash algorithm's algo id from its name given in the
* non-null termnated string in (buffer,buflen). Returns 0 on failure
* or if the algo is not known. */
static char *
string_from_gcry_buffer (gcry_buffer_t *buffer)
{
char *string;
string = xtrymalloc (buffer->len + 1);
if (!string)
return NULL;
memcpy (string, buffer->data, buffer->len);
string[buffer->len] = 0;
return string;
}
/* Helper to construct and hash the
* ECC-CMS-SharedInfo ::= SEQUENCE {
* keyInfo AlgorithmIdentifier,
* entityUInfo [0] EXPLICIT OCTET STRING OPTIONAL,
* suppPubInfo [2] EXPLICIT OCTET STRING }
* as described in RFC-5753, 7.2. */
static gpg_error_t
hash_ecc_cms_shared_info (gcry_md_hd_t hash_hd, const char *wrap_algo_str,
unsigned int keylen,
const void *ukm, unsigned int ukmlen)
{
gpg_error_t err;
void *p;
unsigned char *oid;
size_t n, oidlen, toidlen, tkeyinfo, tukmlen, tsupppubinfo;
unsigned char keylenbuf[6];
membuf_t mb = MEMBUF_ZERO;
err = ksba_oid_from_str (wrap_algo_str, &oid, &oidlen);
if (err)
return err;
toidlen = get_tlv_length (CLASS_UNIVERSAL, TAG_OBJECT_ID, 0, oidlen);
tkeyinfo = get_tlv_length (CLASS_UNIVERSAL, TAG_SEQUENCE, 1, toidlen);
tukmlen = ukm? get_tlv_length (CLASS_CONTEXT, 0, 1, ukmlen) : 0;
keylen *= 8;
keylenbuf[0] = TAG_OCTET_STRING;
keylenbuf[1] = 4;
keylenbuf[2] = (keylen >> 24);
keylenbuf[3] = (keylen >> 16);
keylenbuf[4] = (keylen >> 8);
keylenbuf[5] = keylen;
tsupppubinfo = get_tlv_length (CLASS_CONTEXT, 2, 1, sizeof keylenbuf);
put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_SEQUENCE, 1,
tkeyinfo + tukmlen + tsupppubinfo);
put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_SEQUENCE, 1,
toidlen);
put_tlv_to_membuf (&mb, CLASS_UNIVERSAL, TAG_OBJECT_ID, 0, oidlen);
put_membuf (&mb, oid, oidlen);
ksba_free (oid);
if (ukm)
{
put_tlv_to_membuf (&mb, CLASS_CONTEXT, 0, 1, ukmlen);
put_membuf (&mb, ukm, ukmlen);
}
put_tlv_to_membuf (&mb, CLASS_CONTEXT, 2, 1, sizeof keylenbuf);
put_membuf (&mb, keylenbuf, sizeof keylenbuf);
p = get_membuf (&mb, &n);
if (!p)
return gpg_error_from_syserror ();
gcry_md_write (hash_hd, p, n);
xfree (p);
return 0;
}
/* Derive a KEK (key wrapping key) using (SECRET,SECRETLEN), an
* optional (UKM,ULMLEN), the wrap algorithm WRAP_ALGO_STR in decimal
* dotted form, and the hash algorithm HASH_ALGO. On success a key of
* length KEYLEN is stored at KEY. */
gpg_error_t
ecdh_derive_kek (unsigned char *key, unsigned int keylen,
int hash_algo, const char *wrap_algo_str,
const void *secret, unsigned int secretlen,
const void *ukm, unsigned int ukmlen)
{
gpg_error_t err = 0;
unsigned int hashlen;
gcry_md_hd_t hash_hd;
unsigned char counter;
unsigned int n, ncopy;
hashlen = gcry_md_get_algo_dlen (hash_algo);
if (!hashlen)
return gpg_error (GPG_ERR_INV_ARG);
err = gcry_md_open (&hash_hd, hash_algo, 0);
if (err)
return err;
/* According to SEC1 3.6.1 we should check that
* SECRETLEN + UKMLEN + 4 < maxhashlen
* However, we have no practical limit on the hash length and thus
* there is no point in checking this. The second check that
* KEYLEN < hashlen*(2^32-1)
* is obviously also not needed.
*/
for (n=0, counter=1; n < keylen; counter++)
{
if (counter > 1)
gcry_md_reset (hash_hd);
gcry_md_write (hash_hd, secret, secretlen);
gcry_md_write (hash_hd, "\x00\x00\x00", 3); /* MSBs of counter */
gcry_md_write (hash_hd, &counter, 1);
err = hash_ecc_cms_shared_info (hash_hd, wrap_algo_str, keylen,
ukm, ukmlen);
if (err)
break;
gcry_md_final (hash_hd);
if (n + hashlen > keylen)
ncopy = keylen - n;
else
ncopy = hashlen;
memcpy (key+n, gcry_md_read (hash_hd, 0), ncopy);
n += ncopy;
}
gcry_md_close (hash_hd);
return err;
}
/* This function will modify SECRET. NBITS is the size of the curve
* which which we took from the certificate. */
static gpg_error_t
ecdh_decrypt (unsigned char *secret, size_t secretlen,
unsigned int nbits, gcry_sexp_t enc_val,
unsigned char **r_result, unsigned int *r_resultlen)
{
gpg_error_t err;
gcry_buffer_t ioarray[4] = { {0}, {0}, {0}, {0} };
char *encr_algo_str = NULL;
char *wrap_algo_str = NULL;
int hash_algo, cipher_algo;
const unsigned char *ukm; /* Alias for ioarray[2]. */
unsigned int ukmlen;
const unsigned char *data; /* Alias for ioarray[3]. */
unsigned int datalen;
unsigned int keylen;
unsigned char key[32];
gcry_cipher_hd_t cipher_hd = NULL;
unsigned char *result = NULL;
unsigned int resultlen;
*r_resultlen = 0;
*r_result = NULL;
/* Extract X from SECRET; this is the actual secret. Unless a
* smartcard diretcly returns X, it must be in the format of:
*
* 04 || X || Y
* 40 || X
* 41 || X
*/
if (secretlen < 2)
return gpg_error (GPG_ERR_BAD_DATA);
if (secretlen == (nbits+7)/8)
; /* Matches curve length - this is already the X coordinate. */
else if (*secret == 0x04)
{
secretlen--;
memmove (secret, secret+1, secretlen);
if ((secretlen & 1))
return gpg_error (GPG_ERR_BAD_DATA);
secretlen /= 2;
}
else if (*secret == 0x40 || *secret == 0x41)
{
secretlen--;
memmove (secret, secret+1, secretlen);
}
else
return gpg_error (GPG_ERR_BAD_DATA);
if (!secretlen)
return gpg_error (GPG_ERR_BAD_DATA);
if (DBG_CRYPTO)
log_printhex (secret, secretlen, "ECDH X ..:");
/* We have now the shared secret bytes in (SECRET,SECRETLEN). Now
- * we will compute the KEK using a value dervied from the secret
+ * we will compute the KEK using a value derived from the secret
* bytes. */
err = gcry_sexp_extract_param (enc_val, "enc-val",
"&'encr-algo''wrap-algo''ukm'?s",
ioarray+0, ioarray+1,
ioarray+2, ioarray+3, NULL);
if (err)
{
log_error ("extracting ECDH parameter failed: %s\n", gpg_strerror (err));
goto leave;
}
encr_algo_str = string_from_gcry_buffer (ioarray);
if (!encr_algo_str)
{
err = gpg_error_from_syserror ();
goto leave;
}
wrap_algo_str = string_from_gcry_buffer (ioarray+1);
if (!wrap_algo_str)
{
err = gpg_error_from_syserror ();
goto leave;
}
ukm = ioarray[2].data;
ukmlen = ioarray[2].len;
data = ioarray[3].data;
datalen = ioarray[3].len;
/* Check parameters. */
if (DBG_CRYPTO)
{
log_debug ("encr_algo: %s\n", encr_algo_str);
log_debug ("wrap_algo: %s\n", wrap_algo_str);
log_printhex (ukm, ukmlen, "ukm .....:");
log_printhex (data, datalen, "data ....:");
}
if (!strcmp (encr_algo_str, "1.3.132.1.11.1"))
{
/* dhSinglePass-stdDH-sha256kdf-scheme */
hash_algo = GCRY_MD_SHA256;
}
else if (!strcmp (encr_algo_str, "1.3.132.1.11.2"))
{
/* dhSinglePass-stdDH-sha384kdf-scheme */
hash_algo = GCRY_MD_SHA384;
}
else if (!strcmp (encr_algo_str, "1.3.132.1.11.3"))
{
/* dhSinglePass-stdDH-sha512kdf-scheme */
hash_algo = GCRY_MD_SHA512;
}
else if (!strcmp (encr_algo_str, "1.3.133.16.840.63.0.2"))
{
/* dhSinglePass-stdDH-sha1kdf-scheme */
hash_algo = GCRY_MD_SHA1;
}
else
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.5"))
{
cipher_algo = GCRY_CIPHER_AES128;
keylen = 16;
}
else if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.25"))
{
cipher_algo = GCRY_CIPHER_AES192;
keylen = 24;
}
else if (!strcmp (wrap_algo_str, "2.16.840.1.101.3.4.1.45"))
{
cipher_algo = GCRY_CIPHER_AES256;
keylen = 32;
}
else
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto leave;
}
err = ecdh_derive_kek (key, keylen, hash_algo, wrap_algo_str,
secret, secretlen, ukm, ukmlen);
if (err)
goto leave;
if (DBG_CRYPTO)
log_printhex (key, keylen, "KEK .....:");
/* Unwrap the key. */
if ((datalen % 8) || datalen < 16)
{
log_error ("can't use a shared secret of %u bytes for ecdh\n", datalen);
err = gpg_error (GPG_ERR_BAD_DATA);
goto leave;
}
resultlen = datalen - 8;
result = xtrymalloc_secure (resultlen);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&cipher_hd, cipher_algo, GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
{
log_error ("ecdh failed to initialize AESWRAP: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_cipher_setkey (cipher_hd, key, keylen);
wipememory (key, sizeof key);
if (err)
{
log_error ("ecdh failed in gcry_cipher_setkey: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_cipher_decrypt (cipher_hd, result, resultlen, data, datalen);
if (err)
{
log_error ("ecdh failed in gcry_cipher_decrypt: %s\n",gpg_strerror (err));
goto leave;
}
*r_resultlen = resultlen;
*r_result = result;
result = NULL;
leave:
if (result)
{
wipememory (result, resultlen);
xfree (result);
}
gcry_cipher_close (cipher_hd);
xfree (encr_algo_str);
xfree (wrap_algo_str);
xfree (ioarray[0].data);
xfree (ioarray[1].data);
xfree (ioarray[2].data);
xfree (ioarray[3].data);
return err;
}
/* Helper for pwri_decrypt to parse the derive info.
* Example data for (DER,DERLEN):
* SEQUENCE {
* OCTET STRING
* 60 76 4B E9 5E DF 3C F8 B2 F9 B6 C2 7D 5A FB 90
* 23 B6 47 DF
* INTEGER 10000
* SEQUENCE {
* OBJECT IDENTIFIER
* hmacWithSHA512 (1 2 840 113549 2 11)
* NULL
* }
* }
*/
static gpg_error_t
pwri_parse_pbkdf2 (const unsigned char *der, size_t derlen,
unsigned char const **r_salt, unsigned int *r_saltlen,
unsigned long *r_iterations,
int *r_digest)
{
gpg_error_t err;
size_t objlen, hdrlen;
int class, tag, constructed, ndef;
char *oidstr;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_SEQUENCE
|| !constructed || ndef))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
return err;
derlen = objlen;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_OCTET_STRING
|| constructed || ndef))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
return err;
*r_salt = der;
*r_saltlen = objlen;
der += objlen;
derlen -= objlen;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_INTEGER
|| constructed || ndef))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
return err;
*r_iterations = 0;
for (; objlen; objlen--)
{
*r_iterations <<= 8;
*r_iterations |= (*der++) & 0xff;
derlen--;
}
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_SEQUENCE
|| !constructed || ndef))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
return err;
derlen = objlen;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_OBJECT_ID
|| constructed || ndef))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
return err;
oidstr = ksba_oid_to_str (der, objlen);
if (!oidstr)
return gpg_error_from_syserror ();
*r_digest = gcry_md_map_name (oidstr);
if (*r_digest)
;
else if (!strcmp (oidstr, "1.2.840.113549.2.7"))
*r_digest = GCRY_MD_SHA1;
else if (!strcmp (oidstr, "1.2.840.113549.2.8"))
*r_digest = GCRY_MD_SHA224;
else if (!strcmp (oidstr, "1.2.840.113549.2.9"))
*r_digest = GCRY_MD_SHA256;
else if (!strcmp (oidstr, "1.2.840.113549.2.10"))
*r_digest = GCRY_MD_SHA384;
else if (!strcmp (oidstr, "1.2.840.113549.2.11"))
*r_digest = GCRY_MD_SHA512;
else
err = gpg_error (GPG_ERR_DIGEST_ALGO);
ksba_free (oidstr);
return err;
}
/* Password based decryption.
* ENC_VAL has the form:
* (enc-val
* (pwri
* (derive-algo <oid>) --| both are optional
* (derive-parm <der>) --|
* (encr-algo <oid>)
* (encr-parm <iv>)
* (encr-key <key>))) -- this is the encrypted session key
*
*/
static gpg_error_t
pwri_decrypt (ctrl_t ctrl, gcry_sexp_t enc_val,
unsigned char **r_result, unsigned int *r_resultlen,
struct decrypt_filter_parm_s *parm)
{
gpg_error_t err;
gcry_buffer_t ioarray[5] = { {0} };
char *derive_algo_str = NULL;
char *encr_algo_str = NULL;
const unsigned char *dparm; /* Alias for ioarray[1]. */
unsigned int dparmlen;
const unsigned char *eparm; /* Alias for ioarray[3]. */
unsigned int eparmlen;
const unsigned char *ekey; /* Alias for ioarray[4]. */
unsigned int ekeylen;
unsigned char kek[32];
unsigned int keklen;
int encr_algo;
enum gcry_cipher_modes encr_mode;
gcry_cipher_hd_t encr_hd = NULL;
unsigned char *result = NULL;
unsigned int resultlen;
unsigned int blklen;
const unsigned char *salt; /* Points int dparm. */
unsigned int saltlen;
unsigned long iterations;
int digest_algo;
char *passphrase = NULL;
*r_resultlen = 0;
*r_result = NULL;
err = gcry_sexp_extract_param (enc_val, "enc-val!pwri",
"&'derive-algo'?'derive-parm'?"
"'encr-algo''encr-parm''encr-key'",
ioarray+0, ioarray+1,
ioarray+2, ioarray+3, ioarray+4, NULL);
if (err)
{
- /* If this is not pwri element, it is likly a kekri element
+ /* If this is not pwri element, it is likely a kekri element
* which we do not yet support. Change the error back to the
* original as returned by ksba_cms_get_issuer. */
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = gpg_error (GPG_ERR_UNSUPPORTED_CMS_OBJ);
else
log_error ("extracting PWRI parameter failed: %s\n",
gpg_strerror (err));
goto leave;
}
if (ioarray[0].data)
{
derive_algo_str = string_from_gcry_buffer (ioarray+0);
if (!derive_algo_str)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
dparm = ioarray[1].data;
dparmlen = ioarray[1].len;
encr_algo_str = string_from_gcry_buffer (ioarray+2);
if (!encr_algo_str)
{
err = gpg_error_from_syserror ();
goto leave;
}
eparm = ioarray[3].data;
eparmlen = ioarray[3].len;
ekey = ioarray[4].data;
ekeylen = ioarray[4].len;
/* Check parameters. */
if (DBG_CRYPTO)
{
if (derive_algo_str)
{
log_debug ("derive algo: %s\n", derive_algo_str);
log_printhex (dparm, dparmlen, "derive parm:");
}
log_debug ("encr algo .: %s\n", encr_algo_str);
log_printhex (eparm, eparmlen, "encr parm .:");
log_printhex (ekey, ekeylen, "encr key .:");
}
if (!derive_algo_str)
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
log_info ("PWRI with no key derivation detected\n");
goto leave;
}
if (strcmp (derive_algo_str, "1.2.840.113549.1.5.12"))
{
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
log_info ("PWRI does not use PBKDF2 (but %s)\n", derive_algo_str);
goto leave;
}
digest_algo = 0; /*(silence cc warning)*/
err = pwri_parse_pbkdf2 (dparm, dparmlen,
&salt, &saltlen, &iterations, &digest_algo);
if (err)
{
log_error ("parsing PWRI parameter failed: %s\n", gpg_strerror (err));
goto leave;
}
parm->is_de_vs = (parm->is_de_vs
&& gnupg_digest_is_compliant (CO_DE_VS, digest_algo));
encr_algo = gcry_cipher_map_name (encr_algo_str);
encr_mode = gcry_cipher_mode_from_oid (encr_algo_str);
if (!encr_algo || !encr_mode)
{
log_error ("PWRI uses unknown algorithm %s\n", encr_algo_str);
err = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
parm->is_de_vs =
(parm->is_de_vs
&& gnupg_cipher_is_compliant (CO_DE_VS, encr_algo, encr_mode));
keklen = gcry_cipher_get_algo_keylen (encr_algo);
blklen = gcry_cipher_get_algo_blklen (encr_algo);
if (!keklen || keklen > sizeof kek || blklen != 16 )
{
log_error ("PWRI algorithm %s cannot be used\n", encr_algo_str);
err = gpg_error (GPG_ERR_INV_KEYLEN);
goto leave;
}
if ((ekeylen % blklen) || (ekeylen / blklen < 2))
{
/* Note that we need at least two full blocks. */
log_error ("PWRI uses a wrong length of encrypted key\n");
err = gpg_error (GPG_ERR_INV_KEYLEN);
goto leave;
}
err = gpgsm_agent_ask_passphrase
(ctrl,
i18n_utf8 (N_("Please enter the passphrase for decryption.")),
0, &passphrase);
if (err)
goto leave;
err = gcry_kdf_derive (passphrase, strlen (passphrase),
GCRY_KDF_PBKDF2, digest_algo,
salt, saltlen, iterations,
keklen, kek);
if (passphrase)
{
wipememory (passphrase, strlen (passphrase));
xfree (passphrase);
passphrase = NULL;
}
if (err)
{
log_error ("deriving key from passphrase failed: %s\n",
gpg_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
log_printhex (kek, keklen, "KEK .......:");
/* Unwrap the key. */
resultlen = ekeylen;
result = xtrymalloc_secure (resultlen);
if (!result)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_open (&encr_hd, encr_algo, encr_mode, 0);
if (err)
{
log_error ("PWRI failed to open cipher: %s\n", gpg_strerror (err));
goto leave;
}
err = gcry_cipher_setkey (encr_hd, kek, keklen);
wipememory (kek, sizeof kek);
if (!err)
err = gcry_cipher_setiv (encr_hd, ekey + ekeylen - 2 * blklen, blklen);
if (!err)
err = gcry_cipher_decrypt (encr_hd, result + ekeylen - blklen, blklen,
ekey + ekeylen - blklen, blklen);
if (!err)
err = gcry_cipher_setiv (encr_hd, result + ekeylen - blklen, blklen);
if (!err)
err = gcry_cipher_decrypt (encr_hd, result, ekeylen - blklen,
ekey, ekeylen - blklen);
/* (We assume that that eparm is the octet string with the IV) */
if (!err)
err = gcry_cipher_setiv (encr_hd, eparm, eparmlen);
if (!err)
err = gcry_cipher_decrypt (encr_hd, result, resultlen, NULL, 0);
if (err)
{
log_error ("KEK decryption failed for PWRI: %s\n", gpg_strerror (err));
goto leave;
}
if (DBG_CRYPTO)
log_printhex (result, resultlen, "Frame .....:");
if (result[0] < 8 /* At least 64 bits */
|| (result[0] % 8) /* Multiple of 64 bits */
|| result[0] > resultlen - 4 /* Not more than the size of the input */
|| ( (result[1] ^ result[4]) /* Matching check bytes. */
& (result[2] ^ result[5])
& (result[3] ^ result[6]) ) != 0xff)
{
err = gpg_error (GPG_ERR_BAD_PASSPHRASE);
goto leave;
}
*r_resultlen = result[0];
*r_result = memmove (result, result + 4, result[0]);
result = NULL;
leave:
if (result)
{
wipememory (result, resultlen);
xfree (result);
}
if (passphrase)
{
wipememory (passphrase, strlen (passphrase));
xfree (passphrase);
}
gcry_cipher_close (encr_hd);
xfree (derive_algo_str);
xfree (encr_algo_str);
xfree (ioarray[0].data);
xfree (ioarray[1].data);
xfree (ioarray[2].data);
xfree (ioarray[3].data);
xfree (ioarray[4].data);
return err;
}
/* Decrypt the session key and fill in the parm structure. The
algo and the IV is expected to be already in PARM. */
static int
prepare_decryption (ctrl_t ctrl, const char *hexkeygrip,
int pk_algo, unsigned int nbits, const char *desc,
ksba_const_sexp_t enc_val,
struct decrypt_filter_parm_s *parm)
{
char *seskey = NULL;
size_t n, seskeylen;
int pwri = !hexkeygrip && !pk_algo;
int rc;
if (DBG_CRYPTO)
log_printcanon ("decrypting:", enc_val, 0);
if (!pwri)
{
rc = gpgsm_agent_pkdecrypt (ctrl, hexkeygrip, desc, enc_val,
&seskey, &seskeylen);
if (rc)
{
log_error ("error decrypting session key: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_CRYPTO)
log_printhex (seskey, seskeylen, "DEK frame:");
}
n=0;
if (pwri) /* Password based encryption. */
{
gcry_sexp_t s_enc_val;
unsigned char *decrypted;
unsigned int decryptedlen;
rc = gcry_sexp_sscan (&s_enc_val, NULL, enc_val,
gcry_sexp_canon_len (enc_val, 0, NULL, NULL));
if (rc)
goto leave;
rc = pwri_decrypt (ctrl, s_enc_val, &decrypted, &decryptedlen, parm);
gcry_sexp_release (s_enc_val);
if (rc)
goto leave;
xfree (seskey);
seskey = decrypted;
seskeylen = decryptedlen;
}
else if (pk_algo == GCRY_PK_ECC)
{
gcry_sexp_t s_enc_val;
unsigned char *decrypted;
unsigned int decryptedlen;
rc = gcry_sexp_sscan (&s_enc_val, NULL, enc_val,
gcry_sexp_canon_len (enc_val, 0, NULL, NULL));
if (rc)
goto leave;
rc = ecdh_decrypt (seskey, seskeylen, nbits, s_enc_val,
&decrypted, &decryptedlen);
gcry_sexp_release (s_enc_val);
if (rc)
goto leave;
xfree (seskey);
seskey = decrypted;
seskeylen = decryptedlen;
}
else if (seskeylen == 32 || seskeylen == 24 || seskeylen == 16)
{
/* Smells like an AES-128, 3-DES, or AES-256 key. This might
* happen because a SC has already done the unpacking. A better
* solution would be to test for this only after we triggered
* the GPG_ERR_INV_SESSION_KEY. */
}
else
{
if (n + 7 > seskeylen )
{
rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
goto leave;
}
/* FIXME: Actually the leading zero is required but due to the way
we encode the output in libgcrypt as an MPI we are not able to
encode that leading zero. However, when using a Smartcard we are
doing it the right way and therefore we have to skip the zero. This
should be fixed in gpg-agent of course. */
if (!seskey[n])
n++;
if (seskey[n] != 2 ) /* Wrong block type version. */
{
rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
goto leave;
}
for (n++; n < seskeylen && seskey[n]; n++) /* Skip the random bytes. */
;
n++; /* and the zero byte */
if (n >= seskeylen )
{
rc = gpg_error (GPG_ERR_INV_SESSION_KEY);
goto leave;
}
}
if (DBG_CRYPTO)
{
log_printhex (seskey+n, seskeylen-n, "CEK .......:");
log_printhex (parm->iv, parm->ivlen, "IV ........:");
}
if (opt.verbose)
log_info (_("%s.%s encrypted data\n"),
gcry_cipher_algo_name (parm->algo),
cipher_mode_to_string (parm->mode));
rc = gcry_cipher_open (&parm->hd, parm->algo, parm->mode, 0);
if (rc)
{
log_error ("error creating decryptor: %s\n", gpg_strerror (rc));
goto leave;
}
rc = gcry_cipher_setkey (parm->hd, seskey+n, seskeylen-n);
if (gpg_err_code (rc) == GPG_ERR_WEAK_KEY)
{
log_info (_("WARNING: message was encrypted with "
"a weak key in the symmetric cipher.\n"));
rc = 0;
}
if (rc)
{
log_error("key setup failed: %s\n", gpg_strerror(rc) );
goto leave;
}
rc = gcry_cipher_setiv (parm->hd, parm->iv, parm->ivlen);
if (rc)
{
log_error("IV setup failed: %s\n", gpg_strerror(rc) );
goto leave;
}
if (parm->mode == GCRY_CIPHER_MODE_GCM)
{
/* GCM mode really sucks in CMS. We need to know the AAD before
* we start decrypting but CMS puts the AAD after the content.
* Thus temporary files are required. Let's hope that no real
* messages with actual AAD are ever used. OCB Rules! */
}
leave:
xfree (seskey);
return rc;
}
/* This function is called by the KSBA writer just before the actual
write is done. The function must take INLEN bytes from INBUF,
decrypt it and store it inoutbuf which has a maximum size of
maxoutlen. The valid bytes in outbuf should be return in outlen.
Due to different buffer sizes or different length of input and
output, it may happen that fewer bytes are processed or fewer bytes
are written. */
static gpg_error_t
decrypt_filter (void *arg,
const void *inbuf, size_t inlen, size_t *inused,
void *outbuf, size_t maxoutlen, size_t *outlen)
{
struct decrypt_filter_parm_s *parm = arg;
int blklen = parm->blklen;
size_t orig_inlen = inlen;
/* fixme: Should we issue an error when we have not seen one full block? */
if (!inlen)
return gpg_error (GPG_ERR_BUG);
if (maxoutlen < 2*parm->blklen)
return gpg_error (GPG_ERR_BUG);
/* Make some space because we will later need an extra block at the end. */
maxoutlen -= blklen;
if (parm->helpblocklen)
{
int i, j;
for (i=parm->helpblocklen,j=0; i < blklen && j < inlen; i++, j++)
parm->helpblock[i] = ((const char*)inbuf)[j];
inlen -= j;
if (blklen > maxoutlen)
return gpg_error (GPG_ERR_BUG);
if (i < blklen)
{
parm->helpblocklen = i;
*outlen = 0;
}
else
{
parm->helpblocklen = 0;
if (parm->any_data)
{
memcpy (outbuf, parm->lastblock, blklen);
*outlen =blklen;
}
else
*outlen = 0;
gcry_cipher_decrypt (parm->hd, parm->lastblock, blklen,
parm->helpblock, blklen);
parm->any_data = 1;
}
*inused = orig_inlen - inlen;
return 0;
}
if (inlen > maxoutlen)
inlen = maxoutlen;
if (inlen % blklen)
{ /* store the remainder away */
parm->helpblocklen = inlen%blklen;
inlen = inlen/blklen*blklen;
memcpy (parm->helpblock, (const char*)inbuf+inlen, parm->helpblocklen);
}
*inused = inlen + parm->helpblocklen;
if (inlen)
{
log_assert (inlen >= blklen);
if (parm->any_data)
{
gcry_cipher_decrypt (parm->hd, (char*)outbuf+blklen, inlen,
inbuf, inlen);
memcpy (outbuf, parm->lastblock, blklen);
memcpy (parm->lastblock,(char*)outbuf+inlen, blklen);
*outlen = inlen;
}
else
{
gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen);
memcpy (parm->lastblock, (char*)outbuf+inlen-blklen, blklen);
*outlen = inlen - blklen;
parm->any_data = 1;
}
}
else
*outlen = 0;
return 0;
}
/* This is the GCM version of decrypt_filter. */
static gpg_error_t
decrypt_gcm_filter (void *arg,
const void *inbuf, size_t inlen, size_t *inused,
void *outbuf, size_t maxoutlen, size_t *outlen)
{
struct decrypt_filter_parm_s *parm = arg;
if (!inlen)
return gpg_error (GPG_ERR_BUG);
if (maxoutlen < parm->blklen)
return gpg_error (GPG_ERR_BUG);
if (inlen > maxoutlen)
inlen = maxoutlen;
*inused = inlen;
if (inlen)
{
gcry_cipher_decrypt (parm->hd, outbuf, inlen, inbuf, inlen);
*outlen = inlen;
parm->any_data = 1;
}
else
*outlen = 0;
return 0;
}
/* Perform a decrypt operation. */
int
gpgsm_decrypt (ctrl_t ctrl, estream_t in_fp, estream_t out_fp)
{
int rc;
gnupg_ksba_io_t b64reader = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_reader_t reader;
ksba_writer_t writer;
ksba_cms_t cms = NULL;
ksba_stop_reason_t stopreason;
KEYDB_HANDLE kh;
int recp;
struct decrypt_filter_parm_s dfparm;
char *curve = NULL;
memset (&dfparm, 0, sizeof dfparm);
audit_set_type (ctrl->audit, AUDIT_TYPE_DECRYPT);
kh = keydb_new (ctrl);
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
rc = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)),
in_fp, &reader);
if (rc)
{
log_error ("can't create reader: %s\n", gpg_strerror (rc));
goto leave;
}
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, out_fp, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
gnupg_ksba_set_progress_cb (b64writer, gpgsm_progress_cb, ctrl);
if (ctrl->input_size_hint)
gnupg_ksba_set_total (b64writer, ctrl->input_size_hint);
rc = ksba_cms_new (&cms);
if (rc)
goto leave;
rc = ksba_cms_set_reader_writer (cms, reader, writer);
if (rc)
{
log_error ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (rc));
goto leave;
}
audit_log (ctrl->audit, AUDIT_SETUP_READY);
/* Parser loop. */
do
{
rc = ksba_cms_parse (cms, &stopreason);
if (rc)
{
log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA
|| stopreason == KSBA_SR_DETACHED_DATA)
{
int algo, mode;
const char *algoid;
int any_key = 0;
audit_log (ctrl->audit, AUDIT_GOT_DATA);
algoid = ksba_cms_get_content_oid (cms, 2/* encryption algo*/);
algo = gcry_cipher_map_name (algoid);
mode = gcry_cipher_mode_from_oid (algoid);
if (!algo || !mode)
{
rc = gpg_error (GPG_ERR_UNSUPPORTED_ALGORITHM);
log_error ("unsupported algorithm '%s'\n", algoid? algoid:"?");
if (algoid && !strcmp (algoid, "1.2.840.113549.3.2"))
log_info (_("(this is the RC2 algorithm)\n"));
else if (!algoid)
log_info (_("(this does not seem to be an encrypted"
" message)\n"));
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.algorithm",
numbuf, algoid?algoid:"?", NULL);
audit_log_s (ctrl->audit, AUDIT_BAD_DATA_CIPHER_ALGO, algoid);
}
/* If it seems that this is not an encrypted message we
return a more sensible error code. */
if (!algoid)
rc = gpg_error (GPG_ERR_NO_DATA);
goto leave;
}
/* Check compliance. */
if (! gnupg_cipher_is_allowed (opt.compliance, 0, algo, mode))
{
log_error (_("cipher algorithm '%s'"
" may not be used in %s mode\n"),
gcry_cipher_algo_name (algo),
gnupg_compliance_option_string (opt.compliance));
rc = gpg_error (GPG_ERR_CIPHER_ALGO);
goto leave;
}
/* For CMS, CO_DE_VS demands CBC mode. */
dfparm.is_de_vs = gnupg_cipher_is_compliant (CO_DE_VS, algo, mode);
audit_log_i (ctrl->audit, AUDIT_DATA_CIPHER_ALGO, algo);
dfparm.algo = algo;
dfparm.mode = mode;
dfparm.blklen = gcry_cipher_get_algo_blklen (algo);
if (dfparm.blklen > sizeof (dfparm.helpblock))
{
rc = gpg_error (GPG_ERR_BUG);
goto leave;
}
rc = ksba_cms_get_content_enc_iv (cms,
dfparm.iv,
sizeof (dfparm.iv),
&dfparm.ivlen);
if (rc)
{
log_error ("error getting IV: %s\n", gpg_strerror (rc));
goto leave;
}
for (recp=0; !any_key; recp++)
{
char *issuer;
ksba_sexp_t serial;
ksba_sexp_t enc_val;
char *hexkeygrip = NULL;
char *pkalgostr = NULL;
char *pkfpr = NULL;
char *desc = NULL;
char kidbuf[16+1];
int tmp_rc;
ksba_cert_t cert = NULL;
unsigned int nbits;
int pk_algo = 0;
int maybe_pwri = 0;
*kidbuf = 0;
tmp_rc = ksba_cms_get_issuer_serial (cms, recp, &issuer, &serial);
if (tmp_rc == -1 && recp)
break; /* no more recipients */
audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp);
if (gpg_err_code (tmp_rc) == GPG_ERR_UNSUPPORTED_CMS_OBJ)
{
maybe_pwri = 1;
}
else if (tmp_rc)
{
log_error ("recp %d - error getting info: %s\n",
recp, gpg_strerror (tmp_rc));
}
else
{
if (opt.verbose)
{
log_info ("recp %d - issuer: '%s'\n",
recp, issuer? issuer:"[NONE]");
log_info ("recp %d - serial: ", recp);
gpgsm_dump_serial (serial);
log_printf ("\n");
}
if (ctrl->audit)
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr);
xfree (tmpstr);
}
keydb_search_reset (kh);
rc = keydb_search_issuer_sn (ctrl, kh, issuer, serial);
if (rc)
{
log_error ("failed to find the certificate: %s\n",
gpg_strerror(rc));
goto oops;
}
rc = keydb_get_cert (kh, &cert);
if (rc)
{
log_error ("failed to get cert: %s\n", gpg_strerror (rc));
goto oops;
}
/* Print the ENC_TO status line. Note that we can
do so only if we have the certificate. This is
in contrast to gpg where the keyID is commonly
included in the encrypted messages. It is too
cumbersome to retrieve the used algorithm, thus
we don't print it for now. We also record the
keyid for later use. */
{
unsigned long kid[2];
kid[0] = gpgsm_get_short_fingerprint (cert, kid+1);
snprintf (kidbuf, sizeof kidbuf, "%08lX%08lX",
kid[1], kid[0]);
gpgsm_status2 (ctrl, STATUS_ENC_TO,
kidbuf, "0", "0", NULL);
}
/* Put the certificate into the audit log. */
audit_log_cert (ctrl->audit, AUDIT_SAVE_CERT, cert, 0);
/* Just in case there is a problem with the own
certificate we print this message - should never
happen of course */
rc = gpgsm_cert_use_decrypt_p (cert);
if (rc)
{
char numbuf[50];
sprintf (numbuf, "%d", rc);
gpgsm_status2 (ctrl, STATUS_ERROR, "decrypt.keyusage",
numbuf, NULL);
rc = 0;
}
hexkeygrip = gpgsm_get_keygrip_hexstring (cert);
desc = gpgsm_format_keydesc (cert);
pkfpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
pkalgostr = gpgsm_pubkey_algo_string (cert, NULL);
xfree (curve);
pk_algo = gpgsm_get_key_algo_info (cert, &nbits, &curve);
if (!opt.quiet)
log_info (_("encrypted to %s key %s\n"), pkalgostr, pkfpr);
/* Check compliance. */
if (!gnupg_pk_is_allowed (opt.compliance,
PK_USE_DECRYPTION,
pk_algo, PK_ALGO_FLAG_ECC18,
NULL, nbits, curve))
{
char kidstr[10+1];
snprintf (kidstr, sizeof kidstr, "0x%08lX",
gpgsm_get_short_fingerprint (cert, NULL));
log_info (_("key %s is not suitable for decryption"
" in %s mode\n"),
kidstr,
gnupg_compliance_option_string(opt.compliance));
rc = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto oops;
}
/* Check that all certs are compliant with CO_DE_VS. */
dfparm.is_de_vs =
(dfparm.is_de_vs
&& gnupg_pk_is_compliant (CO_DE_VS, pk_algo, 0,
NULL, nbits, curve));
oops:
if (rc)
{
/* We cannot check compliance of certs that we
* don't have. */
dfparm.is_de_vs = 0;
}
xfree (issuer);
xfree (serial);
ksba_cert_release (cert);
}
if ((!hexkeygrip || !pk_algo) && !maybe_pwri)
;
else if (!(enc_val = ksba_cms_get_enc_val (cms, recp)))
{
log_error ("recp %d - error getting encrypted session key\n",
recp);
if (maybe_pwri)
log_info ("(possibly unsupported KEK info)\n");
}
else
{
if (maybe_pwri && opt.verbose)
log_info ("recp %d - KEKRI or PWRI\n", recp);
rc = prepare_decryption (ctrl, hexkeygrip, pk_algo, nbits,
desc, enc_val, &dfparm);
xfree (enc_val);
if (rc)
{
log_info ("decrypting session key failed: %s\n",
gpg_strerror (rc));
if (gpg_err_code (rc) == GPG_ERR_NO_SECKEY && *kidbuf)
gpgsm_status2 (ctrl, STATUS_NO_SECKEY, kidbuf, NULL);
}
else
{ /* setup the bulk decrypter */
any_key = 1;
ksba_writer_set_filter
(writer,
dfparm.mode == GCRY_CIPHER_MODE_GCM?
decrypt_gcm_filter : decrypt_filter,
&dfparm);
if (dfparm.is_de_vs
&& gnupg_gcrypt_is_compliant (CO_DE_VS))
gpgsm_status (ctrl, STATUS_DECRYPTION_COMPLIANCE_MODE,
gnupg_status_compliance_flag (CO_DE_VS));
else if (opt.require_compliance
&& opt.compliance == CO_DE_VS)
{
log_error (_("operation forced to fail due to"
" unfulfilled compliance rules\n"));
gpgsm_errors_seen = 1;
}
}
audit_log_ok (ctrl->audit, AUDIT_RECP_RESULT, rc);
}
xfree (pkalgostr);
xfree (pkfpr);
xfree (hexkeygrip);
xfree (desc);
}
/* If we write an audit log add the unused recipients to the
log as well. */
if (ctrl->audit && any_key)
{
for (;; recp++)
{
char *issuer;
ksba_sexp_t serial;
int tmp_rc;
tmp_rc = ksba_cms_get_issuer_serial (cms, recp,
&issuer, &serial);
if (tmp_rc == -1)
break; /* no more recipients */
audit_log_i (ctrl->audit, AUDIT_NEW_RECP, recp);
if (tmp_rc)
log_error ("recp %d - error getting info: %s\n",
recp, gpg_strerror (tmp_rc));
else
{
char *tmpstr = gpgsm_format_sn_issuer (serial, issuer);
audit_log_s (ctrl->audit, AUDIT_RECP_NAME, tmpstr);
xfree (tmpstr);
xfree (issuer);
xfree (serial);
}
}
}
if (!any_key)
{
if (!rc)
rc = gpg_error (GPG_ERR_NO_SECKEY);
goto leave;
}
}
else if (stopreason == KSBA_SR_END_DATA)
{
ksba_writer_set_filter (writer, NULL, NULL);
if (dfparm.mode == GCRY_CIPHER_MODE_GCM)
{
/* Nothing yet to do. We wait for the ready event. */
}
else if (dfparm.any_data )
{ /* write the last block with padding removed */
int i, npadding = dfparm.lastblock[dfparm.blklen-1];
if (!npadding || npadding > dfparm.blklen)
{
log_error ("invalid padding with value %d\n", npadding);
rc = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
rc = ksba_writer_write (writer,
dfparm.lastblock,
dfparm.blklen - npadding);
if (rc)
goto leave;
for (i=dfparm.blklen - npadding; i < dfparm.blklen; i++)
{
if (dfparm.lastblock[i] != npadding)
{
log_error ("inconsistent padding\n");
rc = gpg_error (GPG_ERR_INV_DATA);
goto leave;
}
}
}
}
else if (stopreason == KSBA_SR_READY)
{
if (dfparm.mode == GCRY_CIPHER_MODE_GCM)
{
char *authtag;
size_t authtaglen;
rc = ksba_cms_get_message_digest (cms, 0, &authtag, &authtaglen);
if (rc)
{
log_error ("error getting authtag: %s\n", gpg_strerror (rc));
goto leave;
}
if (DBG_CRYPTO)
log_printhex (authtag, authtaglen, "Authtag ...:");
rc = gcry_cipher_checktag (dfparm.hd, authtag, authtaglen);
xfree (authtag);
if (rc)
log_error ("data is not authentic: %s\n", gpg_strerror (rc));
goto leave;
}
}
}
while (stopreason != KSBA_SR_READY);
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_status (ctrl, STATUS_DECRYPTION_OKAY, NULL);
leave:
audit_log_ok (ctrl->audit, AUDIT_DECRYPTION_RESULT, rc);
if (rc)
{
gpgsm_status (ctrl, STATUS_DECRYPTION_FAILED, NULL);
log_error ("message decryption failed: %s <%s>\n",
gpg_strerror (rc), gpg_strsource (rc));
}
xfree (curve);
ksba_cms_release (cms);
gnupg_ksba_destroy_reader (b64reader);
gnupg_ksba_destroy_writer (b64writer);
keydb_release (kh);
if (dfparm.hd)
gcry_cipher_close (dfparm.hd);
return rc;
}
diff --git a/sm/export.c b/sm/export.c
index a6ba40f5d..7b188c2fe 100644
--- a/sm/export.c
+++ b/sm/export.c
@@ -1,773 +1,773 @@
/* export.c - Export certificates and private keys.
* Copyright (C) 2002, 2003, 2004, 2007, 2009,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../common/exechelp.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "minip12.h"
/* A table to store a fingerprint as used in a duplicates table. We
don't need to hash here because a fingerprint is already a perfect
hash value. This we use the most significant bits to index the
table and then use a linked list for the overflow. Possible
enhancement for very large number of certificates: Add a second
level table and then resort to a linked list. */
struct duptable_s
{
struct duptable_s *next;
/* Note that we only need to store 19 bytes because the first byte
is implicitly given by the table index (we require at least 8
bits). */
unsigned char fpr[19];
};
typedef struct duptable_s *duptable_t;
#define DUPTABLE_BITS 12
#define DUPTABLE_SIZE (1 << DUPTABLE_BITS)
static void print_short_info (ksba_cert_t cert, estream_t stream);
static gpg_error_t export_p12 (ctrl_t ctrl,
const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip,
int rawmode,
void **r_result, size_t *r_resultlen);
/* Create a table used to indetify duplicated certificates. */
static duptable_t *
create_duptable (void)
{
return xtrycalloc (DUPTABLE_SIZE, sizeof (duptable_t));
}
static void
destroy_duptable (duptable_t *table)
{
int idx;
duptable_t t, t2;
if (table)
{
for (idx=0; idx < DUPTABLE_SIZE; idx++)
for (t = table[idx]; t; t = t2)
{
t2 = t->next;
xfree (t);
}
xfree (table);
}
}
/* Insert the 20 byte fingerprint FPR into TABLE. Sets EXITS to true
if the fingerprint already exists in the table. */
static gpg_error_t
insert_duptable (duptable_t *table, unsigned char *fpr, int *exists)
{
size_t idx;
duptable_t t;
*exists = 0;
idx = fpr[0];
#if DUPTABLE_BITS > 16 || DUPTABLE_BITS < 8
#error cannot handle a table larger than 16 bits or smaller than 8 bits
#elif DUPTABLE_BITS > 8
idx <<= (DUPTABLE_BITS - 8);
idx |= (fpr[1] & ~(~0U << 4));
#endif
for (t = table[idx]; t; t = t->next)
if (!memcmp (t->fpr, fpr+1, 19))
break;
if (t)
{
*exists = 1;
return 0;
}
/* Insert that fingerprint. */
t = xtrymalloc (sizeof *t);
if (!t)
return gpg_error_from_syserror ();
memcpy (t->fpr, fpr+1, 19);
t->next = table[idx];
table[idx] = t;
return 0;
}
/* Export all certificates or just those given in NAMES. The output
is written to STREAM. */
void
gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream)
{
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
strlist_t sl;
ksba_cert_t cert = NULL;
int rc=0;
int count = 0;
int i;
duptable_t *dtable;
dtable = create_duptable ();
if (!dtable)
{
log_error ("creating duplicates table failed: %s\n", strerror (errno));
goto leave;
}
hd = keydb_new (ctrl);
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
{
log_error ("key '%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* If all specifications are done by fingerprint or keygrip, we
switch to ephemeral mode so that _all_ currently available and
matching certificates are exported. */
if (names && ndesc)
{
for (i=0; (i < ndesc
&& (desc[i].mode == KEYDB_SEARCH_MODE_FPR
|| desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
;
if (i == ndesc)
keydb_set_ephemeral (hd, 1);
}
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
unsigned char fpr[20];
int exists;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
gpgsm_get_fingerprint (cert, 0, fpr, NULL);
rc = insert_duptable (dtable, fpr, &exists);
if (rc)
{
log_error ("inserting into duplicates table failed: %s\n",
gpg_strerror (rc));
goto leave;
}
if (!exists && count && !ctrl->create_pem)
{
log_info ("exporting more than one certificate "
"is not possible in binary mode\n");
log_info ("ignoring other certificates\n");
break;
}
if (!exists)
{
const unsigned char *image;
size_t imagelen;
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
if (count)
es_putc ('\n', stream);
print_short_info (cert, stream);
es_putc ('\n', stream);
}
count++;
if (!b64writer)
{
ctrl->pem_name = "CERTIFICATE";
rc = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 :0)),
ctrl->pem_name, stream, &writer);
if (rc)
{
log_error ("can't create writer: %s\n", gpg_strerror (rc));
goto leave;
}
}
rc = ksba_writer_write (writer, image, imagelen);
if (rc)
{
log_error ("write error: %s\n", gpg_strerror (rc));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
gnupg_ksba_destroy_writer (b64writer);
b64writer = NULL;
}
}
ksba_cert_release (cert);
cert = NULL;
}
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
else if (b64writer)
{
rc = gnupg_ksba_finish_writer (b64writer);
if (rc)
{
log_error ("write failed: %s\n", gpg_strerror (rc));
goto leave;
}
}
leave:
gnupg_ksba_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (desc);
keydb_release (hd);
destroy_duptable (dtable);
}
/* Export a certificate and its private key. RAWMODE controls the
actual output:
- 0 - Private key and certifciate in PKCS#12 format
+ 0 - Private key and certificate in PKCS#12 format
1 - Only unencrypted private key in PKCS#8 format
2 - Only unencrypted private key in PKCS#1 format
*/
void
gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream, int rawmode)
{
gpg_error_t err = 0;
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
gnupg_ksba_io_t b64writer = NULL;
ksba_writer_t writer;
ksba_cert_t cert = NULL;
const unsigned char *image;
size_t imagelen;
char *keygrip = NULL;
char *prompt;
void *data;
size_t datalen;
hd = keydb_new (ctrl);
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
desc = xtrycalloc (1, sizeof *desc);
if (!desc)
{
log_error ("allocating memory for export failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
err = classify_user_id (name, desc, 0);
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
/* Lookup the certificate and make sure that it is unique. */
err = keydb_search (ctrl, hd, desc, 1);
if (!err)
{
err = keydb_get_cert (hd, &cert);
if (err)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (err));
goto leave;
}
next_ambiguous:
err = keydb_search (ctrl, hd, desc, 1);
if (!err)
{
ksba_cert_t cert2 = NULL;
if (!keydb_get_cert (hd, &cert2))
{
if (gpgsm_certs_identical_p (cert, cert2))
{
ksba_cert_release (cert2);
goto next_ambiguous;
}
ksba_cert_release (cert2);
}
err = gpg_error (GPG_ERR_AMBIGUOUS_NAME);
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
err = 0;
if (err)
{
log_error ("key '%s' not found: %s\n",
name, gpg_strerror (err));
goto leave;
}
}
keygrip = gpgsm_get_keygrip_hexstring (cert);
if (!keygrip || gpgsm_agent_havekey (ctrl, keygrip))
{
/* Note, that the !keygrip case indicates a bad certificate. */
err = gpg_error (GPG_ERR_NO_SECKEY);
log_error ("can't export key '%s': %s\n", name, gpg_strerror (err));
goto leave;
}
image = ksba_cert_get_image (cert, &imagelen);
if (!image)
{
log_error ("ksba_cert_get_image failed\n");
goto leave;
}
if (ctrl->create_pem)
{
print_short_info (cert, stream);
es_putc ('\n', stream);
}
if (opt.p12_charset && ctrl->create_pem && !rawmode)
{
es_fprintf (stream, "The passphrase is %s encoded.\n\n",
opt.p12_charset);
}
if (rawmode == 0)
ctrl->pem_name = "PKCS12";
else if (gpgsm_get_key_algo_info (cert, NULL, NULL) == GCRY_PK_ECC)
ctrl->pem_name = "EC PRIVATE KEY";
else if (rawmode == 1)
ctrl->pem_name = "PRIVATE KEY";
else
ctrl->pem_name = "RSA PRIVATE KEY";
err = gnupg_ksba_create_writer
(&b64writer, ((ctrl->create_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->create_base64? GNUPG_KSBA_IO_BASE64 : 0)),
ctrl->pem_name, stream, &writer);
if (err)
{
log_error ("can't create writer: %s\n", gpg_strerror (err));
goto leave;
}
prompt = gpgsm_format_keydesc (cert);
err = export_p12 (ctrl, image, imagelen, prompt, keygrip, rawmode,
&data, &datalen);
xfree (prompt);
if (err)
goto leave;
err = ksba_writer_write (writer, data, datalen);
xfree (data);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
if (ctrl->create_pem)
{
/* We want one certificate per PEM block */
err = gnupg_ksba_finish_writer (b64writer);
if (err)
{
log_error ("write failed: %s\n", gpg_strerror (err));
goto leave;
}
gnupg_ksba_destroy_writer (b64writer);
b64writer = NULL;
}
ksba_cert_release (cert);
cert = NULL;
leave:
gnupg_ksba_destroy_writer (b64writer);
ksba_cert_release (cert);
xfree (keygrip);
xfree (desc);
keydb_release (hd);
}
-/* Print some info about the certifciate CERT to FP or STREAM */
+/* Print some info about the certificate CERT to FP or STREAM */
static void
print_short_info (ksba_cert_t cert, estream_t stream)
{
char *p;
ksba_sexp_t sexp;
int idx;
for (idx=0; (p = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs ((!idx
? "Issuer ...: "
: "\n aka ...: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
es_fputs ("Serial ...: ", stream);
sexp = ksba_cert_get_serial (cert);
if (sexp)
{
int len;
const unsigned char *s = sexp;
if (*s == '(')
{
s++;
for (len=0; *s && *s != ':' && digitp (s); s++)
len = len*10 + atoi_1 (s);
if (*s == ':')
es_write_hexstring (stream, s+1, len, 0, NULL);
}
xfree (sexp);
}
es_putc ('\n', stream);
for (idx=0; (p = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs ((!idx
? "Subject ..: "
: "\n aka ..: "), stream);
gpgsm_es_print_name (stream, p);
xfree (p);
}
es_putc ('\n', stream);
p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
es_fprintf (stream, "Keygrip ..: %s\n", p);
xfree (p);
}
}
/* Parse a private key S-expression and return a malloced array with
the RSA parameters in pkcs#12 order. The caller needs to
deep-release this array. */
static gcry_mpi_t *
sexp_to_kparms (gcry_sexp_t sexp)
{
gcry_sexp_t list, l2;
const char *name;
const char *s;
size_t n;
int idx;
gcry_mpi_t *array;
list = gcry_sexp_find_token (sexp, "private-key", 0 );
if(!list)
return NULL;
l2 = gcry_sexp_cadr (list);
gcry_sexp_release (list);
list = l2;
name = gcry_sexp_nth_data (list, 0, &n);
if (name && n == 3 && !memcmp (name, "rsa", 3))
{
/* Parameter names used with RSA in the pkcs#12 order. */
const char *elems = "nedqp--u";
array = xtrycalloc (strlen(elems) + 1, sizeof *array);
if (!array)
{
gcry_sexp_release (list);
return NULL;
}
for (idx=0, s=elems; *s; s++, idx++ )
{
if (*s == '-')
continue; /* Computed below */
l2 = gcry_sexp_find_token (list, s, 1);
if (l2)
{
array[idx] = gcry_sexp_nth_mpi (l2, 1, GCRYMPI_FMT_USG);
gcry_sexp_release (l2);
}
if (!array[idx]) /* Required parameter not found or invalid. */
{
for (idx=0; array[idx]; idx++)
gcry_mpi_release (array[idx]);
xfree (array);
gcry_sexp_release (list);
return NULL;
}
}
array[5] = gcry_mpi_snew (0); /* compute d mod (q-1) */
gcry_mpi_sub_ui (array[5], array[3], 1);
gcry_mpi_mod (array[5], array[2], array[5]);
array[6] = gcry_mpi_snew (0); /* compute d mod (p-1) */
gcry_mpi_sub_ui (array[6], array[4], 1);
gcry_mpi_mod (array[6], array[2], array[6]);
}
else if (name && n == 3 && !memcmp (name, "ecc", 3))
{
array = xtrycalloc (3 + 1, sizeof *array);
if (!array)
{
gcry_sexp_release (list);
return NULL;
}
if (gcry_sexp_extract_param (list, NULL, "/'curve'qd",
array+0, array+1, array+2, NULL))
{
xfree (array);
array = NULL; /* Error. */
}
}
else
{
array = NULL;
}
gcry_sexp_release (list);
return array;
}
static gpg_error_t
export_p12 (ctrl_t ctrl, const unsigned char *certimg, size_t certimglen,
const char *prompt, const char *keygrip, int rawmode,
void **r_result, size_t *r_resultlen)
{
gpg_error_t err = 0;
void *kek = NULL;
size_t keklen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
gcry_sexp_t s_skey = NULL;
gcry_mpi_t *kparms = NULL;
unsigned char *key = NULL;
size_t keylen;
char *passphrase = NULL;
unsigned char *result = NULL;
size_t resultlen;
int i;
*r_result = NULL;
/* Get the current KEK. */
err = gpgsm_agent_keywrap_key (ctrl, 1, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Receive the wrapped key from the agent. */
err = gpgsm_agent_export_key (ctrl, keygrip, prompt,
&wrappedkey, &wrappedkeylen);
if (err)
goto leave;
/* Unwrap the key. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
if (wrappedkeylen < 24)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto leave;
}
keylen = wrappedkeylen - 8;
key = xtrymalloc_secure (keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_decrypt (cipherhd, key, keylen, wrappedkey, wrappedkeylen);
if (err)
goto leave;
xfree (wrappedkey);
wrappedkey = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
/* Convert to a gcrypt S-expression. */
err = gcry_sexp_create (&s_skey, key, keylen, 0, xfree_fnc);
if (err)
goto leave;
key = NULL; /* Key is now owned by S_KEY. */
/* Get the parameters from the S-expression. */
kparms = sexp_to_kparms (s_skey);
gcry_sexp_release (s_skey);
s_skey = NULL;
if (!kparms)
{
log_error ("error converting key parameters\n");
err = GPG_ERR_BAD_SECKEY;
goto leave;
}
if (rawmode)
{
/* Export in raw mode, that is only the pkcs#1/#8 private key. */
result = p12_raw_build (kparms, rawmode, &resultlen);
if (!result)
err = gpg_error (GPG_ERR_GENERAL);
}
else
{
err = gpgsm_agent_ask_passphrase
(ctrl,
i18n_utf8 (N_("Please enter the passphrase to protect the "
"new PKCS#12 object.")),
1, &passphrase);
if (err)
goto leave;
result = p12_build (kparms, certimg, certimglen, passphrase,
opt.p12_charset, &resultlen);
xfree (passphrase);
passphrase = NULL;
if (!result)
err = gpg_error (GPG_ERR_GENERAL);
}
leave:
xfree (key);
gcry_sexp_release (s_skey);
if (kparms)
{
for (i=0; kparms[i]; i++)
gcry_mpi_release (kparms[i]);
xfree (kparms);
}
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (kek);
if (gpg_err_code (err) == GPG_ERR_BAD_PASSPHRASE)
{
/* During export this is the passphrase used to unprotect the
key and not the pkcs#12 thing as in export. Therefore we can
issue the regular passphrase status. FIXME: replace the all
zero keyid by a regular one. */
gpgsm_status (ctrl, STATUS_BAD_PASSPHRASE, "0000000000000000");
}
if (err)
{
xfree (result);
}
else
{
*r_result = result;
*r_resultlen = resultlen;
}
return err;
}
diff --git a/sm/gpgsm.c b/sm/gpgsm.c
index b1a5f09b5..70463e734 100644
--- a/sm/gpgsm.c
+++ b/sm/gpgsm.c
@@ -1,2481 +1,2481 @@
/* gpgsm.c - GnuPG for S/MIME
* Copyright (C) 2001-2020 Free Software Foundation, Inc.
* Copyright (C) 2001-2019 Werner Koch
* Copyright (C) 2015-2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#include <fcntl.h>
/*#include <mcheck.h>*/
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpgsm.h"
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "passphrase.h"
#include "../common/shareddefs.h"
#include "../kbx/keybox.h" /* malloc hooks */
#include "../common/i18n.h"
#include "keydb.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/init.h"
#include "../common/compliance.h"
#include "../common/comopt.h"
#include "minip12.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
enum cmd_and_opt_values {
aNull = 0,
oArmor = 'a',
aDetachedSign = 'b',
aSym = 'c',
aDecrypt = 'd',
aEncr = 'e',
aListKeys = 'k',
aListSecretKeys = 'K',
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oRecipient = 'r',
aSign = 's',
oUser = 'u',
oVerbose = 'v',
oBatch = 500,
aClearsign,
aKeygen,
aSignEncr,
aDeleteKey,
aImport,
aVerify,
aListExternalKeys,
aListChain,
aSendKeys,
aRecvKeys,
aExport,
aExportSecretKeyP12,
aExportSecretKeyP8,
aExportSecretKeyRaw,
aServer,
aLearnCard,
aCallDirmngr,
aCallProtectTool,
aPasswd,
aGPGConfList,
aGPGConfTest,
aDumpKeys,
aDumpChain,
aDumpSecretKeys,
aDumpExternalKeys,
aShowCerts,
aKeydbClearSomeCertFlags,
aFingerprint,
oOptions,
oDebug,
oDebugLevel,
oDebugAll,
oDebugNone,
oDebugWait,
oDebugAllowCoreDump,
oDebugNoChainValidation,
oDebugIgnoreExpiration,
oDebugForceECDHSHA1KDF,
oLogFile,
oNoLogFile,
oAuditLog,
oHtmlAuditLog,
oLogTime,
oEnableSpecialFilenames,
oDisableFdTranslation,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oLCctype,
oLCmessages,
oXauthority,
oPreferSystemDirmngr,
oDirmngrProgram,
oDisableDirmngr,
oProtectToolProgram,
oFakedSystemTime,
oPassphraseFD,
oPinentryMode,
oRequestOrigin,
oAssumeArmor,
oAssumeBase64,
oAssumeBinary,
oInputSizeHint,
oBase64,
oNoArmor,
oP12Charset,
oCompliance,
oDisableCRLChecks,
oEnableCRLChecks,
oDisableTrustedCertCRLCheck,
oEnableTrustedCertCRLCheck,
oForceCRLRefresh,
oEnableIssuerBasedCRLCheck,
oDisableOCSP,
oEnableOCSP,
oIncludeCerts,
oPolicyFile,
oDisablePolicyChecks,
oEnablePolicyChecks,
oAutoIssuerKeyRetrieve,
oMinRSALength,
oWithFingerprint,
oWithMD5Fingerprint,
oWithKeygrip,
oWithSecret,
oWithKeyScreening,
oAnswerYes,
oAnswerNo,
oNoPrettyDN,
oKeyring,
oDefaultKey,
oDefRecipient,
oDefRecipientSelf,
oNoDefRecipient,
oStatusFD,
oCipherAlgo,
oDigestAlgo,
oExtraDigestAlgo,
oNoVerbose,
oNoSecmemWarn,
oNoDefKeyring,
oNoGreeting,
oNoTTY,
oNoOptions,
oNoBatch,
oHomedir,
oWithColons,
oWithKeyData,
oWithValidation,
oWithEphemeralKeys,
oSkipVerify,
oValidationModel,
oKeyServer,
oKeyServer_deprecated,
oEncryptTo,
oNoEncryptTo,
oLoggerFD,
oDisableCipherAlgo,
oDisablePubkeyAlgo,
oIgnoreTimeConflict,
oNoRandomSeedFile,
oNoCommonCertsImport,
oIgnoreCertExtension,
oIgnoreCertWithOID,
oAuthenticode,
oAttribute,
oChUid,
oUseKeyboxd,
oKeyboxdProgram,
oRequireCompliance,
oCompatibilityFlags,
oKbxBufferSize,
oAlwaysTrust,
oNoAutostart
};
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aSign, "sign", N_("make a signature")),
/*ARGPARSE_c (aClearsign, "clearsign", N_("make a clear text signature") ),*/
ARGPARSE_c (aDetachedSign, "detach-sign", N_("make a detached signature")),
ARGPARSE_c (aEncr, "encrypt", N_("encrypt data")),
/*ARGPARSE_c (aSym, "symmetric", N_("encryption only with symmetric cipher")),*/
ARGPARSE_c (aDecrypt, "decrypt", N_("decrypt data (default)")),
ARGPARSE_c (aVerify, "verify", N_("verify a signature")),
ARGPARSE_c (aListKeys, "list-keys", N_("list keys")),
ARGPARSE_c (aListExternalKeys, "list-external-keys",
N_("list external keys")),
ARGPARSE_c (aListSecretKeys, "list-secret-keys", N_("list secret keys")),
ARGPARSE_c (aListChain, "list-chain", N_("list certificate chain")),
ARGPARSE_c (aFingerprint, "fingerprint", N_("list keys and fingerprints")),
ARGPARSE_c (aKeygen, "generate-key", N_("generate a new key pair")),
ARGPARSE_c (aKeygen, "gen-key", "@"),
ARGPARSE_c (aDeleteKey, "delete-keys",
N_("remove keys from the public keyring")),
/*ARGPARSE_c (aSendKeys, "send-keys", N_("export keys to a keyserver")),*/
/*ARGPARSE_c (aRecvKeys, "recv-keys", N_("import keys from a keyserver")),*/
ARGPARSE_c (aImport, "import", N_("import certificates")),
ARGPARSE_c (aExport, "export", N_("export certificates")),
/* We use -raw and not -p1 for pkcs#1 secret key export so that it
won't accidentally be used in case -p12 was intended. */
ARGPARSE_c (aExportSecretKeyP12, "export-secret-key-p12", "@"),
ARGPARSE_c (aExportSecretKeyP8, "export-secret-key-p8", "@"),
ARGPARSE_c (aExportSecretKeyRaw, "export-secret-key-raw", "@"),
ARGPARSE_c (aLearnCard, "learn-card", N_("register a smartcard")),
ARGPARSE_c (aServer, "server", N_("run in server mode")),
ARGPARSE_c (aCallDirmngr, "call-dirmngr",
N_("pass a command to the dirmngr")),
ARGPARSE_c (aCallProtectTool, "call-protect-tool",
N_("invoke gpg-protect-tool")),
ARGPARSE_c (aPasswd, "change-passphrase", N_("change a passphrase")),
ARGPARSE_c (aPasswd, "passwd", "@"),
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_c (aShowCerts, "show-certs", "@"),
ARGPARSE_c (aDumpKeys, "dump-cert", "@"),
ARGPARSE_c (aDumpKeys, "dump-keys", "@"),
ARGPARSE_c (aDumpChain, "dump-chain", "@"),
ARGPARSE_c (aDumpExternalKeys, "dump-external-keys", "@"),
ARGPARSE_c (aDumpSecretKeys, "dump-secret-keys", "@"),
ARGPARSE_c (aKeydbClearSomeCertFlags, "keydb-clear-some-cert-flags", "@"),
ARGPARSE_header ("Monitor", N_("Options controlling the diagnostic output")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oNoTTY, "no-tty", N_("don't use the terminal at all")),
ARGPARSE_s_n (oNoGreeting, "no-greeting", "@"),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level",
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_n (oDebugNone, "debug-none", "@"),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugNoChainValidation, "debug-no-chain-validation", "@"),
ARGPARSE_s_n (oDebugIgnoreExpiration, "debug-ignore-expiration", "@"),
ARGPARSE_s_n (oDebugForceECDHSHA1KDF, "debug-force-ecdh-sha1kdf", "@"),
ARGPARSE_s_s (oLogFile, "log-file",
N_("|FILE|write server mode logs to FILE")),
ARGPARSE_s_n (oNoLogFile, "no-log-file", "@"),
ARGPARSE_s_i (oLoggerFD, "logger-fd", "@"),
ARGPARSE_s_n (oLogTime, "log-time", "@"),
ARGPARSE_s_n (oNoSecmemWarn, "no-secmem-warning", "@"),
ARGPARSE_header ("Configuration",
N_("Options controlling the configuration")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oFakedSystemTime, "faked-system-time", "@"),
ARGPARSE_s_n (oPreferSystemDirmngr,"prefer-system-dirmngr", "@"),
ARGPARSE_s_s (oValidationModel, "validation-model", "@"),
ARGPARSE_s_i (oIncludeCerts, "include-certs",
N_("|N|number of certificates to include") ),
ARGPARSE_s_s (oPolicyFile, "policy-file",
N_("|FILE|take policy information from FILE")),
ARGPARSE_s_s (oCompliance, "compliance", "@"),
ARGPARSE_p_u (oMinRSALength, "min-rsa-length", "@"),
ARGPARSE_s_n (oNoCommonCertsImport, "no-common-certs-import", "@"),
ARGPARSE_s_s (oIgnoreCertExtension, "ignore-cert-extension", "@"),
ARGPARSE_s_s (oIgnoreCertWithOID, "ignore-cert-with-oid", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oKeyboxdProgram, "keyboxd-program", "@"),
ARGPARSE_s_s (oDirmngrProgram, "dirmngr-program", "@"),
ARGPARSE_s_s (oProtectToolProgram, "protect-tool-program", "@"),
ARGPARSE_header ("Input", N_("Options controlling the input")),
ARGPARSE_s_n (oAssumeArmor, "assume-armor",
N_("assume input is in PEM format")),
ARGPARSE_s_n (oAssumeBase64, "assume-base64",
N_("assume input is in base-64 format")),
ARGPARSE_s_n (oAssumeBinary, "assume-binary",
N_("assume input is in binary format")),
ARGPARSE_s_s (oInputSizeHint, "input-size-hint", "@"),
ARGPARSE_header ("Output", N_("Options controlling the output")),
ARGPARSE_s_n (oArmor, "armor", N_("create ascii armored output")),
ARGPARSE_s_n (oArmor, "armour", "@"),
ARGPARSE_s_n (oNoArmor, "no-armor", "@"),
ARGPARSE_s_n (oNoArmor, "no-armour", "@"),
ARGPARSE_s_n (oBase64, "base64", N_("create base-64 encoded output")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oAuthenticode, "authenticode", "@"),
ARGPARSE_s_s (oAttribute, "attribute", "@"),
ARGPARSE_header (NULL, N_("Options to specify keys")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oDefaultKey, "default-key",
N_("|USER-ID|use USER-ID as default secret key")),
ARGPARSE_s_s (oEncryptTo, "encrypt-to",
N_("|NAME|encrypt to user ID NAME as well")),
ARGPARSE_s_n (oNoEncryptTo, "no-encrypt-to", "@"),
/* Not yet used: */
/* ARGPARSE_s_s (oDefRecipient, "default-recipient", */
/* N_("|NAME|use NAME as default recipient")), */
/* ARGPARSE_s_n (oDefRecipientSelf, "default-recipient-self", */
/* N_("use the default key as default recipient")), */
/* ARGPARSE_s_n (oNoDefRecipient, "no-default-recipient", "@"), */
ARGPARSE_s_s (oKeyring, "keyring",
N_("|FILE|add keyring to the list of keyrings")),
ARGPARSE_s_n (oNoDefKeyring, "no-default-keyring", "@"),
ARGPARSE_s_s (oKeyServer_deprecated, "ldapserver", "@"),
ARGPARSE_s_s (oKeyServer, "keyserver", "@"),
ARGPARSE_s_n (oUseKeyboxd, "use-keyboxd", "@"),
ARGPARSE_header ("ImportExport",
N_("Options controlling key import and export")),
ARGPARSE_s_n (oDisableDirmngr, "disable-dirmngr",
N_("disable all access to the dirmngr")),
ARGPARSE_s_n (oAutoIssuerKeyRetrieve, "auto-issuer-key-retrieve",
N_("fetch missing issuer certificates")),
ARGPARSE_s_s (oP12Charset, "p12-charset",
N_("|NAME|use encoding NAME for PKCS#12 passphrases")),
ARGPARSE_header ("Keylist", N_("Options controlling key listings")),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oWithKeyData,"with-key-data", "@"),
ARGPARSE_s_n (oWithValidation, "with-validation", "@"),
ARGPARSE_s_n (oWithMD5Fingerprint, "with-md5-fingerprint", "@"),
ARGPARSE_s_n (oWithEphemeralKeys, "with-ephemeral-keys", "@"),
ARGPARSE_s_n (oSkipVerify, "skip-verify", "@"),
ARGPARSE_s_n (oWithFingerprint, "with-fingerprint", "@"),
ARGPARSE_s_n (oWithKeygrip, "with-keygrip", "@"),
ARGPARSE_s_n (oWithSecret, "with-secret", "@"),
ARGPARSE_s_n (oWithKeyScreening,"with-key-screening", "@"),
ARGPARSE_s_n (oNoPrettyDN, "no-pretty-dn", "@"),
ARGPARSE_header ("Security", N_("Options controlling the security")),
ARGPARSE_s_n (oDisableCRLChecks, "disable-crl-checks",
N_("never consult a CRL")),
ARGPARSE_s_n (oEnableCRLChecks, "enable-crl-checks", "@"),
ARGPARSE_s_n (oDisableTrustedCertCRLCheck,
"disable-trusted-cert-crl-check",
N_("do not check CRLs for root certificates")),
ARGPARSE_s_n (oEnableTrustedCertCRLCheck,
"enable-trusted-cert-crl-check", "@"),
ARGPARSE_s_n (oDisableOCSP, "disable-ocsp", "@"),
ARGPARSE_s_n (oEnableOCSP, "enable-ocsp", N_("check validity using OCSP")),
ARGPARSE_s_n (oDisablePolicyChecks, "disable-policy-checks",
N_("do not check certificate policies")),
ARGPARSE_s_n (oEnablePolicyChecks, "enable-policy-checks", "@"),
ARGPARSE_s_s (oCipherAlgo, "cipher-algo",
N_("|NAME|use cipher algorithm NAME")),
ARGPARSE_s_s (oDigestAlgo, "digest-algo",
N_("|NAME|use message digest algorithm NAME")),
ARGPARSE_s_s (oExtraDigestAlgo, "extra-digest-algo", "@"),
ARGPARSE_s_s (oDisableCipherAlgo, "disable-cipher-algo", "@"),
ARGPARSE_s_s (oDisablePubkeyAlgo, "disable-pubkey-algo", "@"),
ARGPARSE_s_n (oIgnoreTimeConflict, "ignore-time-conflict", "@"),
ARGPARSE_s_n (oNoRandomSeedFile, "no-random-seed-file", "@"),
ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"),
ARGPARSE_s_n (oAlwaysTrust, "always-trust", "@"),
ARGPARSE_header (NULL, N_("Options for unattended use")),
ARGPARSE_s_n (oBatch, "batch", N_("batch mode: never ask")),
ARGPARSE_s_n (oNoBatch, "no-batch", "@"),
ARGPARSE_s_n (oAnswerYes, "yes", N_("assume yes on most questions")),
ARGPARSE_s_n (oAnswerNo, "no", N_("assume no on most questions")),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_n (oEnableSpecialFilenames, "enable-special-filenames", "@"),
ARGPARSE_s_n (oDisableFdTranslation, "disable-fd-translation", "@"),
ARGPARSE_s_i (oPassphraseFD, "passphrase-fd", "@"),
ARGPARSE_s_s (oPinentryMode, "pinentry-mode", "@"),
ARGPARSE_header (NULL, N_("Other options")),
ARGPARSE_conffile (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_noconffile (oNoOptions, "no-options", "@"),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_s (oRequestOrigin, "request-origin", "@"),
ARGPARSE_s_n (oForceCRLRefresh, "force-crl-refresh", "@"),
ARGPARSE_s_n (oEnableIssuerBasedCRLCheck, "enable-issuer-based-crl-check",
"@"),
ARGPARSE_s_s (oAuditLog, "audit-log",
N_("|FILE|write an audit log to FILE")),
ARGPARSE_s_s (oHtmlAuditLog, "html-audit-log", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oChUid, "chuid", "@"),
ARGPARSE_s_s (oCompatibilityFlags, "compatibility-flags", "@"),
ARGPARSE_p_u (oKbxBufferSize, "kbx-buffer-size", "@"),
ARGPARSE_header (NULL, ""), /* Stop the header group. */
/* Command aliases. */
ARGPARSE_c (aListKeys, "list-key", "@"),
ARGPARSE_c (aListChain, "list-signatures", "@"),
ARGPARSE_c (aListChain, "list-sigs", "@"),
ARGPARSE_c (aListChain, "check-signatures", "@"),
ARGPARSE_c (aListChain, "check-sigs", "@"),
ARGPARSE_c (aDeleteKey, "delete-key", "@"),
ARGPARSE_group (302, N_(
"@\n(See the man page for a complete listing of all commands and options)\n"
)),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_X509_VALUE , "x509" },
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_MEMORY_VALUE , "memory" },
{ DBG_CACHE_VALUE , "cache" },
{ DBG_MEMSTAT_VALUE, "memstat" },
{ DBG_HASHING_VALUE, "hashing" },
{ DBG_IPC_VALUE , "ipc" },
{ DBG_CLOCK_VALUE , "clock" },
{ DBG_LOOKUP_VALUE , "lookup" },
{ 0, NULL }
};
/* The list of compatibility flags. */
static struct compatibility_flags_s compatibility_flags [] =
{
{ COMPAT_ALLOW_KA_TO_ENCR, "allow-ka-to-encr" },
{ 0, NULL }
};
/* Global variable to keep an error count. */
int gpgsm_errors_seen = 0;
/* It is possible that we are currentlu running under setuid permissions */
static int maybe_setuid = 1;
/* Helper to implement --debug-level and --debug*/
static const char *debug_level;
static unsigned int debug_value;
/* Helper for --log-time; */
static int opt_log_time;
/* Default value for include-certs. We need an extra macro for
gpgconf-list because the variable will be changed by the command
line option.
It is often cumbersome to locate intermediate certificates, thus by
default we include all certificates in the chain. However we leave
out the root certificate because that would make it too easy for
the recipient to import that root certificate. A root certificate
should be installed only after due checks and thus it won't help to
send it along with each message. */
#define DEFAULT_INCLUDE_CERTS -2 /* Include all certs but root. */
static int default_include_certs = DEFAULT_INCLUDE_CERTS;
/* Whether the chain mode shall be used for validation. */
static int default_validation_model;
/* The default cipher algo. */
#define DEFAULT_CIPHER_ALGO "AES256"
static char *build_list (const char *text,
const char *(*mapf)(int), int (*chkf)(int));
static void set_cmd (enum cmd_and_opt_values *ret_cmd,
enum cmd_and_opt_values new_cmd );
static void emergency_cleanup (void);
static estream_t open_es_fread (const char *filename, const char *mode);
static estream_t open_es_fwrite (const char *filename);
static void run_protect_tool (int argc, char **argv);
static int
our_pk_test_algo (int algo)
{
switch (algo)
{
case GCRY_PK_RSA:
case GCRY_PK_ECDSA:
case GCRY_PK_EDDSA:
return gcry_pk_test_algo (algo);
default:
return 1;
}
}
static int
our_cipher_test_algo (int algo)
{
switch (algo)
{
case GCRY_CIPHER_3DES:
case GCRY_CIPHER_AES128:
case GCRY_CIPHER_AES192:
case GCRY_CIPHER_AES256:
case GCRY_CIPHER_SERPENT128:
case GCRY_CIPHER_SERPENT192:
case GCRY_CIPHER_SERPENT256:
case GCRY_CIPHER_SEED:
case GCRY_CIPHER_CAMELLIA128:
case GCRY_CIPHER_CAMELLIA192:
case GCRY_CIPHER_CAMELLIA256:
return gcry_cipher_test_algo (algo);
default:
return 1;
}
}
static int
our_md_test_algo (int algo)
{
switch (algo)
{
case GCRY_MD_MD5:
case GCRY_MD_SHA1:
case GCRY_MD_RMD160:
case GCRY_MD_SHA224:
case GCRY_MD_SHA256:
case GCRY_MD_SHA384:
case GCRY_MD_SHA512:
case GCRY_MD_WHIRLPOOL:
return gcry_md_test_algo (algo);
default:
return 1;
}
}
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage( int level )
{
static char *digests, *pubkeys, *ciphers;
static char *ver_gcry, *ver_ksba;
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@GPGSM@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPGSM@ [options] [files] (-h for help)");
break;
case 41:
p = _("Syntax: @GPGSM@ [options] [files]\n"
"Sign, check, encrypt or decrypt using the S/MIME protocol\n"
"Default operation depends on the input data\n");
break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 21:
if (!ver_ksba)
ver_ksba = make_libversion ("libksba", ksba_check_version);
p = ver_ksba;
break;
case 31: p = "\nHome: "; break;
case 32: p = gnupg_homedir (); break;
case 33: p = _("\nSupported algorithms:\n"); break;
case 34:
if (!ciphers)
ciphers = build_list ("Cipher: ", gnupg_cipher_algo_name,
our_cipher_test_algo );
p = ciphers;
break;
case 35:
if (!pubkeys)
pubkeys = build_list ("Pubkey: ", gcry_pk_algo_name,
our_pk_test_algo );
p = pubkeys;
break;
case 36:
if (!digests)
digests = build_list("Hash: ", gcry_md_algo_name, our_md_test_algo );
p = digests;
break;
default: p = NULL; break;
}
return p;
}
static char *
build_list (const char *text, const char * (*mapf)(int), int (*chkf)(int))
{
int i;
size_t n=strlen(text)+2;
char *list, *p;
if (maybe_setuid) {
gcry_control (GCRYCTL_DROP_PRIVS); /* drop setuid */
}
for (i=1; i < 400; i++ )
if (!chkf(i))
n += strlen(mapf(i)) + 2;
list = xmalloc (21 + n);
*list = 0;
for (p=NULL, i=1; i < 400; i++)
{
if (!chkf(i))
{
if( !p )
p = stpcpy (list, text );
else
p = stpcpy (p, ", ");
p = stpcpy (p, mapf(i) );
}
}
if (p)
strcpy (p, "\n" );
return list;
}
/* Set the file pointer into binary mode if required. */
static void
set_binary (FILE *fp)
{
#ifdef HAVE_DOSISH_SYSTEM
setmode (fileno (fp), O_BINARY);
#else
(void)fp;
#endif
}
static void
wrong_args (const char *text)
{
fprintf (stderr, _("usage: %s [options] %s\n"), GPGSM_NAME, text);
gpgsm_exit (2);
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Setup the debugging. With a DEBUG_LEVEL of NULL only the active
debug flags are propagated to the subsystems. With DEBUG_LEVEL
set, a specific set of debug flags is set; and individual debugging
flags will be added on top. */
static void
set_debug (void)
{
int numok = (debug_level && digitp (debug_level));
int numlvl = numok? atoi (debug_level) : 0;
if (!debug_level)
;
else if (!strcmp (debug_level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (debug_level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (debug_level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE|DBG_X509_VALUE;
else if (!strcmp (debug_level, "expert") || (numok && numlvl <= 8))
opt.debug = (DBG_IPC_VALUE|DBG_X509_VALUE
|DBG_CACHE_VALUE|DBG_CRYPTO_VALUE);
else if (!strcmp (debug_level, "guru") || numok)
{
opt.debug = ~0;
/* Unless the "guru" string has been used we don't want to allow
hashing debugging. The rationale is that people tend to
select the highest debug value and would then clutter their
disk with debug files which may reveal confidential data. */
if (numok)
opt.debug &= ~(DBG_HASHING_VALUE);
}
else
{
log_error (_("invalid debug-level '%s' given\n"), debug_level);
gpgsm_exit (2);
}
opt.debug |= debug_value;
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
/* minip12.c may be used outside of GnuPG, thus we don't have the
* opt structure over there. */
p12_set_verbosity (opt.verbose, opt.debug);
}
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
enum cmd_and_opt_values cmd = *ret_cmd;
if (!cmd || cmd == new_cmd)
cmd = new_cmd;
else if ( cmd == aSign && new_cmd == aEncr )
cmd = aSignEncr;
else if ( cmd == aEncr && new_cmd == aSign )
cmd = aSignEncr;
else if ( (cmd == aSign && new_cmd == aClearsign)
|| (cmd == aClearsign && new_cmd == aSign) )
cmd = aClearsign;
else
{
log_error(_("conflicting commands\n"));
gpgsm_exit(2);
}
*ret_cmd = cmd;
}
/* Helper to add recipients to a list. */
static void
do_add_recipient (ctrl_t ctrl, const char *name,
certlist_t *recplist, int is_encrypt_to, int recp_required)
{
int rc = gpgsm_add_to_certlist (ctrl, name, 0, recplist, is_encrypt_to);
if (rc)
{
if (recp_required)
{
log_error ("can't encrypt to '%s': %s\n", name, gpg_strerror (rc));
gpgsm_status2 (ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), name, NULL);
}
else
log_info (_("Note: won't be able to encrypt to '%s': %s\n"),
name, gpg_strerror (rc));
}
}
static void
parse_validation_model (const char *model)
{
int i = gpgsm_parse_validation_model (model);
if (i == -1)
log_error (_("unknown validation model '%s'\n"), model);
else
default_validation_model = i;
}
int
main ( int argc, char **argv)
{
gpg_error_t err = 0;
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
/* char *username;*/
int may_coredump;
strlist_t sl, remusr= NULL, locusr=NULL;
strlist_t nrings=NULL;
int detached_sig = 0;
char *last_configname = NULL;
const char *configname = NULL; /* NULL or points to last_configname.
* NULL also indicates that we are
* processing options from the cmdline. */
int debug_argparser = 0;
int no_more_options = 0;
int default_keyring = 1;
char *logfile = NULL;
char *auditlog = NULL;
char *htmlauditlog = NULL;
int greeting = 0;
int nogreeting = 0;
int debug_wait = 0;
int use_random_seed = 1;
int no_common_certs_import = 0;
int with_fpr = 0;
const char *forced_digest_algo = NULL;
const char *extra_digest_algo = NULL;
enum cmd_and_opt_values cmd = 0;
struct server_control_s ctrl;
certlist_t recplist = NULL;
certlist_t signerlist = NULL;
int do_not_setup_keys = 0;
int recp_required = 0;
estream_t auditfp = NULL;
estream_t htmlauditfp = NULL;
struct assuan_malloc_hooks malloc_hooks;
int pwfd = -1;
static const char *homedirvalue;
static const char *changeuser;
early_system_init ();
gnupg_reopen_std (GPGSM_NAME);
/* trap_unaligned ();*/
gnupg_rl_initialize ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to secmem_init()
somewhere after the option parsing */
log_set_prefix (GPGSM_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
/* Check that the libraries are suitable. Do it here because the
option parse may need services of the library */
if (!ksba_check_version (NEED_KSBA_VERSION) )
log_fatal (_("%s is too old (need %s, have %s)\n"), "libksba",
NEED_KSBA_VERSION, ksba_check_version (NULL) );
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
may_coredump = disable_core_dumps ();
gnupg_init_signals (0, emergency_cleanup);
dotlock_create (NULL, 0); /* Register lockfile cleanup. */
/* Tell the compliance module who we are. */
gnupg_initialize_compliance (GNUPG_MODULE_NAME_GPGSM);
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
strerror (errno));
/* Note: If you change this default cipher algorithm , please
remember to update the Gpgconflist entry as well. */
opt.def_cipher_algoid = DEFAULT_CIPHER_ALGO;
/* First check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
debug_argparser++;
break;
case oNoOptions:
/* Set here here because the homedir would otherwise be
* created before main option parsing starts. */
opt.no_homedir_creation = 1;
break;
case oHomedir:
homedirvalue = pargs.r.ret_str;
break;
case oChUid:
changeuser = pargs.r.ret_str;
break;
case aCallProtectTool:
/* Make sure that --version and --help are passed to the
* protect-tool. */
goto leave_cmdline_parser;
}
}
leave_cmdline_parser:
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* Initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are now working under our real uid
*/
ksba_set_malloc_hooks (gcry_malloc, gcry_realloc, gcry_free );
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Change UID and then set homedir. */
if (changeuser && gnupg_chuid (changeuser, 0))
log_inc_errorcount (); /* Force later termination. */
gnupg_set_homedir (homedirvalue);
/* Setup a default control structure for command line mode */
memset (&ctrl, 0, sizeof ctrl);
gpgsm_init_default_ctrl (&ctrl);
ctrl.no_server = 1;
ctrl.status_fd = -1; /* No status output. */
ctrl.autodetect_encoding = 1;
/* Set the default policy file */
opt.policy_file = make_filename (gnupg_homedir (), "policies.txt", NULL);
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
- * flags so that the internal intialized flag won't be cleared. */
+ * flags so that the internal initialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (!no_more_options
&& gpgrt_argparser (&pargs, opts, GPGSM_NAME EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
if (debug_argparser)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
break;
case aGPGConfList:
case aGPGConfTest:
set_cmd (&cmd, pargs.r_opt);
do_not_setup_keys = 1;
default_keyring = 0;
nogreeting = 1;
break;
case aServer:
opt.batch = 1;
set_cmd (&cmd, aServer);
break;
case aCallDirmngr:
opt.batch = 1;
set_cmd (&cmd, aCallDirmngr);
do_not_setup_keys = 1;
break;
case aCallProtectTool:
opt.batch = 1;
set_cmd (&cmd, aCallProtectTool);
no_more_options = 1; /* Stop parsing. */
do_not_setup_keys = 1;
break;
case aDeleteKey:
set_cmd (&cmd, aDeleteKey);
/*greeting=1;*/
do_not_setup_keys = 1;
break;
case aDetachedSign:
detached_sig = 1;
set_cmd (&cmd, aSign );
break;
case aKeygen:
set_cmd (&cmd, aKeygen);
greeting=1;
do_not_setup_keys = 1;
break;
case aImport:
case aSendKeys:
case aRecvKeys:
case aExport:
case aExportSecretKeyP12:
case aExportSecretKeyP8:
case aExportSecretKeyRaw:
case aShowCerts:
case aDumpKeys:
case aDumpChain:
case aDumpExternalKeys:
case aDumpSecretKeys:
case aListKeys:
case aListExternalKeys:
case aListSecretKeys:
case aListChain:
case aLearnCard:
case aPasswd:
case aKeydbClearSomeCertFlags:
do_not_setup_keys = 1;
set_cmd (&cmd, pargs.r_opt);
break;
case aEncr:
recp_required = 1;
set_cmd (&cmd, pargs.r_opt);
break;
case aSym:
case aDecrypt:
case aSign:
case aClearsign:
case aVerify:
set_cmd (&cmd, pargs.r_opt);
break;
/* Output encoding selection. */
case oArmor:
ctrl.create_pem = 1;
break;
case oBase64:
ctrl.create_pem = 0;
ctrl.create_base64 = 1;
break;
case oNoArmor:
ctrl.create_pem = 0;
ctrl.create_base64 = 0;
break;
case oP12Charset:
opt.p12_charset = pargs.r.ret_str;
break;
case oPassphraseFD:
pwfd = translate_sys2libc_fd_int (pargs.r.ret_int, 0);
break;
case oPinentryMode:
opt.pinentry_mode = parse_pinentry_mode (pargs.r.ret_str);
if (opt.pinentry_mode == -1)
log_error (_("invalid pinentry mode '%s'\n"), pargs.r.ret_str);
break;
case oRequestOrigin:
opt.request_origin = parse_request_origin (pargs.r.ret_str);
if (opt.request_origin == -1)
log_error (_("invalid request origin '%s'\n"), pargs.r.ret_str);
break;
/* Input encoding selection. */
case oAssumeArmor:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 1;
ctrl.is_base64 = 0;
break;
case oAssumeBase64:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 0;
ctrl.is_base64 = 1;
break;
case oAssumeBinary:
ctrl.autodetect_encoding = 0;
ctrl.is_pem = 0;
ctrl.is_base64 = 0;
break;
case oInputSizeHint:
ctrl.input_size_hint = string_to_u64 (pargs.r.ret_str);
break;
case oDisableCRLChecks:
opt.no_crl_check = 1;
break;
case oEnableCRLChecks:
opt.no_crl_check = 0;
break;
case oDisableTrustedCertCRLCheck:
opt.no_trusted_cert_crl_check = 1;
break;
case oEnableTrustedCertCRLCheck:
opt.no_trusted_cert_crl_check = 0;
break;
case oForceCRLRefresh:
opt.force_crl_refresh = 1;
break;
case oEnableIssuerBasedCRLCheck:
opt.enable_issuer_based_crl_check = 1;
break;
case oDisableOCSP:
ctrl.use_ocsp = opt.enable_ocsp = 0;
break;
case oEnableOCSP:
ctrl.use_ocsp = opt.enable_ocsp = 1;
break;
case oIncludeCerts:
ctrl.include_certs = default_include_certs = pargs.r.ret_int;
break;
case oPolicyFile:
xfree (opt.policy_file);
if (*pargs.r.ret_str)
opt.policy_file = xstrdup (pargs.r.ret_str);
else
opt.policy_file = NULL;
break;
case oDisablePolicyChecks:
opt.no_policy_check = 1;
break;
case oEnablePolicyChecks:
opt.no_policy_check = 0;
break;
case oAutoIssuerKeyRetrieve:
opt.auto_issuer_key_retrieve = 1;
break;
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oNoTTY: /* fixme:tty_no_terminal(1);*/ break;
case oDryRun: opt.dry_run = 1; break;
case oVerbose:
opt.verbose++;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oNoVerbose:
opt.verbose = 0;
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oNoLogFile: logfile = NULL; break;
case oLogTime: opt_log_time = 1; break;
case oAuditLog: auditlog = pargs.r.ret_str; break;
case oHtmlAuditLog: htmlauditlog = pargs.r.ret_str; break;
case oBatch:
opt.batch = 1;
greeting = 0;
break;
case oNoBatch: opt.batch = 0; break;
case oAnswerYes: opt.answer_yes = 1; break;
case oAnswerNo: opt.answer_no = 1; break;
case oKeyring: append_to_strlist (&nrings, pargs.r.ret_str); break;
case oUseKeyboxd: opt.use_keyboxd = 1; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &debug_value, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: debug_value = ~0; break;
case oDebugNone: debug_value = 0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
may_coredump = enable_core_dumps ();
break;
case oDebugNoChainValidation: opt.no_chain_validation = 1; break;
case oDebugIgnoreExpiration: opt.ignore_expiration = 1; break;
case oDebugForceECDHSHA1KDF: opt.force_ecdh_sha1kdf = 1; break;
case oCompatibilityFlags:
if (parse_compatibility_flags (pargs.r.ret_str, &opt.compat_flags,
compatibility_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oStatusFD:
ctrl.status_fd = translate_sys2libc_fd_int (pargs.r.ret_int, 1);
break;
case oLoggerFD:
log_set_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oWithMD5Fingerprint:
opt.with_md5_fingerprint=1; /*fall through*/
case oWithFingerprint:
with_fpr=1; /*fall through*/
case aFingerprint:
opt.fingerprint++;
break;
case oWithKeygrip:
opt.with_keygrip = 1;
break;
case oWithKeyScreening:
opt.with_key_screening = 1;
break;
case oNoPrettyDN:
opt.no_pretty_dn = 1;
break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oChUid: break; /* Command line only (see above). */
case oAgentProgram:
xfree (opt.agent_program);
opt.agent_program = make_filename (pargs.r.ret_str, NULL);
break;
case oKeyboxdProgram:
xfree (opt.keyboxd_program);
opt.keyboxd_program = make_filename (pargs.r.ret_str, NULL);
break;
case oDirmngrProgram:
xfree (opt.dirmngr_program);
opt.dirmngr_program = make_filename (pargs.r.ret_str, NULL);
break;
case oDisplay:
set_opt_session_env ("DISPLAY", pargs.r.ret_str);
break;
case oTTYname:
set_opt_session_env ("GPG_TTY", pargs.r.ret_str);
break;
case oTTYtype:
set_opt_session_env ("TERM", pargs.r.ret_str);
break;
case oXauthority:
set_opt_session_env ("XAUTHORITY", pargs.r.ret_str);
break;
case oLCctype: opt.lc_ctype = xstrdup (pargs.r.ret_str); break;
case oLCmessages: opt.lc_messages = xstrdup (pargs.r.ret_str); break;
case oDisableDirmngr: opt.disable_dirmngr = 1; break;
case oPreferSystemDirmngr: /* Obsolete */; break;
case oProtectToolProgram:
opt.protect_tool_program = pargs.r.ret_str;
break;
case oFakedSystemTime:
{
time_t faked_time = isotime2epoch (pargs.r.ret_str);
if (faked_time == (time_t)(-1))
faked_time = (time_t)strtoul (pargs.r.ret_str, NULL, 10);
gnupg_set_time (faked_time, 0);
}
break;
case oNoDefKeyring: default_keyring = 0; break;
case oNoGreeting: nogreeting = 1; break;
case oDefaultKey:
if (*pargs.r.ret_str)
{
xfree (opt.local_user);
opt.local_user = xstrdup (pargs.r.ret_str);
}
break;
case oDefRecipient:
if (*pargs.r.ret_str)
opt.def_recipient = xstrdup (pargs.r.ret_str);
break;
case oDefRecipientSelf:
xfree (opt.def_recipient);
opt.def_recipient = NULL;
opt.def_recipient_self = 1;
break;
case oNoDefRecipient:
xfree (opt.def_recipient);
opt.def_recipient = NULL;
opt.def_recipient_self = 0;
break;
case oWithKeyData: opt.with_key_data=1; /* fall through */
case oWithColons: ctrl.with_colons = 1; break;
case oWithSecret: ctrl.with_secret = 1; break;
case oWithValidation: ctrl.with_validation=1; break;
case oWithEphemeralKeys: ctrl.with_ephemeral_keys=1; break;
case oSkipVerify: opt.skip_verify=1; break;
case oNoEncryptTo: opt.no_encrypt_to = 1; break;
case oEncryptTo: /* Store the recipient in the second list */
sl = add_to_strlist (&remusr, pargs.r.ret_str);
sl->flags = 1;
break;
case oRecipient: /* store the recipient */
add_to_strlist ( &remusr, pargs.r.ret_str);
break;
case oUser: /* Store the local users, the first one is the default */
if (!opt.local_user)
opt.local_user = xstrdup (pargs.r.ret_str);
add_to_strlist (&locusr, pargs.r.ret_str);
break;
case oNoSecmemWarn:
gcry_control (GCRYCTL_DISABLE_SECMEM_WARN);
break;
case oCipherAlgo:
opt.def_cipher_algoid = pargs.r.ret_str;
break;
case oDisableCipherAlgo:
{
int algo = gcry_cipher_map_name (pargs.r.ret_str);
gcry_cipher_ctl (NULL, GCRYCTL_DISABLE_ALGO, &algo, sizeof algo);
}
break;
case oDisablePubkeyAlgo:
{
int algo = gcry_pk_map_name (pargs.r.ret_str);
gcry_pk_ctl (GCRYCTL_DISABLE_ALGO,&algo, sizeof algo );
}
break;
case oDigestAlgo:
forced_digest_algo = pargs.r.ret_str;
break;
case oExtraDigestAlgo:
extra_digest_algo = pargs.r.ret_str;
break;
case oIgnoreTimeConflict: opt.ignore_time_conflict = 1; break;
case oNoRandomSeedFile: use_random_seed = 0; break;
case oNoCommonCertsImport: no_common_certs_import = 1; break;
case oEnableSpecialFilenames:
enable_special_filenames ();
break;
case oDisableFdTranslation:
disable_translate_sys2libc_fd ();
break;
case oValidationModel: parse_validation_model (pargs.r.ret_str); break;
case oKeyServer:
append_to_strlist (&opt.keyserver, pargs.r.ret_str);
break;
case oKeyServer_deprecated:
obsolete_option (configname, pargs.lineno, "ldapserver");
break;
case oIgnoreCertExtension:
add_to_strlist (&opt.ignored_cert_extensions, pargs.r.ret_str);
break;
case oIgnoreCertWithOID:
add_to_strlist (&opt.ignore_cert_with_oid, pargs.r.ret_str);
break;
case oAuthenticode: opt.authenticode = 1; break;
case oAttribute:
add_to_strlist (&opt.attributes, pargs.r.ret_str);
break;
case oNoAutostart: opt.autostart = 0; break;
case oCompliance:
{
struct gnupg_compliance_option compliance_options[] =
{
{ "gnupg", CO_GNUPG },
{ "de-vs", CO_DE_VS }
};
int compliance = gnupg_parse_compliance_option
(pargs.r.ret_str, compliance_options, DIM (compliance_options),
opt.quiet);
if (compliance < 0)
log_inc_errorcount (); /* Force later termination. */
opt.compliance = compliance;
}
break;
case oMinRSALength: opt.min_rsa_length = pargs.r.ret_ulong; break;
case oRequireCompliance: opt.require_compliance = 1; break;
case oAlwaysTrust: opt.always_trust = 1; break;
case oKbxBufferSize:
keybox_set_buffersize (pargs.r.ret_ulong, 0);
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
{
pargs.err = ARGPARSE_PRINT_ERROR;
/* The argparse function calls a plain exit and thus we
* need to print a status here. */
gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser",
gpg_error (GPG_ERR_GENERAL));
}
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
opt.config_filename = gpgrt_fnameconcat (gnupg_homedir (),
GPGSM_NAME EXTSEP_S "conf",
NULL);
else
opt.config_filename = last_configname;
if (log_get_errorcount(0))
{
gpgsm_status_with_error (&ctrl, STATUS_FAILURE,
"option-parser", gpg_error (GPG_ERR_GENERAL));
gpgsm_exit(2);
}
/* Process common component options. */
if (parse_comopt (GNUPG_MODULE_NAME_GPGSM, debug_argparser))
{
gpgsm_status_with_error (&ctrl, STATUS_FAILURE,
"option-parser", gpg_error (GPG_ERR_GENERAL));
gpgsm_exit(2);
}
if (opt.use_keyboxd)
log_info ("Note: Please move option \"%s\" to \"common.conf\"\n",
"use-keyboxd");
opt.use_keyboxd = comopt.use_keyboxd; /* Override. */
if (opt.keyboxd_program)
log_info ("Note: Please move option \"%s\" to \"common.conf\"\n",
"keyboxd-program");
if (!opt.keyboxd_program && comopt.keyboxd_program)
{
opt.keyboxd_program = comopt.keyboxd_program;
comopt.keyboxd_program = NULL;
}
if (comopt.no_autostart)
opt.autostart = 0;
if (pwfd != -1) /* Read the passphrase now. */
read_passphrase_from_fd (pwfd);
/* Now that we have the options parsed we need to update the default
control structure. */
gpgsm_init_default_ctrl (&ctrl);
if (nogreeting)
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage(11), gpgrt_strusage(13), gpgrt_strusage(14) );
es_fprintf (es_stderr, "%s\n", gpgrt_strusage(15) );
}
#ifdef IS_DEVELOPMENT_VERSION
if (!opt.batch)
{
log_info ("NOTE: THIS IS A DEVELOPMENT VERSION!\n");
log_info ("It is only intended for test purposes and should NOT be\n");
log_info ("used in a production environment or with production keys!\n");
}
#endif
if (may_coredump && !opt.quiet)
log_info (_("WARNING: program may create a core file!\n"));
if (opt.require_compliance && opt.always_trust)
{
opt.always_trust = 0;
if (opt.quiet)
log_info (_("WARNING: %s overrides %s\n"),
"--require-compliance","--always-trust");
}
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
/* if (opt.qualsig_approval && !opt.quiet) */
/* log_info (_("This software has officially been approved to " */
/* "create and verify\n" */
/* "qualified signatures according to German law.\n")); */
if (logfile && cmd == aServer)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
else if (opt_log_time)
log_set_prefix (NULL, (GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY
|GPGRT_LOG_WITH_TIME));
if (gnupg_faked_time_p ())
{
gnupg_isotime_t tbuf;
log_info (_("WARNING: running with faked system time: "));
gnupg_get_isotime (tbuf);
dump_isotime (tbuf);
log_printf ("\n");
}
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
/*FIXME if (opt.batch) */
/* tty_batchmode (1); */
gcry_control (GCRYCTL_RESUME_SECMEM_WARN);
set_debug ();
if (opt.verbose) /* Print the compatibility flags. */
parse_compatibility_flags (NULL, &opt.compat_flags, compatibility_flags);
gnupg_set_compliance_extra_info (opt.min_rsa_length);
/* Although we always use gpgsm_exit, we better install a regular
exit handler so that at least the secure memory gets wiped
out. */
if (atexit (emergency_cleanup))
{
log_error ("atexit failed\n");
gpgsm_exit (2);
}
/* Must do this after dropping setuid, because the mapping functions
may try to load an module and we may have disabled an algorithm.
We remap the commonly used algorithms to the OIDs for
convenience. We need to work with the OIDs because they are used
to check whether the encryption mode is actually available. */
if (!strcmp (opt.def_cipher_algoid, "3DES") )
opt.def_cipher_algoid = "1.2.840.113549.3.7";
else if (!strcmp (opt.def_cipher_algoid, "AES")
|| !strcmp (opt.def_cipher_algoid, "AES128"))
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.2";
else if (!strcmp (opt.def_cipher_algoid, "AES192") )
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.22";
else if (!strcmp (opt.def_cipher_algoid, "AES256") )
opt.def_cipher_algoid = "2.16.840.1.101.3.4.1.42";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT")
|| !strcmp (opt.def_cipher_algoid, "SERPENT128") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.2";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT192") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.22";
else if (!strcmp (opt.def_cipher_algoid, "SERPENT256") )
opt.def_cipher_algoid = "1.3.6.1.4.1.11591.13.2.42";
else if (!strcmp (opt.def_cipher_algoid, "SEED") )
opt.def_cipher_algoid = "1.2.410.200004.1.4";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA")
|| !strcmp (opt.def_cipher_algoid, "CAMELLIA128") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.2";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA192") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.3";
else if (!strcmp (opt.def_cipher_algoid, "CAMELLIA256") )
opt.def_cipher_algoid = "1.2.392.200011.61.1.1.1.4";
if (cmd != aGPGConfList)
{
if ( !gcry_cipher_map_name (opt.def_cipher_algoid)
|| !gcry_cipher_mode_from_oid (opt.def_cipher_algoid))
log_error (_("selected cipher algorithm is invalid\n"));
if (forced_digest_algo)
{
opt.forced_digest_algo = gcry_md_map_name (forced_digest_algo);
if (our_md_test_algo(opt.forced_digest_algo) )
log_error (_("selected digest algorithm is invalid\n"));
}
if (extra_digest_algo)
{
opt.extra_digest_algo = gcry_md_map_name (extra_digest_algo);
if (our_md_test_algo (opt.extra_digest_algo) )
log_error (_("selected digest algorithm is invalid\n"));
}
}
/* Check our chosen algorithms against the list of allowed
* algorithms in the current compliance mode, and fail hard if it is
* not. This is us being nice to the user informing her early that
* the chosen algorithms are not available. We also check and
* enforce this right before the actual operation. */
if (! gnupg_cipher_is_allowed (opt.compliance,
cmd == aEncr || cmd == aSignEncr,
gcry_cipher_map_name (opt.def_cipher_algoid),
GCRY_CIPHER_MODE_NONE)
&& ! gnupg_cipher_is_allowed (opt.compliance,
cmd == aEncr || cmd == aSignEncr,
gcry_cipher_mode_from_oid
(opt.def_cipher_algoid),
GCRY_CIPHER_MODE_NONE))
log_error (_("cipher algorithm '%s' may not be used in %s mode\n"),
opt.def_cipher_algoid,
gnupg_compliance_option_string (opt.compliance));
if (forced_digest_algo
&& ! gnupg_digest_is_allowed (opt.compliance,
cmd == aSign
|| cmd == aSignEncr
|| cmd == aClearsign,
opt.forced_digest_algo))
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
forced_digest_algo,
gnupg_compliance_option_string (opt.compliance));
if (extra_digest_algo
&& ! gnupg_digest_is_allowed (opt.compliance,
cmd == aSign
|| cmd == aSignEncr
|| cmd == aClearsign,
opt.extra_digest_algo))
log_error (_("digest algorithm '%s' may not be used in %s mode\n"),
extra_digest_algo,
gnupg_compliance_option_string (opt.compliance));
if (log_get_errorcount(0))
{
gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-postprocessing",
gpg_error (GPG_ERR_GENERAL));
gpgsm_exit (2);
}
/* Set the random seed file. */
if (use_random_seed)
{
char *p = make_filename (gnupg_homedir (), "random_seed", NULL);
gcry_control (GCRYCTL_SET_RANDOM_SEED_FILE, p);
xfree(p);
}
if (!cmd && opt.fingerprint && !with_fpr)
set_cmd (&cmd, aListKeys);
/* If no pinentry is expected shunt
* gnupg_allow_set_foregound_window to avoid useless error
* messages on Windows. */
if (opt.pinentry_mode != PINENTRY_MODE_ASK)
{
gnupg_inhibit_set_foregound_window (1);
}
/* Add default keybox. */
if (!nrings && default_keyring && !opt.use_keyboxd)
{
int created;
keydb_add_resource (&ctrl, "pubring.kbx", 0, &created);
if (created && !no_common_certs_import)
{
/* Import the standard certificates for a new default keybox. */
char *filelist[2];
filelist[0] = make_filename (gnupg_datadir (),"com-certs.pem", NULL);
filelist[1] = NULL;
if (!gnupg_access (filelist[0], F_OK))
{
log_info (_("importing common certificates '%s'\n"),
filelist[0]);
gpgsm_import_files (&ctrl, 1, filelist, open_es_fread);
}
xfree (filelist[0]);
}
}
if (!opt.use_keyboxd)
{
for (sl = nrings; sl; sl = sl->next)
keydb_add_resource (&ctrl, sl->d, 0, NULL);
}
FREE_STRLIST(nrings);
/* Prepare the audit log feature for certain commands. */
if (auditlog || htmlauditlog)
{
switch (cmd)
{
case aEncr:
case aSign:
case aDecrypt:
case aVerify:
audit_release (ctrl.audit);
ctrl.audit = audit_new ();
if (auditlog)
auditfp = open_es_fwrite (auditlog);
if (htmlauditlog)
htmlauditfp = open_es_fwrite (htmlauditlog);
break;
default:
break;
}
}
if (!do_not_setup_keys)
{
int errcount = log_get_errorcount (0);
for (sl = locusr; sl ; sl = sl->next)
{
int rc = gpgsm_add_to_certlist (&ctrl, sl->d, 1, &signerlist, 0);
if (rc)
{
log_error (_("can't sign using '%s': %s\n"),
sl->d, gpg_strerror (rc));
gpgsm_status2 (&ctrl, STATUS_INV_SGNR,
get_inv_recpsgnr_code (rc), sl->d, NULL);
gpgsm_status2 (&ctrl, STATUS_INV_RECP,
get_inv_recpsgnr_code (rc), sl->d, NULL);
}
}
/* Build the recipient list. We first add the regular ones and then
the encrypt-to ones because the underlying function will silently
ignore duplicates and we can't allow keeping a duplicate which is
flagged as encrypt-to as the actually encrypt function would then
complain about no (regular) recipients. */
for (sl = remusr; sl; sl = sl->next)
if (!(sl->flags & 1))
do_add_recipient (&ctrl, sl->d, &recplist, 0, recp_required);
if (!opt.no_encrypt_to)
{
for (sl = remusr; sl; sl = sl->next)
if ((sl->flags & 1))
do_add_recipient (&ctrl, sl->d, &recplist, 1, recp_required);
}
/* We do not require a recipient for decryption but because
* recipients and signers are always checked and log_error is
* sometimes used (for failed signing keys or due to a failed
- * CRL checking) that would have bumbed up the error counter.
+ * CRL checking) that would have bumped up the error counter.
* We clear the counter in the decryption case because there is
* no reason to force decryption to fail. */
if (cmd == aDecrypt && !errcount)
log_get_errorcount (1); /* clear counter */
}
if (log_get_errorcount(0))
gpgsm_exit(1); /* Must stop for invalid recipients. */
/* Dispatch command. */
switch (cmd)
{
case aGPGConfList:
{ /* List default option values in the GPG Conf format. */
es_printf ("debug-level:%lu:\"none:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("include-certs:%lu:%d:\n", GC_OPT_FLAG_DEFAULT,
DEFAULT_INCLUDE_CERTS);
es_printf ("cipher-algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
DEFAULT_CIPHER_ALGO);
es_printf ("p12-charset:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("default-key:%lu:\n", GC_OPT_FLAG_DEFAULT);
es_printf ("encrypt-to:%lu:\n", GC_OPT_FLAG_DEFAULT);
/* The next one is an info only item and should match what
proc_parameters actually implements. */
es_printf ("default_pubkey_algo:%lu:\"%s:\n", GC_OPT_FLAG_DEFAULT,
"RSA-3072");
}
break;
case aGPGConfTest:
/* This is merely a dummy command to test whether the
configuration file is valid. */
break;
case aServer:
if (debug_wait)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
gpgsm_server (recplist);
break;
case aCallDirmngr:
if (!argc)
wrong_args ("--call-dirmngr <command> {args}");
else
if (gpgsm_dirmngr_run_command (&ctrl, *argv, argc-1, argv+1))
gpgsm_exit (1);
break;
case aCallProtectTool:
run_protect_tool (argc, argv);
break;
case aEncr: /* Encrypt the given file. */
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
set_binary (stdin);
if (!argc) /* Source is stdin. */
err = gpgsm_encrypt (&ctrl, recplist, es_stdin, fp);
else if (argc == 1) /* Source is the given file. */
{
estream_t data_fp = es_fopen (*argv, "rb");
if (!data_fp)
{
log_error (_("can't open '%s': %s\n"), *argv,
strerror (errno));
gpgsm_exit (2);
}
err = gpgsm_encrypt (&ctrl, recplist, data_fp, fp);
es_fclose (data_fp);
}
else
wrong_args ("--encrypt [datafile]");
if (err)
gpgrt_fcancel (fp);
else
es_fclose (fp);
}
break;
case aSign: /* Sign the given file. */
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
/* Fixme: We should also allow concatenation of multiple files for
signing because that is what gpg does.*/
set_binary (stdin);
if (!argc) /* Create from stdin. */
err = gpgsm_sign (&ctrl, signerlist, es_stdin, detached_sig, fp);
else if (argc == 1) /* From file. */
{
estream_t data_fp = es_fopen (*argv, "rb");
if (!data_fp)
{
log_error (_("can't open '%s': %s\n"), *argv,
strerror (errno));
gpgsm_exit (2);
}
err = gpgsm_sign (&ctrl, signerlist, data_fp, detached_sig, fp);
es_fclose (data_fp);
}
else
wrong_args ("--sign [datafile]");
#if GPGRT_VERSION_NUMBER >= 0x012700 /* >= 1.39 */
if (err)
gpgrt_fcancel (fp);
else
es_fclose (fp);
#else
(void)err;
es_fclose (fp);
#endif
}
break;
case aSignEncr: /* sign and encrypt the given file */
log_error ("the command '%s' has not yet been implemented\n",
"--sign --encrypt");
gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser",
gpg_error (GPG_ERR_NOT_IMPLEMENTED));
break;
case aClearsign: /* make a clearsig */
log_error ("the command '%s' has not yet been implemented\n",
"--clearsign");
gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser",
gpg_error (GPG_ERR_NOT_IMPLEMENTED));
break;
case aVerify:
{
estream_t fp = NULL;
set_binary (stdin);
if (argc == 2 && opt.outfile)
log_info ("option --output ignored for a detached signature\n");
else if (opt.outfile)
fp = open_es_fwrite (opt.outfile);
if (!argc)
/* normal signature from stdin */
gpgsm_verify (&ctrl, es_stdin, NULL, fp);
else if (argc == 1)
{
estream_t in_fp = es_fopen (*argv, "rb");
if (!in_fp)
{
log_error (_("can't open '%s': %s\n"), *argv,
strerror (errno));
gpgsm_exit (2);
}
gpgsm_verify (&ctrl, in_fp, NULL, fp); /* std signature */
es_fclose (in_fp);
}
else if (argc == 2) /* detached signature (sig, detached) */
{
estream_t in_fp = es_fopen (*argv, "rb");
estream_t data_fp = es_fopen (argv[1], "rb");
if (!in_fp)
{
log_error (_("can't open '%s': %s\n"), *argv,
strerror (errno));
gpgsm_exit (2);
}
if (!data_fp)
{
log_error (_("can't open '%s': %s\n"), argv[1],
strerror (errno));
gpgsm_exit (2);
}
gpgsm_verify (&ctrl, in_fp, data_fp, NULL);
es_fclose (in_fp);
es_fclose (data_fp);
}
else
wrong_args ("--verify [signature [detached_data]]");
es_fclose (fp);
}
break;
case aDecrypt:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
set_binary (stdin);
if (!argc)
err = gpgsm_decrypt (&ctrl, es_stdin, fp); /* from stdin */
else if (argc == 1)
{
estream_t data_fp = es_fopen (*argv, "rb");
if (!data_fp)
{
log_error (_("can't open '%s': %s\n"), *argv,
strerror (errno));
gpgsm_exit (2);
}
err = gpgsm_decrypt (&ctrl, data_fp, fp); /* from file */
es_fclose (data_fp);
}
else
wrong_args ("--decrypt [filename]");
if (err)
gpgrt_fcancel (fp);
else
es_fclose (fp);
}
break;
case aDeleteKey:
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_delete (&ctrl, sl);
free_strlist(sl);
break;
case aListChain:
case aDumpChain:
ctrl.with_chain = 1; /* fall through */
case aListKeys:
case aDumpKeys:
case aListExternalKeys:
case aDumpExternalKeys:
case aListSecretKeys:
case aDumpSecretKeys:
{
unsigned int mode;
estream_t fp;
switch (cmd)
{
case aListChain:
case aListKeys: mode = (0 | 0 | (1<<6)); break;
case aDumpChain:
case aDumpKeys: mode = (256 | 0 | (1<<6)); break;
case aListExternalKeys: mode = (0 | 0 | (1<<7)); break;
case aDumpExternalKeys: mode = (256 | 0 | (1<<7)); break;
case aListSecretKeys: mode = (0 | 2 | (1<<6)); break;
case aDumpSecretKeys: mode = (256 | 2 | (1<<6)); break;
default: BUG();
}
fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_list_keys (&ctrl, sl, fp, mode);
free_strlist(sl);
es_fclose (fp);
}
break;
case aShowCerts:
{
estream_t fp;
fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
gpgsm_show_certs (&ctrl, argc, argv, fp);
es_fclose (fp);
}
break;
case aKeygen: /* Generate a key; well kind of. */
{
estream_t fpin = NULL;
estream_t fpout;
if (opt.batch)
{
if (!argc) /* Create from stdin. */
fpin = open_es_fread ("-", "r");
else if (argc == 1) /* From file. */
fpin = open_es_fread (*argv, "r");
else
wrong_args ("--generate-key --batch [parmfile]");
}
fpout = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (fpin)
gpgsm_genkey (&ctrl, fpin, fpout);
else
gpgsm_gencertreq_tty (&ctrl, fpout);
es_fclose (fpout);
}
break;
case aImport:
gpgsm_import_files (&ctrl, argc, argv, open_es_fread);
break;
case aExport:
{
estream_t fp;
fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
gpgsm_export (&ctrl, sl, fp);
free_strlist(sl);
es_fclose (fp);
}
break;
case aExportSecretKeyP12:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 0);
else
wrong_args ("--export-secret-key-p12 KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aExportSecretKeyP8:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 1);
else
wrong_args ("--export-secret-key-p8 KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aExportSecretKeyRaw:
{
estream_t fp = open_es_fwrite (opt.outfile?opt.outfile:"-");
if (argc == 1)
gpgsm_p12_export (&ctrl, *argv, fp, 2);
else
wrong_args ("--export-secret-key-raw KEY-ID");
if (fp != es_stdout)
es_fclose (fp);
}
break;
case aSendKeys:
case aRecvKeys:
log_error ("this command has not yet been implemented\n");
break;
case aLearnCard:
if (argc)
wrong_args ("--learn-card");
else
{
int rc = gpgsm_agent_learn (&ctrl);
if (rc)
log_error ("error learning card: %s\n", gpg_strerror (rc));
}
break;
case aPasswd:
if (argc != 1)
wrong_args ("--change-passphrase <key-Id>");
else
{
int rc;
ksba_cert_t cert = NULL;
char *grip = NULL;
rc = gpgsm_find_cert (&ctrl, *argv, NULL, &cert, 0);
if (rc)
;
else if (!(grip = gpgsm_get_keygrip_hexstring (cert)))
rc = gpg_error (GPG_ERR_BUG);
else
{
char *desc = gpgsm_format_keydesc (cert);
rc = gpgsm_agent_passwd (&ctrl, grip, desc);
xfree (desc);
}
if (rc)
log_error ("error changing passphrase: %s\n", gpg_strerror (rc));
xfree (grip);
ksba_cert_release (cert);
}
break;
case aKeydbClearSomeCertFlags:
for (sl=NULL; argc; argc--, argv++)
add_to_strlist (&sl, *argv);
keydb_clear_some_cert_flags (&ctrl, sl);
free_strlist(sl);
break;
default:
log_error (_("invalid command (there is no implicit command)\n"));
gpgsm_status_with_error (&ctrl, STATUS_FAILURE, "option-parser",
gpg_error (GPG_ERR_MISSING_ACTION));
break;
}
/* Print the audit result if needed. */
if ((auditlog && auditfp) || (htmlauditlog && htmlauditfp))
{
if (auditlog && auditfp)
audit_print_result (ctrl.audit, auditfp, 0);
if (htmlauditlog && htmlauditfp)
audit_print_result (ctrl.audit, htmlauditfp, 1);
audit_release (ctrl.audit);
ctrl.audit = NULL;
es_fclose (auditfp);
es_fclose (htmlauditfp);
}
/* cleanup */
gpgsm_deinit_default_ctrl (&ctrl);
free_strlist (opt.keyserver);
opt.keyserver = NULL;
gpgsm_release_certlist (recplist);
gpgsm_release_certlist (signerlist);
FREE_STRLIST (remusr);
FREE_STRLIST (locusr);
gpgsm_exit(0);
return 8; /*NOTREACHED*/
}
/* Note: This function is used by signal handlers!. */
static void
emergency_cleanup (void)
{
gcry_control (GCRYCTL_TERM_SECMEM );
}
void
gpgsm_exit (int rc)
{
gcry_control (GCRYCTL_UPDATE_RANDOM_SEED_FILE);
if (opt.debug & DBG_MEMSTAT_VALUE)
{
gcry_control( GCRYCTL_DUMP_MEMORY_STATS );
gcry_control( GCRYCTL_DUMP_RANDOM_STATS );
}
if (opt.debug)
gcry_control (GCRYCTL_DUMP_SECMEM_STATS );
emergency_cleanup ();
rc = rc? rc : log_get_errorcount(0)? 2 : gpgsm_errors_seen? 1 : 0;
exit (rc);
}
void
gpgsm_init_default_ctrl (struct server_control_s *ctrl)
{
ctrl->include_certs = default_include_certs;
ctrl->use_ocsp = opt.enable_ocsp;
ctrl->validation_model = default_validation_model;
ctrl->offline = opt.disable_dirmngr;
ctrl->revoked_at[0] = 0;
ctrl->revocation_reason = NULL;
}
/* This function is called to deinitialize a control object. The
* control object is is not released, though. */
void
gpgsm_deinit_default_ctrl (ctrl_t ctrl)
{
gpgsm_keydb_deinit_session_data (ctrl);
xfree (ctrl->revocation_reason);
ctrl->revocation_reason = NULL;
}
int
gpgsm_parse_validation_model (const char *model)
{
if (!ascii_strcasecmp (model, "shell") )
return 0;
else if ( !ascii_strcasecmp (model, "chain") )
return 1;
else if ( !ascii_strcasecmp (model, "steed") )
return 2;
else
return -1;
}
/* Same as open_read but return an estream_t. */
static estream_t
open_es_fread (const char *filename, const char *mode)
{
gnupg_fd_t fd;
estream_t fp;
if (filename[0] == '-' && !filename[1])
return es_fpopen_nc (stdin, mode);
else
fd = gnupg_check_special_filename (filename);
if (fd != GNUPG_INVALID_FD)
{
fp = open_stream_nc (fd, mode);
if (!fp)
{
log_error ("es_fdopen(%d) failed: %s\n", FD_DBG (fd),
strerror (errno));
gpgsm_exit (2);
}
return fp;
}
fp = es_fopen (filename, mode);
if (!fp)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
/* Open FILENAME for fwrite and return an extended stream. Stop with
an error message in case of problems. "-" denotes stdout and if
special filenames are allowed the given fd is opened instead.
Caller must close the returned stream. */
static estream_t
open_es_fwrite (const char *filename)
{
gnupg_fd_t fd;
estream_t fp;
if (filename[0] == '-' && !filename[1])
{
fflush (stdout);
fp = es_fpopen_nc (stdout, "wb");
return fp;
}
fd = gnupg_check_special_filename (filename);
if (fd != GNUPG_INVALID_FD)
{
fp = open_stream_nc (fd, "wb");
if (!fp)
{
log_error ("es_fdopen(%d) failed: %s\n",
FD_DBG (fd), strerror (errno));
gpgsm_exit (2);
}
return fp;
}
fp = es_fopen (filename, "wb");
if (!fp)
{
log_error (_("can't open '%s': %s\n"), filename, strerror (errno));
gpgsm_exit (2);
}
return fp;
}
static void
run_protect_tool (int argc, char **argv)
{
#ifdef HAVE_W32_SYSTEM
(void)argc;
(void)argv;
#else
const char *pgm;
char **av;
int i;
if (!opt.protect_tool_program || !*opt.protect_tool_program)
pgm = gnupg_module_name (GNUPG_MODULE_NAME_PROTECT_TOOL);
else
pgm = opt.protect_tool_program;
av = xcalloc (argc+2, sizeof *av);
av[0] = strrchr (pgm, '/');
if (!av[0])
av[0] = xstrdup (pgm);
for (i=1; argc; i++, argc--, argv++)
av[i] = *argv;
av[i] = NULL;
execv (pgm, av);
log_error ("error executing '%s': %s\n", pgm, strerror (errno));
#endif /*!HAVE_W32_SYSTEM*/
gpgsm_exit (2);
}
diff --git a/sm/gpgsm.h b/sm/gpgsm.h
index 673ea1687..e9f74be8c 100644
--- a/sm/gpgsm.h
+++ b/sm/gpgsm.h
@@ -1,544 +1,544 @@
/* gpgsm.h - Global definitions for GpgSM
* Copyright (C) 2001, 2003, 2004, 2007, 2009,
* 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#ifndef GPGSM_H
#define GPGSM_H
#ifdef GPG_ERR_SOURCE_DEFAULT
#error GPG_ERR_SOURCE_DEFAULT already defined
#endif
#define GPG_ERR_SOURCE_DEFAULT GPG_ERR_SOURCE_GPGSM
#include <gpg-error.h>
#include <ksba.h>
#include "../common/util.h"
#include "../common/status.h"
#include "../common/audit.h"
#include "../common/session-env.h"
#include "../common/ksba-io-support.h"
#include "../common/compliance.h"
/* The maximum length of a binary fingerprints. This is used to
* provide a static buffer and will be increased if we need to support
* longer fingerprints. */
#define MAX_FINGERPRINT_LEN 32
/* The maximum length of a binary digest. */
#define MAX_DIGEST_LEN 64 /* Fits for SHA-512 */
/* A large struct named "opt" to keep global flags. */
EXTERN_UNLESS_MAIN_MODULE
struct
{
unsigned int debug; /* debug flags (DBG_foo_VALUE) */
int verbose; /* verbosity level */
int quiet; /* be as quiet as possible */
int batch; /* run in batch mode, i.e w/o any user interaction */
int answer_yes; /* assume yes on most questions */
int answer_no; /* assume no on most questions */
int dry_run; /* don't change any persistent data */
int no_homedir_creation;
int use_keyboxd; /* Use the external keyboxd as storage backend. */
const char *config_filename; /* Name of the used config file. */
char *agent_program;
char *keyboxd_program;
session_env_t session_env;
char *lc_ctype;
char *lc_messages;
int autostart;
char *dirmngr_program;
int disable_dirmngr; /* Do not do any dirmngr calls. */
const char *protect_tool_program;
char *outfile; /* name of output file */
int with_key_data;/* include raw key in the column delimited output */
int fingerprint; /* list fingerprints in all key listings */
int with_md5_fingerprint; /* Also print an MD5 fingerprint for
standard key listings. */
int with_keygrip; /* Option --with-keygrip active. */
int with_key_screening; /* Option --with-key-screening active. */
int no_pretty_dn; /* Option --no-pretty-dn */
int pinentry_mode;
int request_origin;
int armor; /* force base64 armoring (see also ctrl.with_base64) */
int no_armor; /* don't try to figure out whether data is base64 armored*/
const char *p12_charset; /* Use this charset for encoding the
pkcs#12 passphrase. */
const char *def_cipher_algoid; /* cipher algorithm to use if
nothing else is specified */
int def_compress_algo; /* Ditto for compress algorithm */
int forced_digest_algo; /* User forced hash algorithm. */
int force_ecdh_sha1kdf; /* Only for debugging and testing. */
char *def_recipient; /* userID of the default recipient */
int def_recipient_self; /* The default recipient is the default key */
int no_encrypt_to; /* Ignore all as encrypt to marked recipients. */
char *local_user; /* NULL or argument to -u */
int extra_digest_algo; /* A digest algorithm also used for
verification of signatures. */
int skip_verify; /* do not check signatures on data */
int lock_once; /* Keep lock once they are set */
int ignore_time_conflict; /* Ignore certain time conflicts */
int no_crl_check; /* Don't do a CRL check */
int no_trusted_cert_crl_check; /* Don't run a CRL check for trusted certs. */
int force_crl_refresh; /* Force refreshing the CRL. */
int enable_issuer_based_crl_check; /* Backward compatibility hack. */
int enable_ocsp; /* Default to use OCSP checks. */
char *policy_file; /* full pathname of policy file */
int no_policy_check; /* ignore certificate policies */
int no_chain_validation; /* Bypass all cert chain validity tests */
int ignore_expiration; /* Ignore the notAfter validity checks. */
int auto_issuer_key_retrieve; /* try to retrieve a missing issuer key. */
int qualsig_approval; /* Set to true if this software has
officially been approved to create an
verify qualified signatures. This is a
runtime option in case we want to check
the integrity of the software at
runtime. */
unsigned int min_rsa_length; /* Used for compliance checks. */
strlist_t keyserver;
/* A list of certificate extension OIDs which are ignored so that
one can claim that a critical extension has been handled. One
OID per string. */
strlist_t ignored_cert_extensions;
/* A list of OIDs which will be used to ignore certificates with
* sunch an OID during --learn-card. */
strlist_t ignore_cert_with_oid;
/* The current compliance mode. */
enum gnupg_compliance_mode compliance;
/* Fail if an operation can't be done in the requested compliance
* mode. */
int require_compliance;
/* Enable always-trust mode - note that there is also server option
* for this. */
int always_trust;
/* Enable creation of authenticode signatures. */
int authenticode;
/* A list of extra attributes put into a signed data object. For a
* signed each attribute each string has the format:
* <oid>:s:<hex_or_filename>
* and for an unsigned attribute
* <oid>:u:<hex_or_filename>
* The OID is in the usual dotted decimal for. The HEX_OR_FILENAME
* is either a list of hex digits or a filename with the DER encoded
* value. A filename is detected by the presence of a slash in the
* HEX_OR_FILENAME. The actual value needs to be encoded as a SET OF
* attribute values. */
strlist_t attributes;
/* Compatibility flags (COMPAT_FLAG_xxxx). */
unsigned int compat_flags;
} opt;
/* Debug values and macros. */
#define DBG_X509_VALUE 1 /* debug x.509 data reading/writing */
#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 /* debug assuan communication */
#define DBG_CLOCK_VALUE 4096
#define DBG_LOOKUP_VALUE 8192 /* debug the key lookup */
#define DBG_X509 (opt.debug & DBG_X509_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)
#define DBG_CLOCK (opt.debug & DBG_CLOCK_VALUE)
#define DBG_LOOKUP (opt.debug & DBG_LOOKUP_VALUE)
/* Compatibility flags */
/* Telesec RSA cards produced for NRW in 2022 came with only the
* keyAgreement bit set. This flag allows there use for encryption
* anyway. Example cert:
* Issuer: /CN=DOI CA 10a/OU=DOI/O=PKI-1-Verwaltung/C=DE
* key usage: digitalSignature nonRepudiation keyAgreement
* policies: 1.3.6.1.4.1.7924.1.1:N:
*/
#define COMPAT_ALLOW_KA_TO_ENCR 1
/* Forward declaration for an object defined in server.c */
struct server_local_s;
/* Object used to keep state locally in keydb.c */
struct keydb_local_s;
typedef struct keydb_local_s *keydb_local_t;
/* Session control object. This object is passed down to most
functions. Note that the default values for it are set by
gpgsm_init_default_ctrl(). */
struct server_control_s
{
int no_server; /* We are not running under server control */
int status_fd; /* Only for non-server mode */
struct server_local_s *server_local;
keydb_local_t keydb_local; /* Local data for call-keyboxd.c */
audit_ctx_t audit; /* NULL or a context for the audit subsystem. */
int agent_seen; /* Flag indicating that the gpg-agent has been
accessed. */
int with_colons; /* Use column delimited output format */
int with_secret; /* Mark secret keys in a public key listing. */
int with_chain; /* Include the certifying certs in a listing */
int with_validation;/* Validate each key while listing. */
int with_ephemeral_keys; /* Include ephemeral flagged keys in the
keylisting. */
int autodetect_encoding; /* Try to detect the input encoding */
int is_pem; /* Is in PEM format */
int is_base64; /* is in plain base-64 format */
/* If > 0 a hint with the expected number of input data bytes. This
* is not necessary an exact number but intended to be used for
* progress info and to decide on how to allocate buffers. */
uint64_t input_size_hint;
int create_base64; /* Create base64 encoded output */
int create_pem; /* create PEM output */
const char *pem_name; /* PEM name to use */
int include_certs; /* -1 to send all certificates in the chain
along with a signature or the number of
certificates up the chain (0 = none, 1 = only
signer) */
int use_ocsp; /* Set to true if OCSP should be used. */
int validation_model; /* 0 := standard model (shell),
1 := chain model,
2 := STEED model. */
int offline; /* If true gpgsm won't do any network access. */
int always_trust; /* True in always-trust mode; see also
* opt.always-trust. */
/* The current time. Used as a helper in certchain.c. */
ksba_isotime_t current_time;
/* The revocation info. Used as a helper inc ertchain.c */
gnupg_isotime_t revoked_at;
char *revocation_reason;
};
/* An object to keep a list of certificates. */
struct certlist_s
{
struct certlist_s *next;
ksba_cert_t cert;
int is_encrypt_to; /* True if the certificate has been set through
the --encrypto-to option. */
int pk_algo; /* The PK_ALGO from CERT or 0 if not yet known. */
int hash_algo; /* Used to track the hash algorithm to use. */
const char *hash_algo_oid; /* And the corresponding OID. */
};
typedef struct certlist_s *certlist_t;
/* A structure carrying information about trusted root certificates. */
struct rootca_flags_s
{
unsigned int valid:1; /* The rest of the structure has valid
information. */
unsigned int relax:1; /* Relax checking of root certificates. */
unsigned int chain_model:1; /* Root requires the use of the chain model. */
- unsigned int qualified:1; /* Root CA used for qualfied signatures. */
+ unsigned int qualified:1; /* Root CA used for qualified signatures. */
unsigned int de_vs:1; /* Root CA is de-vs compliant. */
};
/*-- gpgsm.c --*/
extern int gpgsm_errors_seen;
void gpgsm_exit (int rc);
void gpgsm_init_default_ctrl (struct server_control_s *ctrl);
void gpgsm_deinit_default_ctrl (ctrl_t ctrl);
int gpgsm_parse_validation_model (const char *model);
/*-- server.c --*/
void gpgsm_server (certlist_t default_recplist);
gpg_error_t gpgsm_status (ctrl_t ctrl, int no, const char *text);
gpg_error_t gpgsm_status2 (ctrl_t ctrl, int no, ...) GPGRT_ATTR_SENTINEL(0);
gpg_error_t gpgsm_status_with_err_code (ctrl_t ctrl, int no, const char *text,
gpg_err_code_t ec);
gpg_error_t gpgsm_status_with_error (ctrl_t ctrl, int no, const char *text,
gpg_error_t err);
gpg_error_t gpgsm_progress_cb (ctrl_t ctrl, uint64_t current, uint64_t total);
gpg_error_t gpgsm_proxy_pinentry_notify (ctrl_t ctrl,
const unsigned char *line);
/*-- fingerprint --*/
unsigned char *gpgsm_get_fingerprint (ksba_cert_t cert, int algo,
unsigned char *array, int *r_len);
char *gpgsm_get_fingerprint_string (ksba_cert_t cert, int algo);
char *gpgsm_get_fingerprint_hexstring (ksba_cert_t cert, int algo);
unsigned long gpgsm_get_short_fingerprint (ksba_cert_t cert,
unsigned long *r_high);
unsigned char *gpgsm_get_keygrip (ksba_cert_t cert, unsigned char *array);
char *gpgsm_get_keygrip_hexstring (ksba_cert_t cert);
int gpgsm_get_key_algo_info (ksba_cert_t cert, unsigned int *nbits,
char **r_curve);
int gpgsm_is_ecc_key (ksba_cert_t cert);
char *gpgsm_pubkey_algo_string (ksba_cert_t cert, int *r_algoid);
gcry_mpi_t gpgsm_get_rsa_modulus (ksba_cert_t cert);
char *gpgsm_get_certid (ksba_cert_t cert);
/*-- certdump.c --*/
const void *gpgsm_get_serial (ksba_const_sexp_t sn, size_t *r_length);
void gpgsm_print_serial (estream_t fp, ksba_const_sexp_t p);
void gpgsm_print_serial_decimal (estream_t fp, ksba_const_sexp_t sn);
void gpgsm_print_time (estream_t fp, ksba_isotime_t t);
void gpgsm_print_name2 (FILE *fp, const char *string, int translate);
void gpgsm_print_name (FILE *fp, const char *string);
void gpgsm_es_print_name (estream_t fp, const char *string);
void gpgsm_es_print_name2 (estream_t fp, const char *string, int translate);
void gpgsm_cert_log_name (const char *text, ksba_cert_t cert);
void gpgsm_dump_cert (const char *text, ksba_cert_t cert);
void gpgsm_dump_serial (ksba_const_sexp_t p);
void gpgsm_dump_time (ksba_isotime_t t);
void gpgsm_dump_string (const char *string);
char *gpgsm_format_serial (ksba_const_sexp_t p);
char *gpgsm_format_name2 (const char *name, int translate);
char *gpgsm_format_name (const char *name);
char *gpgsm_format_sn_issuer (ksba_sexp_t sn, const char *issuer);
char *gpgsm_fpr_and_name_for_status (ksba_cert_t cert);
char *gpgsm_format_keydesc (ksba_cert_t cert);
/*-- certcheck.c --*/
int gpgsm_check_cert_sig (ksba_cert_t issuer_cert, ksba_cert_t cert);
int gpgsm_check_cms_signature (ksba_cert_t cert, gcry_sexp_t sigval,
gcry_md_hd_t md,
int hash_algo, unsigned int pkalgoflags,
int *r_pkalgo);
/* fixme: move create functions to another file */
int gpgsm_create_cms_signature (ctrl_t ctrl,
ksba_cert_t cert, gcry_md_hd_t md, int mdalgo,
unsigned char **r_sigval);
/*-- certchain.c --*/
/* Flags used with gpgsm_validate_chain. */
#define VALIDATE_FLAG_NO_DIRMNGR 1
#define VALIDATE_FLAG_CHAIN_MODEL 2
#define VALIDATE_FLAG_STEED 4
#define VALIDATE_FLAG_BYPASS 8 /* No actual validation. */
gpg_error_t gpgsm_walk_cert_chain (ctrl_t ctrl,
ksba_cert_t start, ksba_cert_t *r_next);
int gpgsm_is_root_cert (ksba_cert_t cert);
int gpgsm_validate_chain (ctrl_t ctrl, ksba_cert_t cert,
ksba_isotime_t checktime,
ksba_isotime_t r_exptime,
int listmode, estream_t listfp,
unsigned int flags, unsigned int *retflags);
int gpgsm_basic_cert_check (ctrl_t ctrl, ksba_cert_t cert);
/*-- certlist.c --*/
int gpgsm_cert_use_sign_p (ksba_cert_t cert, int silent);
int gpgsm_cert_use_encrypt_p (ksba_cert_t cert);
int gpgsm_cert_use_verify_p (ksba_cert_t cert);
int gpgsm_cert_use_decrypt_p (ksba_cert_t cert);
int gpgsm_cert_use_cert_p (ksba_cert_t cert);
int gpgsm_cert_use_ocsp_p (ksba_cert_t cert);
int gpgsm_cert_has_well_known_private_key (ksba_cert_t cert);
int gpgsm_certs_identical_p (ksba_cert_t cert_a, ksba_cert_t cert_b);
int gpgsm_add_cert_to_certlist (ctrl_t ctrl, ksba_cert_t cert,
certlist_t *listaddr, int is_encrypt_to);
int gpgsm_add_to_certlist (ctrl_t ctrl, const char *name, int secret,
certlist_t *listaddr, int is_encrypt_to);
void gpgsm_release_certlist (certlist_t list);
#define FIND_CERT_ALLOW_AMBIG 1
#define FIND_CERT_WITH_EPHEM 2
int gpgsm_find_cert (ctrl_t ctrl, const char *name, ksba_sexp_t keyid,
ksba_cert_t *r_cert, unsigned int flags);
/*-- keylist.c --*/
gpg_error_t gpgsm_list_keys (ctrl_t ctrl, strlist_t names,
estream_t fp, unsigned int mode);
gpg_error_t gpgsm_show_certs (ctrl_t ctrl, int nfiles, char **files,
estream_t fp);
/*-- import.c --*/
int gpgsm_import (ctrl_t ctrl, estream_t in_fp, int reimport_mode);
int gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files,
estream_t (*of)(const char *fname, const char *mode));
/*-- export.c --*/
void gpgsm_export (ctrl_t ctrl, strlist_t names, estream_t stream);
void gpgsm_p12_export (ctrl_t ctrl, const char *name, estream_t stream,
int rawmode);
/*-- delete.c --*/
int gpgsm_delete (ctrl_t ctrl, strlist_t names);
/*-- verify.c --*/
int gpgsm_verify (ctrl_t ctrl, estream_t in_fp, estream_t data_fp,
estream_t out_fp);
/*-- sign.c --*/
int gpgsm_get_default_cert (ctrl_t ctrl, ksba_cert_t *r_cert);
int gpgsm_sign (ctrl_t ctrl, certlist_t signerlist,
estream_t data_fp, int detached, estream_t out_fp);
/*-- encrypt.c --*/
int gpgsm_encrypt (ctrl_t ctrl, certlist_t recplist,
estream_t in_fp, estream_t out_fp);
/*-- decrypt.c --*/
gpg_error_t ecdh_derive_kek (unsigned char *key, unsigned int keylen,
int hash_algo, const char *wrap_algo_str,
const void *secret, unsigned int secretlen,
const void *ukm, unsigned int ukmlen);
int gpgsm_decrypt (ctrl_t ctrl, estream_t in_fp, estream_t out_fp);
/*-- certreqgen.c --*/
int gpgsm_genkey (ctrl_t ctrl, estream_t in_stream, estream_t out_stream);
/*-- certreqgen-ui.c --*/
void gpgsm_gencertreq_tty (ctrl_t ctrl, estream_t out_stream);
/*-- qualified.c --*/
gpg_error_t gpgsm_is_in_qualified_list (ctrl_t ctrl, ksba_cert_t cert,
char *country);
gpg_error_t gpgsm_qualified_consent (ctrl_t ctrl, ksba_cert_t cert);
gpg_error_t gpgsm_not_qualified_warning (ctrl_t ctrl, ksba_cert_t cert);
/*-- call-agent.c --*/
int gpgsm_agent_pksign (ctrl_t ctrl, const char *keygrip, const char *desc,
unsigned char *digest,
size_t digestlen,
int digestalgo,
unsigned char **r_buf, size_t *r_buflen);
int gpgsm_scd_pksign (ctrl_t ctrl, const char *keyid, const char *desc,
unsigned char *digest, size_t digestlen, int digestalgo,
unsigned char **r_buf, size_t *r_buflen);
int gpgsm_agent_pkdecrypt (ctrl_t ctrl, const char *keygrip, const char *desc,
ksba_const_sexp_t ciphertext,
char **r_buf, size_t *r_buflen);
int gpgsm_agent_genkey (ctrl_t ctrl,
ksba_const_sexp_t keyparms, ksba_sexp_t *r_pubkey);
int gpgsm_agent_readkey (ctrl_t ctrl, int fromcard, const char *hexkeygrip,
ksba_sexp_t *r_pubkey);
int gpgsm_agent_scd_serialno (ctrl_t ctrl, char **r_serialno);
int gpgsm_agent_scd_keypairinfo (ctrl_t ctrl, strlist_t *r_list);
int gpgsm_agent_istrusted (ctrl_t ctrl, ksba_cert_t cert, const char *hexfpr,
struct rootca_flags_s *rootca_flags);
int gpgsm_agent_havekey (ctrl_t ctrl, const char *hexkeygrip);
int gpgsm_agent_marktrusted (ctrl_t ctrl, ksba_cert_t cert);
int gpgsm_agent_learn (ctrl_t ctrl);
int gpgsm_agent_passwd (ctrl_t ctrl, const char *hexkeygrip, const char *desc);
gpg_error_t gpgsm_agent_get_confirmation (ctrl_t ctrl, const char *desc);
gpg_error_t gpgsm_agent_send_nop (ctrl_t ctrl);
gpg_error_t gpgsm_agent_keyinfo (ctrl_t ctrl, const char *hexkeygrip,
char **r_serialno);
gpg_error_t gpgsm_agent_ask_passphrase (ctrl_t ctrl, const char *desc_msg,
int repeat, char **r_passphrase);
gpg_error_t gpgsm_agent_keywrap_key (ctrl_t ctrl, int forexport,
void **r_kek, size_t *r_keklen);
gpg_error_t gpgsm_agent_import_key (ctrl_t ctrl,
const void *key, size_t keylen);
gpg_error_t gpgsm_agent_export_key (ctrl_t ctrl, const char *keygrip,
const char *desc,
unsigned char **r_result,
size_t *r_resultlen);
/*-- call-dirmngr.c --*/
gpg_error_t gpgsm_dirmngr_isvalid (ctrl_t ctrl,
ksba_cert_t cert, ksba_cert_t issuer_cert,
int use_ocsp,
gnupg_isotime_t r_revoked_at,
char **r_reason);
int gpgsm_dirmngr_lookup (ctrl_t ctrl, strlist_t names, const char *uri,
int cache_only,
void (*cb)(void*, ksba_cert_t), void *cb_value);
int gpgsm_dirmngr_run_command (ctrl_t ctrl, const char *command,
int argc, char **argv);
/*-- misc.c --*/
void gpgsm_print_further_info (const char *format, ...) GPGRT_ATTR_PRINTF(1,2);
void setup_pinentry_env (void);
gpg_error_t transform_sigval (const unsigned char *sigval, size_t sigvallen,
int mdalgo,
unsigned char **r_newsigval,
size_t *r_newsigvallen);
gcry_sexp_t gpgsm_ksba_cms_get_sig_val (ksba_cms_t cms, int idx);
int gpgsm_get_hash_algo_from_sigval (gcry_sexp_t sigval,
unsigned int *r_pkalgo_flags);
#endif /*GPGSM_H*/
diff --git a/sm/import.c b/sm/import.c
index 4f993ef30..2efa24230 100644
--- a/sm/import.c
+++ b/sm/import.c
@@ -1,982 +1,982 @@
/* import.c - Import certificates
* Copyright (C) 2001, 2003, 2004, 2009, 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../common/exechelp.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
#include "../common/membuf.h"
#include "minip12.h"
#ifndef O_BINARY
#define O_BINARY 0
#endif
/* The arbitrary limit of one PKCS#12 object. */
#define MAX_P12OBJ_SIZE 128 /*kb*/
struct stats_s {
unsigned long count;
unsigned long imported;
unsigned long unchanged;
unsigned long not_imported;
unsigned long secret_read;
unsigned long secret_imported;
unsigned long secret_dups;
};
struct rsa_secret_key_s
{
gcry_mpi_t n; /* public modulus */
gcry_mpi_t e; /* public exponent */
gcry_mpi_t d; /* exponent */
gcry_mpi_t p; /* prime p. */
gcry_mpi_t q; /* prime q. */
gcry_mpi_t u; /* inverse of p mod q. */
};
static gpg_error_t parse_p12 (ctrl_t ctrl, ksba_reader_t reader,
struct stats_s *stats);
static void
print_imported_status (ctrl_t ctrl, ksba_cert_t cert, int new_cert)
{
char *fpr;
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
if (new_cert)
gpgsm_status2 (ctrl, STATUS_IMPORTED, fpr, "[X.509]", NULL);
gpgsm_status2 (ctrl, STATUS_IMPORT_OK,
new_cert? "1":"0", fpr, NULL);
xfree (fpr);
}
/* Print an IMPORT_PROBLEM status. REASON is one of:
0 := "No specific reason given".
1 := "Invalid Certificate".
2 := "Issuer Certificate missing".
3 := "Certificate Chain too long".
4 := "Error storing certificate".
*/
static void
print_import_problem (ctrl_t ctrl, ksba_cert_t cert, int reason)
{
char *fpr = NULL;
char buf[25];
int i;
sprintf (buf, "%d", reason);
if (cert)
{
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
/* detetect an error (all high) value */
for (i=0; fpr[i] == 'F'; i++)
;
if (!fpr[i])
{
xfree (fpr);
fpr = NULL;
}
}
gpgsm_status2 (ctrl, STATUS_IMPORT_PROBLEM, buf, fpr, NULL);
xfree (fpr);
}
void
print_imported_summary (ctrl_t ctrl, struct stats_s *stats)
{
char buf[14*25];
if (!opt.quiet)
{
log_info (_("total number processed: %lu\n"), stats->count);
if (stats->imported)
{
log_info (_(" imported: %lu"), stats->imported );
log_printf ("\n");
}
if (stats->unchanged)
log_info (_(" unchanged: %lu\n"), stats->unchanged);
if (stats->secret_read)
log_info (_(" secret keys read: %lu\n"), stats->secret_read );
if (stats->secret_imported)
log_info (_(" secret keys imported: %lu\n"), stats->secret_imported );
if (stats->secret_dups)
log_info (_(" secret keys unchanged: %lu\n"), stats->secret_dups );
if (stats->not_imported)
log_info (_(" not imported: %lu\n"), stats->not_imported);
}
sprintf(buf, "%lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu %lu",
stats->count,
0l /*stats->no_user_id*/,
stats->imported,
0l /*stats->imported_rsa*/,
stats->unchanged,
0l /*stats->n_uids*/,
0l /*stats->n_subk*/,
0l /*stats->n_sigs*/,
0l /*stats->n_revoc*/,
stats->secret_read,
stats->secret_imported,
stats->secret_dups,
0l /*stats->skipped_new_keys*/,
stats->not_imported
);
gpgsm_status (ctrl, STATUS_IMPORT_RES, buf);
}
static void
check_and_store (ctrl_t ctrl, struct stats_s *stats,
ksba_cert_t cert, int depth)
{
int rc;
if (stats)
stats->count++;
if ( depth >= 50 )
{
log_error (_("certificate chain too long\n"));
if (stats)
stats->not_imported++;
print_import_problem (ctrl, cert, 3);
return;
}
/* Some basic checks, but don't care about missing certificates;
this is so that we are able to import entire certificate chains
w/o requiring a special order (i.e. root-CA first). This used
to be different but because gpgsm_verify even imports
certificates without any checks, it doesn't matter much and the
code gets much cleaner. A housekeeping function to remove
certificates w/o an anchor would be nice, though.
Optionally we do a full validation in addition to the basic test.
*/
rc = gpgsm_basic_cert_check (ctrl, cert);
if (!rc && ctrl->with_validation)
rc = gpgsm_validate_chain (ctrl, cert,
GNUPG_ISOTIME_NONE, NULL, 0, NULL, 0, NULL);
if (!rc || (!ctrl->with_validation
&& (gpg_err_code (rc) == GPG_ERR_MISSING_CERT
|| gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT)))
{
int existed;
if (!keydb_store_cert (ctrl, cert, 0, &existed))
{
ksba_cert_t next = NULL;
if (!existed)
{
print_imported_status (ctrl, cert, 1);
if (stats)
stats->imported++;
}
else
{
print_imported_status (ctrl, cert, 0);
if (stats)
stats->unchanged++;
}
if (opt.verbose > 1 && existed)
{
if (depth)
log_info ("issuer certificate already in DB\n");
else
log_info ("certificate already in DB\n");
}
else if (opt.verbose && !existed)
{
if (depth)
log_info ("issuer certificate imported\n");
else
log_info ("certificate imported\n");
}
/* Now lets walk up the chain and import all certificates up
the chain. This is required in case we already stored
parent certificates in the ephemeral keybox. Do not
update the statistics, though. */
if (!gpgsm_walk_cert_chain (ctrl, cert, &next))
{
check_and_store (ctrl, NULL, next, depth+1);
ksba_cert_release (next);
}
}
else
{
log_error (_("error storing certificate\n"));
if (stats)
stats->not_imported++;
print_import_problem (ctrl, cert, 4);
}
}
else
{
log_error (_("basic certificate checks failed - not imported\n"));
if (stats)
stats->not_imported++;
/* We keep the test for GPG_ERR_MISSING_CERT only in case
GPG_ERR_MISSING_CERT has been used instead of the newer
GPG_ERR_MISSING_ISSUER_CERT. */
print_import_problem
(ctrl, cert,
gpg_err_code (rc) == GPG_ERR_MISSING_ISSUER_CERT? 2 :
gpg_err_code (rc) == GPG_ERR_MISSING_CERT? 2 :
gpg_err_code (rc) == GPG_ERR_BAD_CERT? 1 : 0);
}
}
static int
import_one (ctrl_t ctrl, struct stats_s *stats, estream_t fp)
{
int rc;
gnupg_ksba_io_t b64reader = NULL;
ksba_reader_t reader;
ksba_cert_t cert = NULL;
ksba_cms_t cms = NULL;
ksba_content_type_t ct;
int any = 0;
rc = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)
| GNUPG_KSBA_IO_MULTIPEM),
fp, &reader);
if (rc)
{
log_error ("can't create reader: %s\n", gpg_strerror (rc));
goto leave;
}
/* We need to loop here to handle multiple PEM objects in one
file. */
do
{
ksba_cms_release (cms); cms = NULL;
ksba_cert_release (cert); cert = NULL;
ct = ksba_cms_identify (reader);
if (ct == KSBA_CT_SIGNED_DATA)
{ /* This is probably a signed-only message - import the certs */
ksba_stop_reason_t stopreason;
int i;
rc = ksba_cms_new (&cms);
if (rc)
goto leave;
rc = ksba_cms_set_reader_writer (cms, reader, NULL);
if (rc)
{
log_error ("ksba_cms_set_reader_writer failed: %s\n",
gpg_strerror (rc));
goto leave;
}
do
{
rc = ksba_cms_parse (cms, &stopreason);
if (rc)
{
log_error ("ksba_cms_parse failed: %s\n", gpg_strerror (rc));
goto leave;
}
if (stopreason == KSBA_SR_BEGIN_DATA)
log_info ("not a certs-only message\n");
}
while (stopreason != KSBA_SR_READY);
for (i=0; (cert=ksba_cms_get_cert (cms, i)); i++)
{
check_and_store (ctrl, stats, cert, 0);
ksba_cert_release (cert);
cert = NULL;
}
if (!i)
log_error ("no certificate found\n");
else
any = 1;
}
else if (ct == KSBA_CT_PKCS12)
{
/* This seems to be a pkcs12 message. */
rc = parse_p12 (ctrl, reader, stats);
if (!rc)
any = 1;
}
else if (ct == KSBA_CT_NONE)
{ /* Failed to identify this message - assume a certificate */
rc = ksba_cert_new (&cert);
if (rc)
goto leave;
rc = ksba_cert_read_der (cert, reader);
if (rc)
goto leave;
check_and_store (ctrl, stats, cert, 0);
any = 1;
}
else
{
log_error ("can't extract certificates from input\n");
rc = gpg_error (GPG_ERR_NO_DATA);
}
ksba_reader_clear (reader, NULL, NULL);
}
while (!gnupg_ksba_reader_eof_seen (b64reader));
leave:
if (any && gpg_err_code (rc) == GPG_ERR_EOF)
rc = 0;
ksba_cms_release (cms);
ksba_cert_release (cert);
gnupg_ksba_destroy_reader (b64reader);
return rc;
}
-/* Re-import certifciates. IN_FD is a list of linefeed delimited
+/* Re-import certificates. IN_FD is a list of linefeed delimited
fingerprints t re-import. The actual re-import is done by clearing
the ephemeral flag. */
static int
reimport_one (ctrl_t ctrl, struct stats_s *stats, estream_t fp)
{
gpg_error_t err = 0;
char line[100]; /* Sufficient for a fingerprint. */
KEYDB_HANDLE kh;
KEYDB_SEARCH_DESC desc;
ksba_cert_t cert = NULL;
unsigned int flags;
kh = keydb_new (ctrl);
if (!kh)
{
err = gpg_error (GPG_ERR_ENOMEM);;
log_error (_("failed to allocate keyDB handle\n"));
goto leave;
}
keydb_set_ephemeral (kh, 1);
while (es_fgets (line, DIM(line)-1, fp) )
{
if (*line && line[strlen(line)-1] != '\n')
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
goto leave;
}
trim_spaces (line);
if (!*line)
continue;
stats->count++;
err = classify_user_id (line, &desc, 0);
if (err)
{
print_import_problem (ctrl, NULL, 0);
stats->not_imported++;
continue;
}
keydb_search_reset (kh);
err = keydb_search (ctrl, kh, &desc, 1);
if (err)
{
print_import_problem (ctrl, NULL, 0);
stats->not_imported++;
continue;
}
ksba_cert_release (cert);
cert = NULL;
err = keydb_get_cert (kh, &cert);
if (err)
{
log_error ("keydb_get_cert failed in %s: %s <%s>\n",
__func__, gpg_strerror (err), gpg_strsource (err));
print_import_problem (ctrl, NULL, 1);
stats->not_imported++;
continue;
}
err = keydb_get_flags (kh, KEYBOX_FLAG_BLOB, 0, &flags);
if (err)
{
log_error (_("error getting stored flags: %s\n"), gpg_strerror (err));
print_imported_status (ctrl, cert, 0);
stats->not_imported++;
continue;
}
if ( !(flags & KEYBOX_FLAG_BLOB_EPHEMERAL) )
{
print_imported_status (ctrl, cert, 0);
stats->unchanged++;
continue;
}
err = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (err)
{
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (err));
print_import_problem (ctrl, cert, 0);
stats->not_imported++;
continue;
}
print_imported_status (ctrl, cert, 1);
stats->imported++;
}
err = 0;
if (es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("error reading fp %p: %s\n", fp, gpg_strerror (err));
goto leave;
}
leave:
ksba_cert_release (cert);
keydb_release (kh);
return err;
}
int
gpgsm_import (ctrl_t ctrl, estream_t in_fp, int reimport_mode)
{
int rc;
struct stats_s stats;
memset (&stats, 0, sizeof stats);
if (reimport_mode)
rc = reimport_one (ctrl, &stats, in_fp);
else
rc = import_one (ctrl, &stats, in_fp);
print_imported_summary (ctrl, &stats);
/* If we never printed an error message do it now so that a command
line invocation will return with an error (log_error keeps a
global errorcount) */
if (rc && !log_get_errorcount (0))
log_error (_("error importing certificate: %s\n"), gpg_strerror (rc));
return rc;
}
int
gpgsm_import_files (ctrl_t ctrl, int nfiles, char **files,
estream_t (*of)(const char *fname, const char *mode))
{
int rc = 0;
struct stats_s stats;
memset (&stats, 0, sizeof stats);
if (!nfiles)
{
#ifdef HAVE_DOSISH_SYSTEM
setmode (0, O_BINARY);
#endif
rc = import_one (ctrl, &stats, es_stdin);
}
else
{
for (; nfiles && !rc ; nfiles--, files++)
{
estream_t fp = of (*files, "rb");
rc = import_one (ctrl, &stats, fp);
es_fclose (fp);
if (rc == -1/* legacy*/ || gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = 0;
}
}
print_imported_summary (ctrl, &stats);
/* If we never printed an error message do it now so that a command
line invocation will return with an error (log_error keeps a
global errorcount) */
if (rc && !log_get_errorcount (0))
log_error (_("error importing certificate: %s\n"), gpg_strerror (rc));
return rc;
}
/* Check that the RSA secret key SKEY is valid. Swap parameters to
the libgcrypt standard. */
static gpg_error_t
rsa_key_check (struct rsa_secret_key_s *skey)
{
int err = 0;
gcry_mpi_t t = gcry_mpi_snew (0);
gcry_mpi_t t1 = gcry_mpi_snew (0);
gcry_mpi_t t2 = gcry_mpi_snew (0);
gcry_mpi_t phi = gcry_mpi_snew (0);
/* Check that n == p * q. */
gcry_mpi_mul (t, skey->p, skey->q);
if (gcry_mpi_cmp( t, skey->n) )
{
log_error ("RSA oops: n != p * q\n");
err++;
}
/* Check that p is less than q. */
if (gcry_mpi_cmp (skey->p, skey->q) > 0)
{
gcry_mpi_t tmp;
log_info ("swapping secret primes\n");
tmp = gcry_mpi_copy (skey->p);
gcry_mpi_set (skey->p, skey->q);
gcry_mpi_set (skey->q, tmp);
gcry_mpi_release (tmp);
/* Recompute u. */
gcry_mpi_invm (skey->u, skey->p, skey->q);
}
/* Check that e divides neither p-1 nor q-1. */
gcry_mpi_sub_ui (t, skey->p, 1 );
gcry_mpi_div (NULL, t, t, skey->e, 0);
if (!gcry_mpi_cmp_ui( t, 0) )
{
log_error ("RSA oops: e divides p-1\n");
err++;
}
gcry_mpi_sub_ui (t, skey->q, 1);
gcry_mpi_div (NULL, t, t, skey->e, 0);
if (!gcry_mpi_cmp_ui( t, 0))
{
log_info ("RSA oops: e divides q-1\n" );
err++;
}
/* Check that d is correct. */
gcry_mpi_sub_ui (t1, skey->p, 1);
gcry_mpi_sub_ui (t2, skey->q, 1);
gcry_mpi_mul (phi, t1, t2);
gcry_mpi_invm (t, skey->e, phi);
if (gcry_mpi_cmp (t, skey->d))
{
/* No: try universal exponent. */
gcry_mpi_gcd (t, t1, t2);
gcry_mpi_div (t, NULL, phi, t, 0);
gcry_mpi_invm (t, skey->e, t);
if (gcry_mpi_cmp (t, skey->d))
{
log_error ("RSA oops: bad secret exponent\n");
err++;
}
}
/* Check for correctness of u. */
gcry_mpi_invm (t, skey->p, skey->q);
if (gcry_mpi_cmp (t, skey->u))
{
log_info ("RSA oops: bad u parameter\n");
err++;
}
if (err)
log_info ("RSA secret key check failed\n");
gcry_mpi_release (t);
gcry_mpi_release (t1);
gcry_mpi_release (t2);
gcry_mpi_release (phi);
return err? gpg_error (GPG_ERR_BAD_SECKEY):0;
}
/* Object passed to store_cert_cb. */
struct store_cert_parm_s
{
gpg_error_t err; /* First error seen. */
struct stats_s *stats; /* The stats object. */
ctrl_t ctrl; /* The control object. */
};
/* Helper to store the DER encoded certificate CERTDATA of length
CERTDATALEN. */
static void
store_cert_cb (void *opaque,
const unsigned char *certdata, size_t certdatalen)
{
struct store_cert_parm_s *parm = opaque;
gpg_error_t err;
ksba_cert_t cert;
err = ksba_cert_new (&cert);
if (err)
{
if (!parm->err)
parm->err = err;
return;
}
err = ksba_cert_init_from_mem (cert, certdata, certdatalen);
if (err)
{
log_error ("failed to parse a certificate: %s\n", gpg_strerror (err));
if (!parm->err)
parm->err = err;
}
else
check_and_store (parm->ctrl, parm->stats, cert, 0);
ksba_cert_release (cert);
}
/* Helper for parse_p12. */
static gpg_error_t
p12_to_skey (gcry_mpi_t *kparms, const char *curve, gcry_sexp_t *r_skey)
{
gpg_error_t err = 0;
struct rsa_secret_key_s sk;
gcry_ctx_t ecctx = NULL;
if (curve)
{
/* log_debug ("curve: %s\n", curve); */
/* gcry_log_debugmpi ("MPI[0]", kparms[0]); */
/* We need to get the public key. */
err = gcry_mpi_ec_new (&ecctx, NULL, curve);
if (err)
{
log_error ("error creating context for curve '%s': %s\n",
curve, gpg_strerror (err));
goto leave;
}
err = gcry_mpi_ec_set_mpi ("d", kparms[0], ecctx);
if (err)
{
log_error ("error setting 'd' into context of curve '%s': %s\n",
curve, gpg_strerror (err));
goto leave;
}
kparms[1] = gcry_mpi_ec_get_mpi ("q", ecctx, 1);
if (!kparms[1])
{
log_error ("error computing 'q' from 'd' for curve '%s'\n", curve);
goto leave;
}
err = gcry_sexp_build (r_skey, NULL,
"(private-key(ecc(curve %s)(q%m)(d%m)))",
curve, kparms[1], kparms[0], NULL);
}
else /* RSA */
{
/* print_mpi (" n", kparms[0]); */
/* print_mpi (" e", kparms[1]); */
/* print_mpi (" d", kparms[2]); */
/* print_mpi (" p", kparms[3]); */
/* print_mpi (" q", kparms[4]); */
/* print_mpi ("dmp1", kparms[5]); */
/* print_mpi ("dmq1", kparms[6]); */
/* print_mpi (" u", kparms[7]); */
sk.n = kparms[0];
sk.e = kparms[1];
sk.d = kparms[2];
sk.q = kparms[3];
sk.p = kparms[4];
sk.u = kparms[7];
err = rsa_key_check (&sk);
if (err)
goto leave;
/* print_mpi (" n", sk.n); */
/* print_mpi (" e", sk.e); */
/* print_mpi (" d", sk.d); */
/* print_mpi (" p", sk.p); */
/* print_mpi (" q", sk.q); */
/* print_mpi (" u", sk.u); */
/* Create an S-expression from the parameters. */
err = gcry_sexp_build (r_skey, NULL,
"(private-key(rsa(n%m)(e%m)(d%m)(p%m)(q%m)(u%m)))",
sk.n, sk.e, sk.d, sk.p, sk.q, sk.u, NULL);
}
leave:
gcry_ctx_release (ecctx);
return err;
}
/* Assume that the reader is at a pkcs#12 message and try to import
certificates from that stupid format. We will transfer secret
keys to the agent. */
static gpg_error_t
parse_p12 (ctrl_t ctrl, ksba_reader_t reader, struct stats_s *stats)
{
gpg_error_t err = 0;
char buffer[1024];
size_t ntotal, nread;
membuf_t p12mbuf;
char *p12buffer = NULL;
size_t p12buflen;
size_t p12bufoff;
gcry_mpi_t *kparms = NULL;
char *passphrase = NULL;
unsigned char *key = NULL;
size_t keylen;
void *kek = NULL;
size_t keklen;
unsigned char *wrappedkey = NULL;
size_t wrappedkeylen;
gcry_cipher_hd_t cipherhd = NULL;
gcry_sexp_t s_key = NULL;
unsigned char grip[20];
int bad_pass = 0;
char *curve = NULL;
int i;
struct store_cert_parm_s store_cert_parm;
memset (&store_cert_parm, 0, sizeof store_cert_parm);
store_cert_parm.ctrl = ctrl;
store_cert_parm.stats = stats;
init_membuf (&p12mbuf, 4096);
ntotal = 0;
while (!(err = ksba_reader_read (reader, buffer, sizeof buffer, &nread)))
{
if (ntotal >= MAX_P12OBJ_SIZE*1024)
{
/* Arbitrary limit to avoid DoS attacks. */
err = gpg_error (GPG_ERR_TOO_LARGE);
log_error ("pkcs#12 object is larger than %dk\n", MAX_P12OBJ_SIZE);
break;
}
put_membuf (&p12mbuf, buffer, nread);
ntotal += nread;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
if (!err)
{
p12buffer = get_membuf (&p12mbuf, &p12buflen);
if (!p12buffer)
err = gpg_error_from_syserror ();
}
if (err)
{
log_error (_("error reading input: %s\n"), gpg_strerror (err));
goto leave;
}
/* GnuPG 2.0.4 accidentally created binary P12 files with the string
"The passphrase is %s encoded.\n\n" prepended to the ASN.1 data.
We fix that here. */
if (p12buflen > 29 && !memcmp (p12buffer, "The passphrase is ", 18))
{
for (p12bufoff=18;
p12bufoff < p12buflen && p12buffer[p12bufoff] != '\n';
p12bufoff++)
;
p12bufoff++;
if (p12bufoff < p12buflen && p12buffer[p12bufoff] == '\n')
p12bufoff++;
}
else
p12bufoff = 0;
err = gpgsm_agent_ask_passphrase
(ctrl,
i18n_utf8 (N_("Please enter the passphrase to unprotect the PKCS#12 object.")),
0, &passphrase);
if (err)
goto leave;
kparms = p12_parse (p12buffer + p12bufoff, p12buflen - p12bufoff,
passphrase, store_cert_cb,
&store_cert_parm, &bad_pass, &curve);
xfree (passphrase);
passphrase = NULL;
if (!kparms)
{
log_error ("error parsing or decrypting the PKCS#12 file\n");
err = gpg_error (GPG_ERR_INV_OBJ);
goto leave;
}
err = p12_to_skey (kparms, curve, &s_key);
p12_parse_free_kparms (kparms);
kparms = NULL;
if (err)
{
log_error ("failed to create S-expression from key: %s\n",
gpg_strerror (err));
goto leave;
}
/* Compute the keygrip. */
if (!gcry_pk_get_keygrip (s_key, grip))
{
err = gpg_error (GPG_ERR_GENERAL);
log_error ("can't calculate keygrip\n");
goto leave;
}
if (DBG_X509)
log_printhex (grip, 20, "keygrip:");
/* Convert to canonical encoding using a function which pads it to a
multiple of 64 bits. We need this padding for AESWRAP. */
err = make_canon_sexp_pad (s_key, 1, &key, &keylen);
if (err)
{
log_error ("error creating canonical S-expression\n");
goto leave;
}
gcry_sexp_release (s_key);
s_key = NULL;
/* Get the current KEK. */
err = gpgsm_agent_keywrap_key (ctrl, 0, &kek, &keklen);
if (err)
{
log_error ("error getting the KEK: %s\n", gpg_strerror (err));
goto leave;
}
/* Wrap the key. */
err = gcry_cipher_open (&cipherhd, GCRY_CIPHER_AES128,
GCRY_CIPHER_MODE_AESWRAP, 0);
if (err)
goto leave;
err = gcry_cipher_setkey (cipherhd, kek, keklen);
if (err)
goto leave;
xfree (kek);
kek = NULL;
wrappedkeylen = keylen + 8;
wrappedkey = xtrymalloc (wrappedkeylen);
if (!wrappedkey)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gcry_cipher_encrypt (cipherhd, wrappedkey, wrappedkeylen, key, keylen);
if (err)
goto leave;
xfree (key);
key = NULL;
gcry_cipher_close (cipherhd);
cipherhd = NULL;
/* Send the wrapped key to the agent. */
err = gpgsm_agent_import_key (ctrl, wrappedkey, wrappedkeylen);
if (!err)
{
stats->count++;
stats->secret_read++;
stats->secret_imported++;
}
else if ( gpg_err_code (err) == GPG_ERR_EEXIST )
{
err = 0;
stats->count++;
stats->secret_read++;
stats->secret_dups++;
}
/* If we did not get an error from storing the secret key we return
a possible error from parsing the certificates. We do this after
storing the secret keys so that a bad certificate does not
inhibit our chance to store the secret key. */
if (!err && store_cert_parm.err)
err = store_cert_parm.err;
leave:
if (kparms)
{
for (i=0; i < 8; i++)
gcry_mpi_release (kparms[i]);
gcry_free (kparms);
kparms = NULL;
}
xfree (key);
gcry_sexp_release (s_key);
xfree (passphrase);
gcry_cipher_close (cipherhd);
xfree (wrappedkey);
xfree (kek);
xfree (get_membuf (&p12mbuf, NULL));
xfree (p12buffer);
xfree (curve);
if (bad_pass)
{
/* We only write a plain error code and not direct
BAD_PASSPHRASE because the pkcs12 parser might issue this
message multiple times, BAD_PASSPHRASE in general requires a
keyID and parts of the import might actually succeed so that
IMPORT_PROBLEM is also not appropriate. */
gpgsm_status_with_err_code (ctrl, STATUS_ERROR,
"import.parsep12", GPG_ERR_BAD_PASSPHRASE);
}
return err;
}
diff --git a/sm/keydb.c b/sm/keydb.c
index 9d704a110..1f93268a8 100644
--- a/sm/keydb.c
+++ b/sm/keydb.c
@@ -1,2202 +1,2202 @@
/* keydb.c - key database dispatcher
* Copyright (C) 2001, 2003, 2004 Free Software Foundation, Inc.
* Copyright (C) 2014, 2020 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include "gpgsm.h"
#include <assuan.h>
#include "../kbx/keybox.h"
#include "keydb.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/comopt.h"
#include "../kbx/kbx-client-util.h"
typedef enum {
KEYDB_RESOURCE_TYPE_NONE = 0,
KEYDB_RESOURCE_TYPE_KEYBOX
} KeydbResourceType;
#define MAX_KEYDB_RESOURCES 20
struct resource_item {
KeydbResourceType type;
union {
KEYBOX_HANDLE kr;
} u;
void *token;
};
/* Data used to keep track of keybox daemon sessions. This allows us
- * to use several sessions with the keyboxd and also to re-use already
+ * to use several sessions with the keyboxd and also to reuse already
* established sessions. Note that gpgdm.h defines the type
* keydb_local_t for this structure. */
struct keydb_local_s
{
/* Link to other keyboxd contexts which are used simultaneously. */
struct keydb_local_s *next;
/* The active Assuan context. */
assuan_context_t ctx;
/* The client data helper context. */
kbx_client_data_t kcd;
/* I/O buffer with the last search result or NULL. Used if
* D-lines are used to convey the keyblocks. */
struct {
char *buf;
size_t len;
} search_result;
/* The "stack" used by keydb_push_found_state. */
struct {
char *buf;
size_t len;
} saved_search_result;
/* This flag set while an operation is running on this context. */
unsigned int is_active : 1;
/* Flag indicating that a search reset is required. */
unsigned int need_search_reset : 1;
};
static struct resource_item all_resources[MAX_KEYDB_RESOURCES];
static int used_resources;
/* Whether we have successfully registered any resource. */
static int any_registered;
/* Number of active handles. */
static int active_handles;
struct keydb_handle {
/* CTRL object passed to keydb_new. */
ctrl_t ctrl;
/* If set the keyboxdd is used instead of the local files. */
int use_keyboxd;
/* BEGIN USE_KEYBOXD */
/* (These fields are only valid if USE_KEYBOXD is set.) */
/* Connection info which also keeps the local state. (This points
* into the CTRL->keybox_local list.) */
keydb_local_t kbl;
/* Various flags. */
unsigned int last_ubid_valid:1;
unsigned int last_is_ephemeral; /* Last found key is ephemeral. */
/* The UBID of the last returned keyblock. */
unsigned char last_ubid[UBID_LEN];
/* END USE_KEYBOXD */
/* BEGIN !USE_KEYBOXD */
/* (The remaining fields are only valid if USE_KEYBOXD is cleared.) */
/* If this flag is set the resources is locked. */
int locked;
/* If this flag is set a lock will only be released by
* keydb_release. */
int keep_lock;
int found;
int saved_found;
int current;
int is_ephemeral;
int used; /* items in active */
struct resource_item active[MAX_KEYDB_RESOURCES];
/* END !USE_KEYBOXD */
};
static int lock_all (KEYDB_HANDLE hd);
static void unlock_all (KEYDB_HANDLE hd);
/* Deinitialize all session resources pertaining to the keyboxd. */
void
gpgsm_keydb_deinit_session_data (ctrl_t ctrl)
{
keydb_local_t kbl;
while ((kbl = ctrl->keydb_local))
{
ctrl->keydb_local = kbl->next;
if (kbl->is_active)
log_error ("oops: trying to cleanup an active keydb context\n");
else
{
assuan_release (kbl->ctx);
kbl->ctx = NULL;
/*
* Since there may be pipe output FD sent to the server (so
* that it can receive data through the pipe), we should
* release the assuan connection before releasing KBL->KCD.
* This way, the data receiving thread can finish cleanly,
* and we can join the thread.
*/
kbx_client_data_release (kbl->kcd);
kbl->kcd = NULL;
}
xfree (kbl);
}
}
static void
try_make_homedir (const char *fname)
{
if ( opt.dry_run || opt.no_homedir_creation )
return;
gnupg_maybe_make_homedir (fname, opt.quiet);
}
/* Handle the creation of a keybox if it does not yet exist. Take
into account that other processes might have the keybox already
locked. This lock check does not work if the directory itself is
not yet available. If R_CREATED is not NULL it will be set to true
if the function created a new keybox. */
static gpg_error_t
maybe_create_keybox (char *filename, int force, int *r_created)
{
gpg_err_code_t ec;
dotlock_t lockhd = NULL;
estream_t fp;
int rc;
mode_t oldmask;
char *last_slash_in_filename;
int save_slash;
if (r_created)
*r_created = 0;
/* A quick test whether the filename already exists. */
if (!gnupg_access (filename, F_OK))
return !gnupg_access (filename, R_OK)? 0 : gpg_error (GPG_ERR_EACCES);
/* If we don't want to create a new file at all, there is no need to
go any further - bail out right here. */
if (!force)
return gpg_error (GPG_ERR_ENOENT);
/* First of all we try to create the home directory. Note, that we
don't do any locking here because any sane application of gpg
would create the home directory by itself and not rely on gpg's
tricky auto-creation which is anyway only done for some home
directory name patterns. */
last_slash_in_filename = strrchr (filename, DIRSEP_C);
#if HAVE_W32_SYSTEM
{
/* Windows may either have a slash or a backslash. Take care of it. */
char *p = strrchr (filename, '/');
if (!last_slash_in_filename || p > last_slash_in_filename)
last_slash_in_filename = p;
}
#endif /*HAVE_W32_SYSTEM*/
if (!last_slash_in_filename)
return gpg_error (GPG_ERR_ENOENT); /* No slash at all - should
not happen though. */
save_slash = *last_slash_in_filename;
*last_slash_in_filename = 0;
if (gnupg_access(filename, F_OK))
{
static int tried;
if (!tried)
{
tried = 1;
try_make_homedir (filename);
}
if ((ec = gnupg_access (filename, F_OK)))
{
rc = gpg_error (ec);
*last_slash_in_filename = save_slash;
goto leave;
}
*last_slash_in_filename = save_slash;
if (!opt.use_keyboxd
&& !parse_comopt (GNUPG_MODULE_NAME_GPG, 0)
&& comopt.use_keyboxd)
{
/* The above try_make_homedir created a new default hoemdir
* and also wrote a new common.conf. Thus we now see that
* use-keyboxd has been set. Let's set this option and
* return a dedicated error code. */
opt.use_keyboxd = comopt.use_keyboxd;
rc = gpg_error (GPG_ERR_TRUE);
goto leave;
}
}
else
*last_slash_in_filename = save_slash;
/* To avoid races with other instances of gpg trying to create or
update the keybox (it is removed during an update for a short
time), we do the next stuff in a locked state. */
lockhd = dotlock_create (filename, 0);
if (!lockhd)
{
/* A reason for this to fail is that the directory is not
writable. However, this whole locking stuff does not make
sense if this is the case. An empty non-writable directory
with no keyring is not really useful at all. */
if (opt.verbose)
log_info ("can't allocate lock for '%s'\n", filename );
if (!force)
return gpg_error (GPG_ERR_ENOENT);
else
return gpg_error (GPG_ERR_GENERAL);
}
if ( dotlock_take (lockhd, -1) )
{
/* This is something bad. Probably a stale lockfile. */
log_info ("can't lock '%s'\n", filename);
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
/* Now the real test while we are locked. */
if (!gnupg_access(filename, F_OK))
{
rc = 0; /* Okay, we may access the file now. */
goto leave;
}
/* The file does not yet exist, create it now. */
oldmask = umask (077);
fp = es_fopen (filename, "wb");
if (!fp)
{
rc = gpg_error_from_syserror ();
umask (oldmask);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
umask (oldmask);
/* Make sure that at least one record is in a new keybox file, so
that the detection magic for OpenPGP keyboxes works the next time
it is used. */
rc = _keybox_write_header_blob (fp, 0);
if (rc)
{
es_fclose (fp);
log_error (_("error creating keybox '%s': %s\n"),
filename, gpg_strerror (rc));
goto leave;
}
if (!opt.quiet)
log_info (_("keybox '%s' created\n"), filename);
if (r_created)
*r_created = 1;
es_fclose (fp);
rc = 0;
leave:
if (lockhd)
{
dotlock_release (lockhd);
dotlock_destroy (lockhd);
}
return rc;
}
/*
* Register a resource (which currently may only be a keybox file).
* The first keybox which is added by this function is created if it
* does not exist. If AUTO_CREATED is not NULL it will be set to true
* if the function has created a new keybox.
*/
gpg_error_t
keydb_add_resource (ctrl_t ctrl, const char *url, int force, int *auto_created)
{
const char *resname = url;
char *filename = NULL;
gpg_error_t err = 0;
KeydbResourceType rt = KEYDB_RESOURCE_TYPE_NONE;
if (auto_created)
*auto_created = 0;
/* Do we have an URL?
gnupg-kbx:filename := this is a plain keybox
filename := See what it is, but create as plain keybox.
*/
if (strlen (resname) > 10)
{
if (!strncmp (resname, "gnupg-kbx:", 10) )
{
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
resname += 10;
}
#if !defined(HAVE_DRIVE_LETTERS) && !defined(__riscos__)
else if (strchr (resname, ':'))
{
log_error ("invalid key resource URL '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
#endif /* !HAVE_DRIVE_LETTERS && !__riscos__ */
}
if (*resname != DIRSEP_C )
{ /* do tilde expansion etc */
if (strchr(resname, DIRSEP_C) )
filename = make_filename (resname, NULL);
else
filename = make_filename (gnupg_homedir (), resname, NULL);
}
else
filename = xstrdup (resname);
if (!force)
force = !any_registered;
/* see whether we can determine the filetype */
if (rt == KEYDB_RESOURCE_TYPE_NONE)
{
estream_t fp;
fp = es_fopen( filename, "rb" );
if (fp)
{
u32 magic;
/* FIXME: check for the keybox magic */
if (es_fread (&magic, 4, 1, fp) == 1 )
{
if (magic == 0x13579ace || magic == 0xce9a5713)
; /* GDBM magic - no more support */
else
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
else /* maybe empty: assume keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
es_fclose (fp);
}
else /* no file yet: create keybox */
rt = KEYDB_RESOURCE_TYPE_KEYBOX;
}
switch (rt)
{
case KEYDB_RESOURCE_TYPE_NONE:
log_error ("unknown type of key resource '%s'\n", url );
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = maybe_create_keybox (filename, force, auto_created);
if (err)
goto leave;
/* Now register the file */
{
void *token;
err = keybox_register_file (filename, 0, &token);
if (gpg_err_code (err) == GPG_ERR_EEXIST)
; /* Already registered - ignore. */
else if (err)
; /* Other error. */
else if (used_resources >= MAX_KEYDB_RESOURCES)
err = gpg_error (GPG_ERR_RESOURCE_LIMIT);
else
{
KEYBOX_HANDLE kbxhd;
all_resources[used_resources].type = rt;
all_resources[used_resources].u.kr = NULL; /* Not used here */
all_resources[used_resources].token = token;
/* Do a compress run if needed and the keybox is not locked. */
kbxhd = keybox_new_x509 (token, 0);
if (kbxhd)
{
if (!keybox_lock (kbxhd, 1, 0))
{
keybox_compress (kbxhd);
keybox_lock (kbxhd, 0, 0);
}
keybox_release (kbxhd);
}
used_resources++;
}
}
break;
default:
log_error ("resource type of '%s' not supported\n", url);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* fixme: check directory permissions and print a warning */
leave:
if (err)
{
if (gpg_err_code (err) != GPG_ERR_TRUE)
{
log_error ("keyblock resource '%s': %s\n",
filename, gpg_strerror (err));
gpgsm_status_with_error (ctrl, STATUS_ERROR,
"add_keyblock_resource", err);
}
}
else
any_registered = 1;
xfree (filename);
return err;
}
/* Print a warning if the server's version number is less than our
version number. Returns an error code on a connection problem. */
static gpg_error_t
warn_version_mismatch (ctrl_t ctrl, assuan_context_t ctx,
const char *servername)
{
return warn_server_version_mismatch (ctx, servername, 0,
gpgsm_status2, ctrl,
!opt.quiet);
}
/* Connect to the keybox daemon and launch it if necessary. Handle
* the server's initial greeting and set global options. Returns a
* new assuan context or an error. */
static gpg_error_t
create_new_context (ctrl_t ctrl, assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
*r_ctx = NULL;
err = start_new_keyboxd (&ctx,
GPG_ERR_SOURCE_DEFAULT,
opt.keyboxd_program,
opt.autostart?ASSHELP_FLAG_AUTOSTART:0,
opt.verbose, DBG_IPC,
NULL, ctrl);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_KEYBOXD)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no keyboxd running in this session\n"));
}
}
else if (!err && !(err = warn_version_mismatch (ctrl, ctx, KEYBOXD_NAME)))
{
/* Place to emit global options. */
}
if (err)
assuan_release (ctx);
else
*r_ctx = ctx;
return err;
}
/* Get a context for accessing keyboxd. If no context is available a
* new one is created and if necessary keyboxd is started. R_KBL
* receives a pointer to the local context object. */
static gpg_error_t
open_context (ctrl_t ctrl, keydb_local_t *r_kbl)
{
gpg_error_t err;
keydb_local_t kbl;
*r_kbl = NULL;
for (;;)
{
for (kbl = ctrl->keydb_local; kbl && kbl->is_active; kbl = kbl->next)
;
if (kbl)
{
/* Found an inactive keyboxd session - return that. */
log_assert (!kbl->is_active);
kbl->is_active = 1;
kbl->need_search_reset = 1;
*r_kbl = kbl;
return 0;
}
/* None found. Create a new session and retry. */
kbl = xtrycalloc (1, sizeof *kbl);
if (!kbl)
return gpg_error_from_syserror ();
err = create_new_context (ctrl, &kbl->ctx);
if (err)
{
xfree (kbl);
return err;
}
err = kbx_client_data_new (&kbl->kcd, kbl->ctx, 0);
if (err)
{
assuan_release (kbl->ctx);
xfree (kbl);
return err;
}
/* For thread-saftey we add it to the list and retry; this is
* easier than to employ a lock. */
kbl->next = ctrl->keydb_local;
ctrl->keydb_local = kbl;
}
/*NOTREACHED*/
}
KEYDB_HANDLE
keydb_new (ctrl_t ctrl)
{
gpg_error_t err;
KEYDB_HANDLE hd;
int rc, i, j;
if (DBG_CLOCK)
log_clock ("%s: enter\n", __func__);
hd = xcalloc (1, sizeof *hd);
hd->found = -1;
hd->saved_found = -1;
hd->use_keyboxd = opt.use_keyboxd;
hd->ctrl = ctrl;
if (hd->use_keyboxd)
{
err = open_context (ctrl, &hd->kbl);
if (err)
{
log_error (_("error opening key DB: %s\n"), gpg_strerror (err));
xfree (hd);
hd = NULL;
if (!(rc = gpg_err_code_to_errno (err)))
rc = gpg_err_code_to_errno (GPG_ERR_EIO);
gpg_err_set_errno (rc);
goto leave;
}
}
else /* Use the local keybox. */
{
log_assert (used_resources <= MAX_KEYDB_RESOURCES);
for (i=j=0; i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
hd->active[j].type = all_resources[i].type;
hd->active[j].token = all_resources[i].token;
hd->active[j].u.kr = keybox_new_x509 (all_resources[i].token, 0);
if (!hd->active[j].u.kr)
{
xfree (hd);
return NULL; /* fixme: free all previously allocated handles*/
}
j++;
break;
}
}
hd->used = j;
}
active_handles++;
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (hd=%p)\n", __func__, hd);
return hd;
}
void
keydb_release (KEYDB_HANDLE hd)
{
keydb_local_t kbl;
int i;
if (!hd)
return;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
log_assert (active_handles > 0);
active_handles--;
if (hd->use_keyboxd)
{
kbl = hd->kbl;
if (DBG_CLOCK)
log_clock ("close_context (found)");
if (!kbl->is_active)
log_fatal ("closing inactive keyboxd context %p\n", kbl);
kbl->is_active = 0;
hd->kbl = NULL;
}
else
{
hd->keep_lock = 0;
unlock_all (hd);
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_release (hd->active[i].u.kr);
break;
}
}
}
xfree (hd);
if (DBG_CLOCK)
log_clock ("%s: leave\n", __func__);
}
/* Return the name of the current resource. This is function first
looks for the last found found, then for the current search
position, and last returns the first available resource. The
returned string is only valid as long as the handle exists. This
function does only return NULL if no handle is specified, in all
other error cases an empty string is returned. */
const char *
keydb_get_resource_name (KEYDB_HANDLE hd)
{
int idx;
const char *s = NULL;
if (!hd)
return NULL;
if (hd->use_keyboxd)
return "[keyboxd]";
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
idx = 0;
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
s = NULL;
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
s = keybox_get_resource_name (hd->active[idx].u.kr);
break;
}
return s? s: "";
}
/* Switch the handle into ephemeral mode and return the original value. */
int
keydb_set_ephemeral (KEYDB_HANDLE hd, int yes)
{
int i;
if (!hd)
return 0;
if (hd->use_keyboxd)
return 0; /* FIXME: No support yet. */
yes = !!yes;
if (hd->is_ephemeral != yes)
{
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_set_ephemeral (hd->active[i].u.kr, yes);
break;
}
}
}
i = hd->is_ephemeral;
hd->is_ephemeral = yes;
return i;
}
/* If the keyring has not yet been locked, lock it now. This
* operation is required before any update operation; it is optional
* for an insert operation. The lock is kept until a keydb_release so
* that internal unlock_all calls have no effect. */
gpg_error_t
keydb_lock (KEYDB_HANDLE hd)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_HANDLE);
if (hd->use_keyboxd)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
err = lock_all (hd);
if (!err)
hd->keep_lock = 1;
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
static int
lock_all (KEYDB_HANDLE hd)
{
int i, rc = 0;
if (hd->use_keyboxd)
return 0;
/* Fixme: This locking scheme may lead to deadlock if the resources
are not added in the same order by all processes. We are
currently only allowing one resource so it is not a problem. */
for (i=0; i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
rc = keybox_lock (hd->active[i].u.kr, 1, -1);
break;
}
if (rc)
break;
}
if (rc)
{
/* Revert the already set locks. */
for (i--; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kr, 0, 0);
break;
}
}
}
else
hd->locked = 1;
return rc;
}
static void
unlock_all (KEYDB_HANDLE hd)
{
int i;
if (hd->use_keyboxd)
return;
if (!hd->locked || hd->keep_lock)
return;
for (i=hd->used-1; i >= 0; i--)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_lock (hd->active[i].u.kr, 0, 0);
break;
}
}
hd->locked = 0;
}
/* Push the last found state if any. Only one state is saved. */
void
keydb_push_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->use_keyboxd)
{
xfree (hd->kbl->saved_search_result.buf);
hd->kbl->saved_search_result.buf = hd->kbl->search_result.buf;
hd->kbl->saved_search_result.len = hd->kbl->search_result.len;
hd->kbl->search_result.buf = NULL;
hd->kbl->search_result.len = 0;
}
else
{
if (hd->found < 0 || hd->found >= hd->used)
hd->saved_found = -1;
else
{
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_push_found_state (hd->active[hd->found].u.kr);
break;
}
hd->saved_found = hd->found;
hd->found = -1;
}
}
if (DBG_CLOCK)
log_clock ("%s: done (hd=%p)\n", __func__, hd);
}
/* Pop the last found state. */
void
keydb_pop_found_state (KEYDB_HANDLE hd)
{
if (!hd)
return;
if (hd->use_keyboxd)
{
xfree (hd->kbl->search_result.buf);
hd->kbl->search_result.buf = hd->kbl->saved_search_result.buf;
hd->kbl->search_result.len = hd->kbl->saved_search_result.len;
hd->kbl->saved_search_result.buf = NULL;
hd->kbl->saved_search_result.len = 0;
}
else
{
hd->found = hd->saved_found;
hd->saved_found = -1;
if (hd->found < 0 || hd->found >= hd->used)
;
else
{
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
keybox_pop_found_state (hd->active[hd->found].u.kr);
break;
}
}
}
if (DBG_CLOCK)
log_clock ("%s: done (hd=%p)\n", __func__, hd);
}
/* Return the last found certificate. Caller must free it. */
int
keydb_get_cert (KEYDB_HANDLE hd, ksba_cert_t *r_cert)
{
int err = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
ksba_cert_t cert;
/* Fixme: We should clear that also in non-keyboxd mode but we
* did not in the past and thus all code should be checked
* whether this is okay. If we run into error in keyboxd mode,
* this is a not as severe because keyboxd is currently
* experimental. */
*r_cert = NULL;
if (!hd->kbl->search_result.buf || !hd->kbl->search_result.len)
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_init_from_mem (cert,
hd->kbl->search_result.buf,
hd->kbl->search_result.len);
if (err)
{
ksba_cert_release (cert);
goto leave;
}
*r_cert = cert;
goto leave;
}
if ( hd->found < 0 || hd->found >= hd->used)
{
/* Fixme: It would be better to use GPG_ERR_VALUE_NOT_FOUND here
* but for now we use NOT_FOUND because that is our standard
* replacement for the formerly used (-1). */
err = gpg_error (GPG_ERR_NOT_FOUND); /* nothing found */
goto leave;
}
err = GPG_ERR_BUG;
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_get_cert (hd->active[hd->found].u.kr, r_cert);
break;
}
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (rc=%d)\n", __func__, err);
return err;
}
/* Return a flag of the last found object. WHICH is the flag requested;
it should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored at the address given by
VALUE. Return 0 on success or an error code. */
gpg_error_t
keydb_get_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int *value)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
/* FIXME */
*value = 0;
err = 0;
goto leave;
}
if ( hd->found < 0 || hd->found >= hd->used)
{
err = gpg_error (GPG_ERR_NOTHING_FOUND);
goto leave;
}
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_get_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/* Set a flag of the last found object. WHICH is the flag to be set; it
should be one of the KEYBOX_FLAG_ values. If the operation is
successful, the flag value will be stored in the keybox. Note,
that some flag values can't be updated and thus may return an
error, some other flag values may be masked out before an update.
Returns 0 on success or an error code. */
gpg_error_t
keydb_set_flags (KEYDB_HANDLE hd, int which, int idx, unsigned int value)
{
gpg_error_t err = 0;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
/* FIXME */
goto leave;
}
if ( hd->found < 0 || hd->found >= hd->used)
{
err = gpg_error (GPG_ERR_NOTHING_FOUND);
goto leave;
}
if (!hd->locked)
{
err = gpg_error (GPG_ERR_NOT_LOCKED);
goto leave;
}
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_set_flags (hd->active[hd->found].u.kr, which, idx, value);
break;
}
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/* Default status callback used to show diagnostics from the keyboxd */
static gpg_error_t
keydb_default_status_cb (void *opaque, const char *line)
{
const char *s;
(void)opaque;
if ((s = has_leading_keyword (line, "NOTE")))
log_info (_("Note: %s\n"), s);
else if ((s = has_leading_keyword (line, "WARNING")))
log_info (_("WARNING: %s\n"), s);
return 0;
}
/* Communication object for Keyboxd STORE commands. */
struct store_parm_s
{
assuan_context_t ctx;
const void *data; /* The certificate in X.509 binary format. */
size_t datalen; /* The length of DATA. */
};
/* Handle the inquiries from the STORE command. */
static gpg_error_t
store_inq_cb (void *opaque, const char *line)
{
struct store_parm_s *parm = opaque;
gpg_error_t err = 0;
if (has_leading_keyword (line, "BLOB"))
{
if (parm->data)
err = assuan_send_data (parm->ctx, parm->data, parm->datalen);
}
else
return gpg_error (GPG_ERR_ASS_UNKNOWN_INQUIRE);
return err;
}
/*
* Insert a new Certificate into one of the resources.
*/
gpg_error_t
keydb_insert_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
gpg_error_t err;
int idx;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (opt.dry_run)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
struct store_parm_s parm;
parm.ctx = hd->kbl->ctx;
parm.data = ksba_cert_get_image (cert, &parm.datalen);
if (!parm.data)
{
log_debug ("broken ksba cert object\n");
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
err = assuan_transact (hd->kbl->ctx, "STORE --insert",
NULL, NULL,
store_inq_cb, &parm,
keydb_default_status_cb, hd);
goto leave;
}
if ( hd->found >= 0 && hd->found < hd->used)
idx = hd->found;
else if ( hd->current >= 0 && hd->current < hd->used)
idx = hd->current;
else
{
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!hd->locked)
{
err = gpg_error (GPG_ERR_NOT_LOCKED);
goto leave;
}
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[idx].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_insert_cert (hd->active[idx].u.kr, cert, digest);
break;
}
unlock_all (hd);
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/* Update the current keyblock with KB. */
/* Note: This function is currently not called. */
gpg_error_t
keydb_update_cert (KEYDB_HANDLE hd, ksba_cert_t cert)
{
(void)hd;
(void)cert;
return GPG_ERR_BUG;
#if 0
gpg_error_t err;
unsigned char digest[20];
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if ( hd->found < 0 || hd->found >= hd->used)
return gpg_error (GPG_ERR_NOT_FOUND);
if (opt.dry_run)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
/* FIXME */
goto leave;
}
err = lock_all (hd);
if (err)
goto leave;
gpgsm_get_fingerprint (cert, GCRY_MD_SHA1, digest, NULL); /* kludge*/
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL); /* oops */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_update_cert (hd->active[hd->found].u.kr, cert, digest);
break;
}
unlock_all (hd);
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
#endif /*0*/
}
/*
* The current keyblock or cert will be deleted.
*/
gpg_error_t
keydb_delete (KEYDB_HANDLE hd)
{
gpg_error_t err;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!hd->use_keyboxd && (hd->found < 0 || hd->found >= hd->used))
return gpg_error (GPG_ERR_NOT_FOUND);
if (opt.dry_run)
return 0;
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (hd->use_keyboxd)
{
unsigned char hexubid[UBID_LEN * 2 + 1];
char line[ASSUAN_LINELENGTH];
if (!hd->last_ubid_valid)
{
err = gpg_error (GPG_ERR_VALUE_NOT_FOUND);
goto leave;
}
bin2hex (hd->last_ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "DELETE %s", hexubid);
err = assuan_transact (hd->kbl->ctx, line,
NULL, NULL,
NULL, NULL,
keydb_default_status_cb, hd);
goto leave;
}
if (!hd->locked)
{
err = gpg_error (GPG_ERR_NOT_LOCKED);
goto leave;
}
err = gpg_error (GPG_ERR_BUG);
switch (hd->active[hd->found].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
err = gpg_error (GPG_ERR_GENERAL);
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_delete (hd->active[hd->found].u.kr);
break;
}
unlock_all (hd);
leave:
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
/*
* Locate the default writable key resource, so that the next
* operation (which is only relevant for inserts) will be done on this
* resource.
*/
static gpg_error_t
keydb_locate_writable (KEYDB_HANDLE hd, const char *reserved)
{
int rc;
(void)reserved;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (hd->use_keyboxd)
return 0; /* Not required. */
rc = keydb_search_reset (hd); /* this does reset hd->current */
if (rc)
return rc;
for ( ; hd->current >= 0 && hd->current < hd->used; hd->current++)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG();
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
if (keybox_is_writable (hd->active[hd->current].token))
return 0; /* found (hd->current is set to it) */
break;
}
}
return gpg_error (GPG_ERR_NOT_FOUND);
}
/*
* Rebuild the caches of all key resources.
*/
void
keydb_rebuild_caches (void)
{
int i;
/* This function does nothing and thus we don't need to handle keyboxd in a
* special way. */
for (i=0; i < used_resources; i++)
{
switch (all_resources[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE: /* ignore */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
/* rc = keybox_rebuild_cache (all_resources[i].token); */
/* if (rc) */
/* log_error (_("failed to rebuild keybox cache: %s\n"), */
/* g10_errstr (rc)); */
break;
}
}
}
/*
* Start the next search on this handle right at the beginning
*/
gpg_error_t
keydb_search_reset (KEYDB_HANDLE hd)
{
gpg_error_t err = 0;
int i;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
hd->current = 0;
hd->found = -1;
if (hd->use_keyboxd)
{
/* All we need is to tell search that a reset is pending. Note that
* keydb_new sets this flag as well. To comply with the
* specification of keydb_delete_keyblock we also need to clear the
* ubid flag so that after a reset a delete can't be performed. */
hd->kbl->need_search_reset = 1;
hd->last_ubid_valid = 0;
}
else
{
/* Reset all resources */
for (i=0; !err && i < hd->used; i++)
{
switch (hd->active[i].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_search_reset (hd->active[i].u.kr);
break;
}
}
}
if (DBG_CLOCK)
log_clock ("%s: leave (err=%s)\n", __func__, gpg_strerror (err));
return err;
}
char *
keydb_search_desc_dump (struct keydb_search_desc *desc)
{
char *fpr;
char *result;
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
return xasprintf ("EXACT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SUBSTR:
return xasprintf ("SUBSTR: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAIL:
return xasprintf ("MAIL: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILSUB:
return xasprintf ("MAILSUB: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_MAILEND:
return xasprintf ("MAILEND: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_WORDS:
return xasprintf ("WORDS: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_SHORT_KID:
return xasprintf ("SHORT_KID: '%08lX'", (ulong)desc->u.kid[1]);
case KEYDB_SEARCH_MODE_LONG_KID:
return xasprintf ("LONG_KID: '%08lX%08lX'",
(ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
case KEYDB_SEARCH_MODE_FPR:
fpr = bin2hexcolon (desc->u.fpr, desc->fprlen, NULL);
result = xasprintf ("FPR%02d: '%s'", desc->fprlen, fpr);
xfree (fpr);
return result;
case KEYDB_SEARCH_MODE_ISSUER:
return xasprintf ("ISSUER: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_ISSUER_SN:
return xasprintf ("ISSUER_SN: '#%.*s/%s'",
(int)desc->snlen,desc->sn, desc->u.name);
case KEYDB_SEARCH_MODE_SN:
return xasprintf ("SN: '%.*s'",
(int)desc->snlen, desc->sn);
case KEYDB_SEARCH_MODE_SUBJECT:
return xasprintf ("SUBJECT: '%s'", desc->u.name);
case KEYDB_SEARCH_MODE_KEYGRIP:
return xasprintf ("KEYGRIP: %s", desc->u.grip);
case KEYDB_SEARCH_MODE_FIRST:
return xasprintf ("FIRST");
case KEYDB_SEARCH_MODE_NEXT:
return xasprintf ("NEXT");
default:
return xasprintf ("Bad search mode (%d)", desc->mode);
}
}
-/* Status callback for SEARCH and NEXT operaions. */
+/* Status callback for SEARCH and NEXT operations. */
static gpg_error_t
search_status_cb (void *opaque, const char *line)
{
KEYDB_HANDLE hd = opaque;
gpg_error_t err = 0;
const char *s;
unsigned int n;
if ((s = has_leading_keyword (line, "PUBKEY_INFO")))
{
if (atoi (s) != PUBKEY_TYPE_X509)
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
else
{
hd->last_ubid_valid = 0;
while (*s && !spacep (s))
s++;
if (!(n=hex2fixedbuf (s, hd->last_ubid, sizeof hd->last_ubid)))
err = gpg_error (GPG_ERR_INV_VALUE);
else
{
hd->last_ubid_valid = 1;
s += n;
hd->last_is_ephemeral = (*s == 'e');
}
}
}
else
err = keydb_default_status_cb (opaque, line);
return err;
}
/* Search through all keydb resources, starting at the current
* position, for a keyblock which contains one of the keys described
* in the DESC array. In keyboxd mode the search is instead delegated
* to the keyboxd.
*
* DESC is an array of search terms with NDESC entries. The search
* terms are or'd together. That is, the next entry in the DB that
* matches any of the descriptions will be returned.
*
* Note: this function resumes searching where the last search left
* off (i.e., at the current file position). If you want to search
* from the start of the database, then you need to first call
* keydb_search_reset().
*
* If no key matches the search description, the error code
- * GPG_ERR_NOT_FOUND is retruned. If there was a match, 0 is
+ * GPG_ERR_NOT_FOUND is returned. If there was a match, 0 is
* returned. If an error occurred, that error code is returned.
*
* The returned key is considered to be selected and the certificate
* can be detched via keydb_get_cert. */
gpg_error_t
keydb_search (ctrl_t ctrl, KEYDB_HANDLE hd,
KEYDB_SEARCH_DESC *desc, size_t ndesc)
{
gpg_error_t err = gpg_error (GPG_ERR_EOF);
unsigned long skipped = 0;
int i;
if (!hd)
return gpg_error (GPG_ERR_INV_VALUE);
if (!any_registered && !hd->use_keyboxd)
{
gpgsm_status_with_error (ctrl, STATUS_ERROR, "keydb_search",
gpg_error (GPG_ERR_KEYRING_OPEN));
return gpg_error (GPG_ERR_NOT_FOUND);
}
if (DBG_CLOCK)
log_clock ("%s: enter (hd=%p)\n", __func__, hd);
if (DBG_LOOKUP)
{
log_debug ("%s: %zd search description(s):\n", __func__, ndesc);
for (i = 0; i < ndesc; i ++)
{
char *t = keydb_search_desc_dump (&desc[i]);
log_debug ("%s: %d: %s\n", __func__, i, t);
xfree (t);
}
}
if (hd->use_keyboxd)
{
char line[ASSUAN_LINELENGTH];
/* Clear the result objects. */
if (hd->kbl->search_result.buf)
{
xfree (hd->kbl->search_result.buf);
hd->kbl->search_result.buf = NULL;
hd->kbl->search_result.len = 0;
}
/* Check whether this is a NEXT search. */
if (!hd->kbl->need_search_reset)
{
/* A reset was not requested thus continue the search. The
* keyboxd keeps the context of the search and thus the NEXT
* operates on the last search pattern. This is the way how
* we always used the keydb functions. In theory we were
* able to modify the search pattern between searches but
* that is not anymore supported by keyboxd and a cursory
* check does not show that we actually made use of that
* misfeature. */
snprintf (line, sizeof line, "NEXT --x509");
goto do_search;
}
hd->kbl->need_search_reset = 0;
if (!ndesc)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* FIXME: Implement --multi */
switch (desc->mode)
{
case KEYDB_SEARCH_MODE_EXACT:
snprintf (line, sizeof line, "SEARCH --x509 =%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SUBSTR:
snprintf (line, sizeof line, "SEARCH --x509 *%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_MAIL:
snprintf (line, sizeof line, "SEARCH --x509 <%s",
desc[0].u.name + (desc[0].u.name[0] == '<'));
break;
case KEYDB_SEARCH_MODE_MAILSUB:
snprintf (line, sizeof line, "SEARCH --x509 @%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_MAILEND:
snprintf (line, sizeof line, "SEARCH --x509 .%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_WORDS:
snprintf (line, sizeof line, "SEARCH --x509 +%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SHORT_KID:
snprintf (line, sizeof line, "SEARCH --x509 0x%08lX",
(ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_LONG_KID:
snprintf (line, sizeof line, "SEARCH --x509 0x%08lX%08lX",
(ulong)desc->u.kid[0], (ulong)desc->u.kid[1]);
break;
case KEYDB_SEARCH_MODE_FPR:
{
unsigned char hexfpr[MAX_FINGERPRINT_LEN * 2 + 1];
log_assert (desc[0].fprlen <= MAX_FINGERPRINT_LEN);
bin2hex (desc[0].u.fpr, desc[0].fprlen, hexfpr);
snprintf (line, sizeof line, "SEARCH --x509 0x%s", hexfpr);
}
break;
case KEYDB_SEARCH_MODE_ISSUER:
snprintf (line, sizeof line, "SEARCH --x509 #/%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_ISSUER_SN:
if (desc[0].snhex)
snprintf (line, sizeof line, "SEARCH --x509 #%.*s/%s",
(int)desc[0].snlen, desc[0].sn, desc[0].u.name);
else
{
char *hexsn = bin2hex (desc[0].sn, desc[0].snlen, NULL);
if (!hexsn)
{
err = gpg_error_from_syserror ();
goto leave;
}
snprintf (line, sizeof line, "SEARCH --x509 #%s/%s",
hexsn, desc[0].u.name);
xfree (hexsn);
}
break;
case KEYDB_SEARCH_MODE_SN:
snprintf (line, sizeof line, "SEARCH --x509 #%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_SUBJECT:
snprintf (line, sizeof line, "SEARCH --x509 /%s", desc[0].u.name);
break;
case KEYDB_SEARCH_MODE_KEYGRIP:
{
unsigned char hexgrip[KEYGRIP_LEN * 2 + 1];
bin2hex (desc[0].u.grip, KEYGRIP_LEN, hexgrip);
snprintf (line, sizeof line, "SEARCH --x509 &%s", hexgrip);
}
break;
case KEYDB_SEARCH_MODE_UBID:
{
unsigned char hexubid[UBID_LEN * 2 + 1];
bin2hex (desc[0].u.ubid, UBID_LEN, hexubid);
snprintf (line, sizeof line, "SEARCH --x509 ^%s", hexubid);
}
break;
case KEYDB_SEARCH_MODE_FIRST:
snprintf (line, sizeof line, "SEARCH --x509");
break;
case KEYDB_SEARCH_MODE_NEXT:
log_debug ("%s: mode next - we should not get to here!\n", __func__);
snprintf (line, sizeof line, "NEXT --x509");
break;
default:
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
do_search:
hd->last_ubid_valid = 0;
/* To avoid silent truncation we error out on a too long line. */
if (strlen (line) + 5 >= sizeof line)
err = gpg_error (GPG_ERR_ASS_LINE_TOO_LONG);
else
err = kbx_client_data_cmd (hd->kbl->kcd, line, search_status_cb, hd);
if (!err && !(err = kbx_client_data_wait (hd->kbl->kcd,
&hd->kbl->search_result.buf,
&hd->kbl->search_result.len)))
{
/* if (hd->last_ubid_valid) */
/* log_printhex (hd->last_ubid, 20, "found UBID%s:", */
/* hd->last_is_ephemeral? "(ephemeral)":""); */
}
}
else /* Local keyring search. */
{
while (gpg_err_code (err) == GPG_ERR_EOF
&& hd->current >= 0 && hd->current < hd->used)
{
switch (hd->active[hd->current].type)
{
case KEYDB_RESOURCE_TYPE_NONE:
BUG(); /* we should never see it here */
break;
case KEYDB_RESOURCE_TYPE_KEYBOX:
err = keybox_search (hd->active[hd->current].u.kr, desc, ndesc,
KEYBOX_BLOBTYPE_X509,
NULL, &skipped);
if (err == -1) /* Map legacy code. */
err = gpg_error (GPG_ERR_EOF);
break;
}
if (DBG_LOOKUP)
log_debug ("%s: searched %s (resource %d of %d) => %s\n",
__func__,
hd->active[hd->current].type==KEYDB_RESOURCE_TYPE_KEYBOX
? "keybox" : "unknown type",
hd->current, hd->used, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_EOF)
{ /* EOF -> switch to next resource */
hd->current++;
}
else if (!err)
hd->found = hd->current;
}
}
leave:
/* The NOTHING_FOUND error is triggered by a NEXT command. */
if (gpg_err_code (err) == GPG_ERR_EOF
|| gpg_err_code (err) == GPG_ERR_NOTHING_FOUND)
err = gpg_error (GPG_ERR_NOT_FOUND);
if (DBG_CLOCK)
log_clock ("%s: leave (%s)\n", __func__, gpg_strerror (err));
return err;
}
int
keydb_search_first (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FIRST;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_next (ctrl_t ctrl, KEYDB_HANDLE hd)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_NEXT;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_kid (ctrl_t ctrl, KEYDB_HANDLE hd, u32 *kid)
{
KEYDB_SEARCH_DESC desc;
(void)kid;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_LONG_KID;
desc.u.kid[0] = kid[0];
desc.u.kid[1] = kid[1];
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_fpr (ctrl_t ctrl, KEYDB_HANDLE hd, const byte *fpr)
{
KEYDB_SEARCH_DESC desc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_FPR;
memcpy (desc.u.fpr, fpr, 20);
desc.fprlen = 20;
return keydb_search (ctrl, hd, &desc, 1);
}
int
keydb_search_issuer (ctrl_t ctrl, KEYDB_HANDLE hd, const char *issuer)
{
KEYDB_SEARCH_DESC desc;
int rc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_ISSUER;
desc.u.name = issuer;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_issuer_sn (ctrl_t ctrl, KEYDB_HANDLE hd,
const char *issuer, ksba_const_sexp_t serial)
{
KEYDB_SEARCH_DESC desc;
int rc;
const unsigned char *s;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_ISSUER_SN;
s = serial;
if (*s !='(')
return gpg_error (GPG_ERR_INV_VALUE);
s++;
for (desc.snlen = 0; digitp (s); s++)
desc.snlen = 10*desc.snlen + atoi_1 (s);
if (*s !=':')
return gpg_error (GPG_ERR_INV_VALUE);
desc.sn = s+1;
desc.u.name = issuer;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
int
keydb_search_subject (ctrl_t ctrl, KEYDB_HANDLE hd, const char *name)
{
KEYDB_SEARCH_DESC desc;
int rc;
memset (&desc, 0, sizeof desc);
desc.mode = KEYDB_SEARCH_MODE_SUBJECT;
desc.u.name = name;
rc = keydb_search (ctrl, hd, &desc, 1);
return rc;
}
/* Store the certificate in the key DB but make sure that it does not
already exists. We do this simply by comparing the fingerprint.
If EXISTED is not NULL it will be set to true if the certificate
was already in the DB. */
int
keydb_store_cert (ctrl_t ctrl, ksba_cert_t cert, int ephemeral, int *existed)
{
KEYDB_HANDLE kh;
int rc;
unsigned char fpr[20];
if (existed)
*existed = 0;
if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL))
{
log_error (_("failed to get the fingerprint\n"));
return gpg_error (GPG_ERR_GENERAL);
}
kh = keydb_new (ctrl);
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
/* Set the ephemeral flag so that the search looks at all
records. */
keydb_set_ephemeral (kh, 1);
if (!kh->use_keyboxd)
{
rc = lock_all (kh);
if (rc)
return rc;
}
rc = keydb_search_fpr (ctrl, kh, fpr);
if (gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
{
keydb_release (kh);
if (!rc)
{
if (existed)
*existed = 1;
if (!ephemeral)
{
/* Remove ephemeral flags from existing certificate to "store"
it permanently. */
rc = keydb_set_cert_flags (ctrl, cert, 1, KEYBOX_FLAG_BLOB, 0,
KEYBOX_FLAG_BLOB_EPHEMERAL, 0);
if (rc)
{
log_error ("clearing ephemeral flag failed: %s\n",
gpg_strerror (rc));
return rc;
}
}
return 0; /* okay */
}
log_error (_("problem looking for existing certificate: %s\n"),
gpg_strerror (rc));
return rc;
}
/* Reset the ephemeral flag if not requested. */
if (!ephemeral)
keydb_set_ephemeral (kh, 0);
rc = keydb_locate_writable (kh, 0);
if (rc)
{
log_error (_("error finding writable keyDB: %s\n"), gpg_strerror (rc));
keydb_release (kh);
return rc;
}
rc = keydb_insert_cert (kh, cert);
if (rc)
{
log_error (_("error storing certificate: %s\n"), gpg_strerror (rc));
keydb_release (kh);
return rc;
}
keydb_release (kh);
return 0;
}
/* This is basically keydb_set_flags but it implements a complete
transaction by locating the certificate in the DB and updating the
flags. */
gpg_error_t
keydb_set_cert_flags (ctrl_t ctrl, ksba_cert_t cert, int ephemeral,
int which, int idx,
unsigned int mask, unsigned int value)
{
KEYDB_HANDLE kh;
gpg_error_t err;
unsigned char fpr[20];
unsigned int old_value;
if (!gpgsm_get_fingerprint (cert, 0, fpr, NULL))
{
log_error (_("failed to get the fingerprint\n"));
return gpg_error (GPG_ERR_GENERAL);
}
kh = keydb_new (ctrl);
if (!kh)
{
log_error (_("failed to allocate keyDB handle\n"));
return gpg_error (GPG_ERR_ENOMEM);;
}
if (ephemeral)
keydb_set_ephemeral (kh, 1);
if (!kh->use_keyboxd)
{
err = keydb_lock (kh);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
}
err = keydb_search_fpr (ctrl, kh, fpr);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NOT_FOUND)
log_error (_("problem re-searching certificate: %s\n"),
gpg_strerror (err));
keydb_release (kh);
return err;
}
err = keydb_get_flags (kh, which, idx, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
value = ((old_value & ~mask) | (value & mask));
if (value != old_value)
{
err = keydb_set_flags (kh, which, idx, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
keydb_release (kh);
return err;
}
}
keydb_release (kh);
return 0;
}
/* Reset all the certificate flags we have stored with the certificates
for performance reasons. */
void
keydb_clear_some_cert_flags (ctrl_t ctrl, strlist_t names)
{
gpg_error_t err;
KEYDB_HANDLE hd = NULL;
KEYDB_SEARCH_DESC *desc = NULL;
int ndesc;
strlist_t sl;
int rc=0;
unsigned int old_value, value;
(void)ctrl;
hd = keydb_new (ctrl);
if (!hd)
{
log_error ("keydb_new failed\n");
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
log_error ("allocating memory failed: %s\n",
gpg_strerror (out_of_core ()));
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
log_error ("key '%s' not found: %s\n", sl->d, gpg_strerror (rc));
else
ndesc++;
}
}
if (!hd->use_keyboxd)
{
err = keydb_lock (hd);
if (err)
{
log_error (_("error locking keybox: %s\n"), gpg_strerror (err));
goto leave;
}
}
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
err = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &old_value);
if (err)
{
log_error (_("error getting stored flags: %s\n"),
gpg_strerror (err));
goto leave;
}
value = (old_value & ~VALIDITY_REVOKED);
if (value != old_value)
{
err = keydb_set_flags (hd, KEYBOX_FLAG_VALIDITY, 0, value);
if (err)
{
log_error (_("error storing flags: %s\n"), gpg_strerror (err));
goto leave;
}
}
}
if (rc && gpg_err_code (rc) != GPG_ERR_NOT_FOUND)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
xfree (desc);
keydb_release (hd);
}
diff --git a/sm/keylist.c b/sm/keylist.c
index 47fe69f30..65902480d 100644
--- a/sm/keylist.c
+++ b/sm/keylist.c
@@ -1,1901 +1,1901 @@
/* keylist.c - Print certificates in various formats.
* Copyright (C) 1998, 1999, 2000, 2001, 2003, 2004, 2005, 2008, 2009,
* 2010, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#include "gpgsm.h"
#include <gcrypt.h>
#include <ksba.h>
#include "keydb.h"
#include "../kbx/keybox.h" /* for KEYBOX_FLAG_* */
#include "../common/i18n.h"
#include "../common/tlv.h"
#include "../common/compliance.h"
#include "../common/pkscreening.h"
struct list_external_parm_s
{
ctrl_t ctrl;
estream_t fp;
int print_header;
int with_colons;
int with_chain;
int raw_mode;
};
/* Do not print this extension in the list of extensions. This is set
for oids which are already available via ksba functions. */
#define OID_FLAG_SKIP 1
/* The extension is a simple UTF8String and should be printed. */
#define OID_FLAG_UTF8 2
/* The extension can be printed as a hex string. */
#define OID_FLAG_HEX 4
-/* Define if this specififies a key purpose. */
+/* Define if this specifies a key purpose. */
#define OID_FLAG_KP 8
/* A table mapping OIDs to a descriptive string. */
static struct
{
char *oid;
char *name;
unsigned int flag; /* A flag as described above. */
} oidtranstbl[] = {
/* Algorithms. */
{ "1.2.840.10040.4.1", "dsa" },
{ "1.2.840.10040.4.3", "dsaWithSha1" },
{ "1.2.840.113549.1.1.1", "rsaEncryption" },
{ "1.2.840.113549.1.1.2", "md2WithRSAEncryption" },
{ "1.2.840.113549.1.1.3", "md4WithRSAEncryption" },
{ "1.2.840.113549.1.1.4", "md5WithRSAEncryption" },
{ "1.2.840.113549.1.1.5", "sha1WithRSAEncryption" },
{ "1.2.840.113549.1.1.7", "rsaOAEP" },
{ "1.2.840.113549.1.1.8", "rsaOAEP-MGF" },
{ "1.2.840.113549.1.1.9", "rsaOAEP-pSpecified" },
{ "1.2.840.113549.1.1.10", "rsaPSS" },
{ "1.2.840.113549.1.1.11", "sha256WithRSAEncryption" },
{ "1.2.840.113549.1.1.12", "sha384WithRSAEncryption" },
{ "1.2.840.113549.1.1.13", "sha512WithRSAEncryption" },
{ "1.3.14.3.2.26", "sha1" },
{ "1.3.14.3.2.29", "sha-1WithRSAEncryption" },
{ "1.3.36.3.3.1.2", "rsaSignatureWithripemd160" },
/* Telesec extensions. */
{ "0.2.262.1.10.12.0", "certExtensionLiabilityLimitationExt" },
{ "0.2.262.1.10.12.1", "telesecCertIdExt" },
{ "0.2.262.1.10.12.2", "telesecPolicyIdentifier" },
{ "0.2.262.1.10.12.3", "telesecPolicyQualifierID" },
{ "0.2.262.1.10.12.4", "telesecCRLFilteredExt" },
{ "0.2.262.1.10.12.5", "telesecCRLFilterExt"},
{ "0.2.262.1.10.12.6", "telesecNamingAuthorityExt" },
#define OIDSTR_restriction \
"1.3.36.8.3.8"
{ OIDSTR_restriction, "restriction", OID_FLAG_UTF8 },
/* PKIX private extensions. */
{ "1.3.6.1.5.5.7.1.1", "authorityInfoAccess" },
{ "1.3.6.1.5.5.7.1.2", "biometricInfo" },
{ "1.3.6.1.5.5.7.1.3", "qcStatements" },
{ "1.3.6.1.5.5.7.1.4", "acAuditIdentity" },
{ "1.3.6.1.5.5.7.1.5", "acTargeting" },
{ "1.3.6.1.5.5.7.1.6", "acAaControls" },
{ "1.3.6.1.5.5.7.1.7", "sbgp-ipAddrBlock" },
{ "1.3.6.1.5.5.7.1.8", "sbgp-autonomousSysNum" },
{ "1.3.6.1.5.5.7.1.9", "sbgp-routerIdentifier" },
{ "1.3.6.1.5.5.7.1.10", "acProxying" },
{ "1.3.6.1.5.5.7.1.11", "subjectInfoAccess" },
{ "1.3.6.1.5.5.7.3.1", "serverAuth", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.2", "clientAuth", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.3", "codeSigning", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.4", "emailProtection", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.5", "ipsecEndSystem", OID_FLAG_KP }, /* historic */
{ "1.3.6.1.5.5.7.3.6", "ipsecTunnel", OID_FLAG_KP }, /* historic */
{ "1.3.6.1.5.5.7.3.7", "ipsecUser", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.8", "timeStamping", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.9", "ocspSigning", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.10", "dvcs", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.11", "sbgpCertAAServerAuth", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.13", "eapOverPPP", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.14", "wlanSSID", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.3.17", "ipsecIKE", OID_FLAG_KP }, /* rfc-4945 */
{ "1.3.6.1.5.5.7.48.1", "ocsp" },
{ "1.3.6.1.5.5.7.48.1.5", "ocspNoCheck", OID_FLAG_KP },
{ "1.3.6.1.5.5.7.48.2", "caIssuers" },
{ "1.3.6.1.5.5.7.48.3", "timeStamping" },
{ "1.3.6.1.5.5.7.48.5", "caRepository" },
/* X.509 id-ce */
{ "2.5.29.14", "subjectKeyIdentifier", OID_FLAG_SKIP},
{ "2.5.29.15", "keyUsage", OID_FLAG_SKIP},
{ "2.5.29.16", "privateKeyUsagePeriod" },
{ "2.5.29.17", "subjectAltName", OID_FLAG_SKIP},
{ "2.5.29.18", "issuerAltName", OID_FLAG_SKIP},
{ "2.5.29.19", "basicConstraints", OID_FLAG_SKIP},
{ "2.5.29.20", "cRLNumber" },
{ "2.5.29.21", "cRLReason" },
{ "2.5.29.22", "expirationDate" },
{ "2.5.29.23", "instructionCode" },
{ "2.5.29.24", "invalidityDate" },
{ "2.5.29.27", "deltaCRLIndicator" },
{ "2.5.29.28", "issuingDistributionPoint" },
{ "2.5.29.29", "certificateIssuer" },
{ "2.5.29.30", "nameConstraints" },
{ "2.5.29.31", "cRLDistributionPoints", OID_FLAG_SKIP},
{ "2.5.29.32", "certificatePolicies", OID_FLAG_SKIP},
{ "2.5.29.32.0", "anyPolicy" },
{ "2.5.29.33", "policyMappings" },
{ "2.5.29.35", "authorityKeyIdentifier", OID_FLAG_SKIP},
{ "2.5.29.36", "policyConstraints" },
{ "2.5.29.37", "extKeyUsage", OID_FLAG_SKIP},
{ "2.5.29.37.0", "anyExtendedKeyUsage", OID_FLAG_KP},
{ "2.5.29.46", "freshestCRL" },
{ "2.5.29.54", "inhibitAnyPolicy" },
/* Netscape certificate extensions. */
{ "2.16.840.1.113730.1.1", "netscape-cert-type" },
{ "2.16.840.1.113730.1.2", "netscape-base-url" },
{ "2.16.840.1.113730.1.3", "netscape-revocation-url" },
{ "2.16.840.1.113730.1.4", "netscape-ca-revocation-url" },
{ "2.16.840.1.113730.1.7", "netscape-cert-renewal-url" },
{ "2.16.840.1.113730.1.8", "netscape-ca-policy-url" },
{ "2.16.840.1.113730.1.9", "netscape-homePage-url" },
{ "2.16.840.1.113730.1.10", "netscape-entitylogo" },
{ "2.16.840.1.113730.1.11", "netscape-userPicture" },
{ "2.16.840.1.113730.1.12", "netscape-ssl-server-name" },
{ "2.16.840.1.113730.1.13", "netscape-comment" },
{ "2.16.840.1.113730.4.1", "serverGatedCrypto.ns", OID_FLAG_KP },
/* GnuPG extensions */
{ "1.3.6.1.4.1.11591.2.1.1", "pkaAddress" },
{ "1.3.6.1.4.1.11591.2.2.1", "standaloneCertificate" },
{ "1.3.6.1.4.1.11591.2.2.2", "wellKnownPrivateKey" },
{ "1.3.6.1.4.1.11591.2.6.1", "gpgUsageCert", OID_FLAG_KP },
{ "1.3.6.1.4.1.11591.2.6.2", "gpgUsageSign", OID_FLAG_KP },
{ "1.3.6.1.4.1.11591.2.6.3", "gpgUsageEncr", OID_FLAG_KP },
{ "1.3.6.1.4.1.11591.2.6.4", "gpgUsageAuth", OID_FLAG_KP },
/* Extensions used by the Bundesnetzagentur. */
{ "1.3.6.1.4.1.8301.3.5", "validityModel" },
/* Yubikey extensions for attestation certificates. */
{ "1.3.6.1.4.1.41482.3.3", "yubikey-firmware-version", OID_FLAG_HEX },
{ "1.3.6.1.4.1.41482.3.7", "yubikey-serial-number", OID_FLAG_HEX },
{ "1.3.6.1.4.1.41482.3.8", "yubikey-pin-touch-policy", OID_FLAG_HEX },
{ "1.3.6.1.4.1.41482.3.9", "yubikey-formfactor", OID_FLAG_HEX },
/* Microsoft extensions. */
{ "1.3.6.1.4.1.311.3.10.3.12","ms-old-documentSigning", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.10.3.3", "ms-serverGatedCrypto", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.10.3.11","ms-keyRecovery", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.10.3.12","ms-documentSigning", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.10.3.4", "ms-encryptedFileSystem", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.10.3.4.1","ms-efsRecovery", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.20.2.1", "ms-enrollmentAgent", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.20.2.2", "ms-smartcardLogon", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.21.5", "ms-caExchange", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.21.6", "ms-keyRecovery", OID_FLAG_KP },
{ "1.3.6.1.4.1.311.21.19", "ms-dsEmailReplication", OID_FLAG_KP },
/* BSI policies. */
/* Other vendor extensions. */
{ "1.3.6.1.4.1.30205.13.1.1", "trusted-disk", OID_FLAG_KP },
{ "1.2.840.113583.1.1.5", "pdfAuthenticDocumentsTrust", OID_FLAG_KP },
{ "1.3.6.1.4.1.6449.1.3.5.2", "comodoCertifiedDeliveryService", OID_FLAG_KP },
/* ARRL */
{ "1.3.6.1.4.1.12348.1.1", "lotw-callsign" },
{ "1.3.6.1.4.1.12348.1.2", "lotw-qso-first-date" },
{ "1.3.6.1.4.1.12348.1.3", "lotw-qso-end-date" },
{ "1.3.6.1.4.1.12348.1.4", "lotw-dxcc-entity" },
/* { "1.3.6.1.4.1.12348.1.5", "lotw-fixme" }, */
{ NULL }
};
/* Return the description for OID; if no description is available NULL
* is returned. If MATCHFLAG is set the flag of the OID must match
* MATCHFLAG; otherwise NULL is returned. */
static const char *
get_oid_desc (const char *oid, unsigned int matchflag, unsigned int *flag)
{
int i;
if (oid)
for (i=0; oidtranstbl[i].oid; i++)
if (!strcmp (oidtranstbl[i].oid, oid)
&& (!matchflag || (oidtranstbl[i].flag & matchflag)))
{
if (flag)
*flag = oidtranstbl[i].flag;
return oidtranstbl[i].name;
}
if (flag)
*flag = 0;
return NULL;
}
static void
print_key_data (ksba_cert_t cert, estream_t fp)
{
#if 0
int n = pk ? pubkey_get_npkey( pk->pubkey_algo ) : 0;
int i;
for(i=0; i < n; i++ )
{
es_fprintf (fp, "pkd:%d:%u:", i, mpi_get_nbits( pk->pkey[i] ) );
mpi_print(stdout, pk->pkey[i], 1 );
putchar(':');
putchar('\n');
}
#else
(void)cert;
(void)fp;
#endif
}
/* Various public key screenings. (Right now just ROCA). With
* COLON_MODE set the output is formatted for use in the compliance
* field of a colon listing. */
static void
print_pk_screening (ksba_cert_t cert, int colon_mode, estream_t fp)
{
gpg_error_t err;
gcry_mpi_t modulus;
modulus = gpgsm_get_rsa_modulus (cert);
if (modulus)
{
err = screen_key_for_roca (modulus);
if (!err)
;
else if (gpg_err_code (err) == GPG_ERR_TRUE)
{
if (colon_mode)
es_fprintf (fp, colon_mode > 1? " %d":"%d", 6001);
else
es_fprintf (fp, " screening: ROCA vulnerability detected\n");
}
else if (!colon_mode)
es_fprintf (fp, " screening: [ROCA check failed: %s]\n",
gpg_strerror (err));
gcry_mpi_release (modulus);
}
}
static void
print_capabilities (ksba_cert_t cert, int algo, estream_t fp)
{
gpg_error_t err;
unsigned int use;
unsigned int is_encr, is_sign, is_cert;
size_t buflen;
char buffer[1];
err = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!err && buflen)
{
if (*buffer)
es_putc ('q', fp);
}
else if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (err));
err = ksba_cert_get_key_usage (cert, &use);
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
{
es_putc ('e', fp);
es_putc ('s', fp);
es_putc ('c', fp);
es_putc ('E', fp);
es_putc ('S', fp);
es_putc ('C', fp);
return;
}
if (err)
{
log_error (_("error getting key usage information: %s\n"),
gpg_strerror (err));
return;
}
is_encr = is_sign = is_cert = 0;
if ((use & (KSBA_KEYUSAGE_KEY_ENCIPHERMENT|KSBA_KEYUSAGE_DATA_ENCIPHERMENT)))
is_encr = 1;
if ((use & (KSBA_KEYUSAGE_DIGITAL_SIGNATURE|KSBA_KEYUSAGE_NON_REPUDIATION)))
is_sign = 1;
if ((use & KSBA_KEYUSAGE_KEY_CERT_SIGN))
is_cert = 1;
/* We need to returned the faked key usage to frontends so that they
* can select the right key. Note that we don't do this for the
* human readable keyUsage. */
if ((algo == GCRY_PK_ECC || (opt.compat_flags & COMPAT_ALLOW_KA_TO_ENCR))
&& (use & KSBA_KEYUSAGE_KEY_AGREEMENT))
is_encr = 1;
if (is_encr)
es_putc ('e', fp);
if (is_sign)
es_putc ('s', fp);
if (is_cert)
es_putc ('c', fp);
if (is_encr)
es_putc ('E', fp);
if (is_sign)
es_putc ('S', fp);
if (is_cert)
es_putc ('C', fp);
}
static void
print_time (gnupg_isotime_t t, estream_t fp)
{
if (!t || !*t)
;
else
es_fputs (t, fp);
}
/* Return an allocated string with the email address extracted from a
DN. Note hat we use this code also in ../kbx/keybox-blob.c. */
static char *
email_kludge (const char *name)
{
const char *p, *string;
unsigned char *buf;
int n;
string = name;
for (;;)
{
p = strstr (string, "1.2.840.113549.1.9.1=#");
if (!p)
return NULL;
if (p == name || (p > string+1 && p[-1] == ',' && p[-2] != '\\'))
{
name = p + 22;
break;
}
string = p + 22;
}
/* This looks pretty much like an email address in the subject's DN
we use this to add an additional user ID entry. This way,
OpenSSL generated keys get a nicer and usable listing. */
for (n=0, p=name; hexdigitp (p) && hexdigitp (p+1); p +=2, n++)
;
if (!n)
return NULL;
buf = xtrymalloc (n+3);
if (!buf)
return NULL; /* oops, out of core */
*buf = '<';
for (n=1, p=name; hexdigitp (p); p +=2, n++)
buf[n] = xtoi_2 (p);
buf[n++] = '>';
buf[n] = 0;
return (char*)buf;
}
/* Print the compliance flags to field 18. ALGO is the gcrypt algo
* number. NBITS is the length of the key in bits. */
static void
print_compliance_flags (ksba_cert_t cert, int algo, unsigned int nbits,
const char *curvename, estream_t fp)
{
int indent = 0;
int hashalgo;
/* Note that we do not need to test for PK_ALGO_FLAG_RSAPSS because
* that is not a property of the key but one of the created
* signature. */
if (gnupg_pk_is_compliant (CO_DE_VS, algo, 0, NULL, nbits, curvename))
{
hashalgo = gcry_md_map_name (ksba_cert_get_digest_algo (cert));
if (gnupg_digest_is_compliant (CO_DE_VS, hashalgo))
{
es_fputs (gnupg_status_compliance_flag (CO_DE_VS), fp);
indent = 1;
}
}
if (opt.with_key_screening)
print_pk_screening (cert, 1+indent, fp);
}
/* List one certificate in colon mode */
static void
list_cert_colon (ctrl_t ctrl, ksba_cert_t cert, unsigned int validity,
estream_t fp, int have_secret)
{
int rc;
int idx;
char truststring[2];
char *p;
ksba_sexp_t sexp;
char *fpr;
ksba_isotime_t t;
gpg_error_t valerr;
int algo;
unsigned int nbits;
char *curve = NULL;
const char *chain_id;
char *chain_id_buffer = NULL;
int is_root = 0;
char *kludge_uid;
if (ctrl->with_validation)
valerr = gpgsm_validate_chain (ctrl, cert,
GNUPG_ISOTIME_NONE, NULL, 1, NULL, 0, NULL);
else
valerr = 0;
/* We need to get the fingerprint and the chaining ID in advance. */
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA1);
{
ksba_cert_t next;
rc = gpgsm_walk_cert_chain (ctrl, cert, &next);
if (!rc) /* We known the issuer's certificate. */
{
p = gpgsm_get_fingerprint_hexstring (next, GCRY_MD_SHA1);
chain_id_buffer = p;
chain_id = chain_id_buffer;
ksba_cert_release (next);
}
else if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
{
/* We have reached the root certificate. */
chain_id = fpr;
is_root = 1;
}
else
chain_id = NULL;
}
es_fputs (have_secret? "crs:":"crt:", fp);
/* Note: We can't use multiple flags, like "ei", because the
validation check does only return one error. */
truststring[0] = 0;
truststring[1] = 0;
if ((validity & VALIDITY_REVOKED)
|| gpg_err_code (valerr) == GPG_ERR_CERT_REVOKED)
*truststring = 'r';
else if (gpg_err_code (valerr) == GPG_ERR_CERT_EXPIRED)
*truststring = 'e';
else
{
/* Lets also check whether the certificate under question
expired. This is merely a hack until we found a proper way
to store the expiration flag in the keybox. */
ksba_isotime_t current_time, not_after;
gnupg_get_isotime (current_time);
if (!opt.ignore_expiration
&& !ksba_cert_get_validity (cert, 1, not_after)
&& *not_after && strcmp (current_time, not_after) > 0 )
*truststring = 'e';
else if (valerr)
{
if (gpgsm_cert_has_well_known_private_key (cert))
*truststring = 'w'; /* Well, this is dummy CA. */
else if (gpg_err_code (valerr) == GPG_ERR_NOT_TRUSTED)
*truststring = 'n'; /* Likely the root cert is not trusted. */
else
*truststring = 'i';
}
else if (ctrl->with_validation && !is_root)
*truststring = 'f';
}
/* If we have no truststring yet (i.e. the certificate might be
good) and this is a root certificate, we ask the agent whether
this is a trusted root certificate. */
if (!*truststring && is_root)
{
struct rootca_flags_s dummy_flags;
if (gpgsm_cert_has_well_known_private_key (cert))
*truststring = 'w'; /* Well, this is dummy CA. */
else
{
rc = gpgsm_agent_istrusted (ctrl, cert, NULL, &dummy_flags);
if (!rc)
*truststring = 'u'; /* Yes, we trust this one (ultimately). */
else if (gpg_err_code (rc) == GPG_ERR_NOT_TRUSTED)
*truststring = 'n'; /* No, we do not trust this one. */
/* (in case of an error we can't tell anything.) */
}
}
if (*truststring)
es_fputs (truststring, fp);
algo = gpgsm_get_key_algo_info (cert, &nbits, &curve);
es_fprintf (fp, ":%u:%d:%s:", nbits, algo, fpr+24);
ksba_cert_get_validity (cert, 0, t);
print_time (t, fp);
es_putc (':', fp);
ksba_cert_get_validity (cert, 1, t);
print_time ( t, fp);
es_putc (':', fp);
/* Field 8, serial number: */
if ((sexp = ksba_cert_get_serial (cert)))
{
int len;
const unsigned char *s = sexp;
if (*s == '(')
{
s++;
for (len=0; *s && *s != ':' && digitp (s); s++)
len = len*10 + atoi_1 (s);
if (*s == ':')
for (s++; len; len--, s++)
es_fprintf (fp,"%02X", *s);
}
xfree (sexp);
}
es_putc (':', fp);
/* Field 9, ownertrust - not used here */
es_putc (':', fp);
/* field 10, old user ID - we use it here for the issuer DN */
if ((p = ksba_cert_get_issuer (cert,0)))
{
es_write_sanitized (fp, p, strlen (p), ":", NULL);
xfree (p);
}
es_putc (':', fp);
/* Field 11, signature class - not used */
es_putc (':', fp);
/* Field 12, capabilities: */
print_capabilities (cert, algo, fp);
es_putc (':', fp);
/* Field 13, not used: */
es_putc (':', fp);
/* Field 14, not used: */
es_putc (':', fp);
if (have_secret || ctrl->with_secret)
{
char *cardsn;
p = gpgsm_get_keygrip_hexstring (cert);
if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn)
&& (cardsn || ctrl->with_secret))
{
/* Field 15: Token serial number or secret key indicator. */
if (cardsn)
es_fputs (cardsn, fp);
else if (ctrl->with_secret)
es_putc ('+', fp);
}
xfree (cardsn);
xfree (p);
}
es_putc (':', fp); /* End of field 15. */
es_putc (':', fp); /* End of field 16. */
if (curve)
es_fputs (curve, fp);
es_putc (':', fp); /* End of field 17. */
print_compliance_flags (cert, algo, nbits, curve, fp);
es_putc (':', fp); /* End of field 18. */
es_putc ('\n', fp);
/* FPR record */
es_fprintf (fp, "fpr:::::::::%s:::", fpr);
/* Print chaining ID (field 13)*/
if (chain_id)
es_fputs (chain_id, fp);
es_putc (':', fp);
es_putc ('\n', fp);
xfree (fpr); fpr = NULL; chain_id = NULL;
xfree (chain_id_buffer); chain_id_buffer = NULL;
/* SHA256 FPR record */
fpr = gpgsm_get_fingerprint_hexstring (cert, GCRY_MD_SHA256);
es_fprintf (fp, "fp2:::::::::%s::::\n", fpr);
xfree (fpr); fpr = NULL;
/* Always print the keygrip. */
if ( (p = gpgsm_get_keygrip_hexstring (cert)))
{
es_fprintf (fp, "grp:::::::::%s:\n", p);
xfree (p);
}
if (opt.with_key_data)
print_key_data (cert, fp);
kludge_uid = NULL;
for (idx=0; (p = ksba_cert_get_subject (cert,idx)); idx++)
{
/* In the case that the same email address is in the subject DN
as well as in an alternate subject name we avoid printing it
a second time. */
if (kludge_uid && !strcmp (kludge_uid, p))
continue;
es_fprintf (fp, "uid:%s::::::::", truststring);
es_write_sanitized (fp, p, strlen (p), ":", NULL);
es_putc (':', fp);
es_putc (':', fp);
es_putc ('\n', fp);
if (!idx)
{
/* It would be better to get the faked email address from
the keydb. But as long as we don't have a way to pass
the meta data back, we just check it the same way as the
code used to create the keybox meta data does */
kludge_uid = email_kludge (p);
if (kludge_uid)
{
es_fprintf (fp, "uid:%s::::::::", truststring);
es_write_sanitized (fp, kludge_uid, strlen (kludge_uid),
":", NULL);
es_putc (':', fp);
es_putc (':', fp);
es_putc ('\n', fp);
}
}
xfree (p);
}
xfree (kludge_uid);
xfree (curve);
}
static void
print_name_raw (estream_t fp, const char *string)
{
if (!string)
es_fputs ("[error]", fp);
else
es_write_sanitized (fp, string, strlen (string), NULL, NULL);
}
static void
print_names_raw (estream_t fp, int indent, ksba_name_t name)
{
int idx;
const char *s;
int indent_all;
if ((indent_all = (indent < 0)))
indent = - indent;
if (!name)
{
es_fputs ("none\n", fp);
return;
}
for (idx=0; (s = ksba_name_enum (name, idx)); idx++)
{
char *p = ksba_name_get_uri (name, idx);
es_fprintf (fp, "%*s", idx||indent_all?indent:0, "");
es_write_sanitized (fp, p?p:s, strlen (p?p:s), NULL, NULL);
es_putc ('\n', fp);
xfree (p);
}
}
static void
print_utf8_extn_raw (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
gpg_error_t err;
int class, tag, constructed, ndef;
size_t objlen, hdrlen;
if (indent < 0)
indent = - indent;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
es_fprintf (fp, "%*s[%s]\n", indent, "", gpg_strerror (err));
return;
}
es_fprintf (fp, "%*s(%.*s)\n", indent, "", (int)objlen, der);
}
static void
print_utf8_extn (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
gpg_error_t err;
int class, tag, constructed, ndef;
size_t objlen, hdrlen;
int indent_all;
if ((indent_all = (indent < 0)))
indent = - indent;
err = parse_ber_header (&der, &derlen, &class, &tag, &constructed,
&ndef, &objlen, &hdrlen);
if (!err && (objlen > derlen || tag != TAG_UTF8_STRING))
err = gpg_error (GPG_ERR_INV_OBJ);
if (err)
{
es_fprintf (fp, "%*s[%s%s]\n",
indent_all? indent:0, "", _("Error - "), gpg_strerror (err));
return;
}
es_fprintf (fp, "%*s\"", indent_all? indent:0, "");
/* Fixme: we should implement word wrapping */
es_write_sanitized (fp, der, objlen, "\"", NULL);
es_fputs ("\"\n", fp);
}
/* Print the extension described by (DER,DERLEN) in hex. */
static void
print_hex_extn (estream_t fp, int indent,
const unsigned char *der, size_t derlen)
{
if (indent < 0)
indent = - indent;
es_fprintf (fp, "%*s(", indent, "");
for (; derlen; der++, derlen--)
es_fprintf (fp, "%02X%s", *der, derlen > 1? " ":"");
es_fprintf (fp, ")\n");
}
/* List one certificate in raw mode useful to have a closer look at
the certificate. This one does no beautification and only minimal
output sanitation. It is mainly useful for debugging. */
static void
list_cert_raw (ctrl_t ctrl, KEYDB_HANDLE hd,
ksba_cert_t cert, estream_t fp, int have_secret,
int with_validation)
{
gpg_error_t err;
size_t off, len;
ksba_sexp_t sexp, keyid;
char *dn;
ksba_isotime_t t;
int idx, i;
int is_ca, chainlen;
unsigned int kusage;
char *string, *p, *pend;
const char *oid, *s;
ksba_name_t name, name2;
unsigned int reason;
const unsigned char *cert_der = NULL;
char *algostr;
int algoid;
(void)have_secret;
es_fprintf (fp, " ID: 0x%08lX\n",
gpgsm_get_short_fingerprint (cert, NULL));
sexp = ksba_cert_get_serial (cert);
es_fputs (" S/N: ", fp);
gpgsm_print_serial (fp, sexp);
es_putc ('\n', fp);
es_fputs (" (dec): ", fp);
gpgsm_print_serial_decimal (fp, sexp);
es_putc ('\n', fp);
ksba_free (sexp);
dn = ksba_cert_get_issuer (cert, 0);
es_fputs (" Issuer: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = ksba_cert_get_subject (cert, 0);
es_fputs (" Subject: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
print_name_raw (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA256);
es_fprintf (fp, " sha2_fpr: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_fingerprint_string (cert, 0);
es_fprintf (fp, " sha1_fpr: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
es_fprintf (fp, " md5_fpr: %s\n", dn?dn:"error");
xfree (dn);
algoid = 0;
algostr = gpgsm_pubkey_algo_string (cert, &algoid);
/* For RSA we support printing an OpenPGP v4 fingerprint under the
* assumption that the not-before date would be used as the OpenPGP
* key creation date. */
if (algoid == GCRY_PK_RSA)
{
ksba_sexp_t pk;
size_t pklen;
const unsigned char *m, *e;
size_t mlen, elen;
unsigned char fpr20[20];
time_t tmpt;
unsigned long keytime;
pk = ksba_cert_get_public_key (cert);
if (pk)
{
ksba_cert_get_validity (cert, 0, t);
tmpt = isotime2epoch (t);
keytime = (tmpt == (time_t)(-1))? 0 : (u32)tmpt;
pklen = gcry_sexp_canon_len (pk, 0, NULL, NULL);
if (!pklen)
log_error ("libksba did not return a proper S-Exp\n");
else if (!get_rsa_pk_from_canon_sexp (pk, pklen,
&m, &mlen, &e, &elen)
&& !compute_openpgp_fpr_rsa (4,
keytime,
m, mlen, e, elen,
fpr20, NULL))
{
char *fpr = bin2hex (fpr20, 20, NULL);
es_fprintf (fp, " pgp_fpr: %s\n", fpr);
xfree (fpr);
}
ksba_free (pk);
}
}
dn = gpgsm_get_certid (cert);
es_fprintf (fp, " certid: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_keygrip_hexstring (cert);
es_fprintf (fp, " keygrip: %s\n", dn?dn:"error");
xfree (dn);
ksba_cert_get_validity (cert, 0, t);
es_fputs (" notBefore: ", fp);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
es_fputs (" notAfter: ", fp);
ksba_cert_get_validity (cert, 1, t);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
oid = ksba_cert_get_digest_algo (cert);
s = get_oid_desc (oid, 0, NULL);
es_fprintf (fp, " hashAlgo: %s%s%s%s\n", oid, s?" (":"",s?s:"",s?")":"");
es_fprintf (fp, " keyType: %s\n", algostr? algostr : "[error]");
/* subjectKeyIdentifier */
es_fputs (" subjKeyId: ", fp);
err = ksba_cert_get_subj_key_id (cert, NULL, &keyid);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA)
es_fputs ("[none]\n", fp);
else
{
gpgsm_print_serial (fp, keyid);
ksba_free (keyid);
es_putc ('\n', fp);
}
}
else
es_fputs ("[?]\n", fp);
/* authorityKeyIdentifier */
es_fputs (" authKeyId: ", fp);
err = ksba_cert_get_auth_key_id (cert, &keyid, &name, &sexp);
if (!err || gpg_err_code (err) == GPG_ERR_NO_DATA)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA || !name)
es_fputs ("[none]\n", fp);
else
{
gpgsm_print_serial (fp, sexp);
ksba_free (sexp);
es_putc ('\n', fp);
print_names_raw (fp, -15, name);
ksba_name_release (name);
}
if (keyid)
{
es_fputs (" authKeyId.ki: ", fp);
gpgsm_print_serial (fp, keyid);
ksba_free (keyid);
es_putc ('\n', fp);
}
}
else
es_fputs ("[?]\n", fp);
es_fputs (" keyUsage:", fp);
err = ksba_cert_get_key_usage (cert, &kusage);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, " [error: %s]", gpg_strerror (err));
else
{
if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
es_fputs (" digitalSignature", fp);
if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
es_fputs (" nonRepudiation", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
es_fputs (" keyEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
es_fputs (" dataEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
es_fputs (" keyAgreement", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_fputs (" certSign", fp);
if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
es_fputs (" crlSign", fp);
if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
es_fputs (" encipherOnly", fp);
if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
es_fputs (" decipherOnly", fp);
}
es_putc ('\n', fp);
}
else
es_fputs (" [none]\n", fp);
es_fputs (" extKeyUsage: ", fp);
err = ksba_cert_get_ext_key_usages (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
s = get_oid_desc (p, OID_FLAG_KP, NULL);
es_fputs (s ? s : p, fp);
p = pend;
if (*p != 'C')
es_fputs (" (suggested)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs ("\n ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
else
es_fputs ("[none]\n", fp);
es_fputs (" policies: ", fp);
err = ksba_cert_get_cert_policies (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
s = get_oid_desc (p, OID_FLAG_KP, NULL);
es_fputs (s?s:p, fp);
p = pend;
if (*p == 'C')
es_fputs (" (critical)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs ("\n ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
else
es_fputs ("[none]\n", fp);
es_fputs (" chainLength: ", fp);
err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
if (err || is_ca)
{
if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
es_fprintf (fp, "[none]");
else if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else if (chainlen == -1)
es_fputs ("unlimited", fp);
else
es_fprintf (fp, "%d", chainlen);
es_putc ('\n', fp);
}
else
es_fputs ("not a CA\n", fp);
/* CRL distribution point */
for (idx=0; !(err=ksba_cert_get_crl_dist_point (cert, idx, &name, &name2,
&reason)) ;idx++)
{
es_fputs (" crlDP: ", fp);
print_names_raw (fp, 15, name);
if (reason)
{
es_fputs (" reason: ", fp);
if ( (reason & KSBA_CRLREASON_UNSPECIFIED))
es_fputs (" unused", fp);
if ( (reason & KSBA_CRLREASON_KEY_COMPROMISE))
es_fputs (" keyCompromise", fp);
if ( (reason & KSBA_CRLREASON_CA_COMPROMISE))
es_fputs (" caCompromise", fp);
if ( (reason & KSBA_CRLREASON_AFFILIATION_CHANGED))
es_fputs (" affiliationChanged", fp);
if ( (reason & KSBA_CRLREASON_SUPERSEDED))
es_fputs (" superseded", fp);
if ( (reason & KSBA_CRLREASON_CESSATION_OF_OPERATION))
es_fputs (" cessationOfOperation", fp);
if ( (reason & KSBA_CRLREASON_CERTIFICATE_HOLD))
es_fputs (" certificateHold", fp);
es_putc ('\n', fp);
}
es_fputs (" issuer: ", fp);
print_names_raw (fp, 23, name2);
ksba_name_release (name);
ksba_name_release (name2);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" crlDP: [error]\n", fp);
else if (!idx)
es_fputs (" crlDP: [none]\n", fp);
/* authorityInfoAccess. */
for (idx=0; !(err=ksba_cert_get_authority_info_access (cert, idx, &string,
&name)); idx++)
{
es_fputs (" authInfo: ", fp);
s = get_oid_desc (string, 0, NULL);
es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
print_names_raw (fp, -15, name);
ksba_name_release (name);
ksba_free (string);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" authInfo: [error]\n", fp);
else if (!idx)
es_fputs (" authInfo: [none]\n", fp);
/* subjectInfoAccess. */
for (idx=0; !(err=ksba_cert_get_subject_info_access (cert, idx, &string,
&name)); idx++)
{
es_fputs (" subjectInfo: ", fp);
s = get_oid_desc (string, 0, NULL);
es_fprintf (fp, "%s%s%s%s\n", string, s?" (":"", s?s:"", s?")":"");
print_names_raw (fp, -15, name);
ksba_name_release (name);
ksba_free (string);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF
&& gpg_err_code (err) != GPG_ERR_NO_VALUE)
es_fputs (" subjInfo: [error]\n", fp);
else if (!idx)
es_fputs (" subjInfo: [none]\n", fp);
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, &i, &off, &len));idx++)
{
unsigned int flag;
s = get_oid_desc (oid, 0, &flag);
if ((flag & OID_FLAG_SKIP))
continue;
es_fprintf (fp, " %s: %s%s%s%s",
i? "critExtn":" extn",
oid, s?" (":"", s?s:"", s?")":"");
if ((flag & OID_FLAG_UTF8))
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
log_assert (cert_der);
es_fprintf (fp, "\n");
print_utf8_extn_raw (fp, -15, cert_der+off, len);
}
else if ((flag & OID_FLAG_HEX))
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
log_assert (cert_der);
es_fprintf (fp, "\n");
print_hex_extn (fp, -15, cert_der+off, len);
}
else
es_fprintf (fp, " [%d octets]\n", (int)len);
}
if (with_validation)
{
err = gpgsm_validate_chain (ctrl, cert,
GNUPG_ISOTIME_NONE, NULL, 1, fp, 0, NULL);
if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED
&& !check_isotime (ctrl->revoked_at))
{
es_fputs (" revoked: ", fp);
gpgsm_print_time (fp, ctrl->revoked_at);
if (ctrl->revocation_reason)
es_fprintf (fp, " (%s)", ctrl->revocation_reason);
es_putc ('\n', fp);
}
if (!err)
es_fprintf (fp, " [certificate is good]\n");
else
es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err));
}
if (hd)
{
unsigned int blobflags;
err = keydb_get_flags (hd, KEYBOX_FLAG_BLOB, 0, &blobflags);
if (err)
es_fprintf (fp, " [error getting keyflags: %s]\n",gpg_strerror (err));
else if ((blobflags & KEYBOX_FLAG_BLOB_EPHEMERAL))
es_fprintf (fp, " [stored as ephemeral]\n");
}
xfree (algostr);
}
/* List one certificate in standard mode */
static void
list_cert_std (ctrl_t ctrl, ksba_cert_t cert, estream_t fp, int have_secret,
int with_validation)
{
gpg_error_t err;
ksba_sexp_t sexp;
char *dn;
ksba_isotime_t t;
int idx;
int is_ca, chainlen;
unsigned int kusage;
char *string, *p, *pend;
size_t off, len;
const char *oid, *s;
const unsigned char *cert_der = NULL;
es_fprintf (fp, " ID: 0x%08lX\n",
gpgsm_get_short_fingerprint (cert, NULL));
sexp = ksba_cert_get_serial (cert);
es_fputs (" S/N: ", fp);
gpgsm_print_serial (fp, sexp);
es_putc ('\n', fp);
es_fputs (" (dec): ", fp);
gpgsm_print_serial_decimal (fp, sexp);
es_putc ('\n', fp);
ksba_free (sexp);
dn = ksba_cert_get_issuer (cert, 0);
es_fputs (" Issuer: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_issuer (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
dn = ksba_cert_get_subject (cert, 0);
es_fputs (" Subject: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
for (idx=1; (dn = ksba_cert_get_subject (cert, idx)); idx++)
{
es_fputs (" aka: ", fp);
gpgsm_es_print_name (fp, dn);
ksba_free (dn);
es_putc ('\n', fp);
}
ksba_cert_get_validity (cert, 0, t);
es_fputs (" validity: ", fp);
gpgsm_print_time (fp, t);
es_fputs (" through ", fp);
ksba_cert_get_validity (cert, 1, t);
gpgsm_print_time (fp, t);
es_putc ('\n', fp);
{
char *algostr;
algostr = gpgsm_pubkey_algo_string (cert, NULL);
es_fprintf (fp, " key type: %s\n", algostr? algostr : "[error]");
xfree (algostr);
}
err = ksba_cert_get_key_usage (cert, &kusage);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs (" key usage:", fp);
if (err)
es_fprintf (fp, " [error: %s]", gpg_strerror (err));
else
{
if ( (kusage & KSBA_KEYUSAGE_DIGITAL_SIGNATURE))
es_fputs (" digitalSignature", fp);
if ( (kusage & KSBA_KEYUSAGE_NON_REPUDIATION))
es_fputs (" nonRepudiation", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_ENCIPHERMENT))
es_fputs (" keyEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_DATA_ENCIPHERMENT))
es_fputs (" dataEncipherment", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_AGREEMENT))
es_fputs (" keyAgreement", fp);
if ( (kusage & KSBA_KEYUSAGE_KEY_CERT_SIGN))
es_fputs (" certSign", fp);
if ( (kusage & KSBA_KEYUSAGE_CRL_SIGN))
es_fputs (" crlSign", fp);
if ( (kusage & KSBA_KEYUSAGE_ENCIPHER_ONLY))
es_fputs (" encipherOnly", fp);
if ( (kusage & KSBA_KEYUSAGE_DECIPHER_ONLY))
es_fputs (" decipherOnly", fp);
}
es_putc ('\n', fp);
}
err = ksba_cert_get_ext_key_usages (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs ("ext key usage: ", fp);
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
p = string;
while (p && (pend=strchr (p, ':')))
{
*pend++ = 0;
s = get_oid_desc (p, OID_FLAG_KP, NULL);
es_fputs (s? s : p, fp);
p = pend;
if (*p != 'C')
es_fputs (" (suggested)", fp);
if ((p = strchr (p, '\n')))
{
p++;
es_fputs (", ", fp);
}
}
xfree (string);
}
es_putc ('\n', fp);
}
/* Print restrictions. */
for (idx=0; !(err=ksba_cert_get_extension (cert, idx,
&oid, NULL, &off, &len));idx++)
{
if (!strcmp (oid, OIDSTR_restriction) )
{
if (!cert_der)
cert_der = ksba_cert_get_image (cert, NULL);
log_assert (cert_der);
es_fputs (" restriction: ", fp);
print_utf8_extn (fp, 15, cert_der+off, len);
}
}
/* Print policies. */
err = ksba_cert_get_cert_policies (cert, &string);
if (gpg_err_code (err) != GPG_ERR_NO_DATA)
{
es_fputs (" policies: ", fp);
if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else
{
for (p=string; *p; p++)
{
if (*p == '\n')
*p = ',';
}
es_write_sanitized (fp, string, strlen (string), NULL, NULL);
xfree (string);
}
es_putc ('\n', fp);
}
err = ksba_cert_is_ca (cert, &is_ca, &chainlen);
if (err || is_ca)
{
es_fputs (" chain length: ", fp);
if (gpg_err_code (err) == GPG_ERR_NO_VALUE )
es_fprintf (fp, "none");
else if (err)
es_fprintf (fp, "[error: %s]", gpg_strerror (err));
else if (chainlen == -1)
es_fputs ("unlimited", fp);
else
es_fprintf (fp, "%d", chainlen);
es_putc ('\n', fp);
}
if (opt.with_md5_fingerprint)
{
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_MD5);
es_fprintf (fp, " md5 fpr: %s\n", dn?dn:"error");
xfree (dn);
}
dn = gpgsm_get_fingerprint_string (cert, 0);
es_fprintf (fp, " sha1 fpr: %s\n", dn?dn:"error");
xfree (dn);
dn = gpgsm_get_fingerprint_string (cert, GCRY_MD_SHA256);
es_fprintf (fp, " sha2 fpr: %s\n", dn?dn:"error");
xfree (dn);
if (opt.with_keygrip)
{
dn = gpgsm_get_keygrip_hexstring (cert);
if (dn)
{
es_fprintf (fp, " keygrip: %s\n", dn);
xfree (dn);
}
}
if (opt.with_key_screening)
print_pk_screening (cert, 0, fp);
if (have_secret)
{
char *cardsn;
p = gpgsm_get_keygrip_hexstring (cert);
if (!gpgsm_agent_keyinfo (ctrl, p, &cardsn) && cardsn)
es_fprintf (fp, " card s/n: %s\n", cardsn);
xfree (cardsn);
xfree (p);
}
if (with_validation)
{
gpg_error_t tmperr;
size_t buflen;
char buffer[1];
err = gpgsm_validate_chain (ctrl, cert,
GNUPG_ISOTIME_NONE, NULL, 1, fp, 0, NULL);
if (gpg_err_code (err) == GPG_ERR_CERT_REVOKED
&& !check_isotime (ctrl->revoked_at))
{
es_fputs (" revoked: ", fp);
gpgsm_print_time (fp, ctrl->revoked_at);
if (ctrl->revocation_reason)
es_fprintf (fp, " (%s)", ctrl->revocation_reason);
es_putc ('\n', fp);
}
tmperr = ksba_cert_get_user_data (cert, "is_qualified",
&buffer, sizeof (buffer), &buflen);
if (!tmperr && buflen)
{
if (*buffer)
es_fputs (" [qualified]\n", fp);
}
else if (gpg_err_code (tmperr) == GPG_ERR_NOT_FOUND)
; /* Don't know - will not get marked as 'q' */
else
log_debug ("get_user_data(is_qualified) failed: %s\n",
gpg_strerror (tmperr));
if (!err)
es_fprintf (fp, " [certificate is good]\n");
else
es_fprintf (fp, " [certificate is bad: %s]\n", gpg_strerror (err));
}
if (opt.debug)
es_fflush (fp);
}
/* Same as standard mode list all certifying certs too. */
static void
list_cert_chain (ctrl_t ctrl, KEYDB_HANDLE hd,
ksba_cert_t cert, int raw_mode,
estream_t fp, int with_validation)
{
ksba_cert_t next = NULL;
int depth = 0;
if (raw_mode)
list_cert_raw (ctrl, hd, cert, fp, 0, with_validation);
else
list_cert_std (ctrl, cert, fp, 0, with_validation);
ksba_cert_ref (cert);
while (!gpgsm_walk_cert_chain (ctrl, cert, &next))
{
es_fputs ("Certified by\n", fp);
if (++depth > 50)
{
es_fputs (_("certificate chain too long\n"), fp);
break;
}
ksba_cert_release (cert);
if (raw_mode)
list_cert_raw (ctrl, hd, next, fp, 0, with_validation);
else
list_cert_std (ctrl, next, fp, 0, with_validation);
cert = next;
}
ksba_cert_release (cert);
es_putc ('\n', fp);
}
/* List all internal keys or just the keys given as NAMES. MODE is a
bit vector to specify what keys are to be included; see
gpgsm_list_keys (below) for details. If RAW_MODE is true, the raw
output mode will be used instead of the standard beautified one.
*/
static gpg_error_t
list_internal_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
unsigned int mode, int raw_mode)
{
KEYDB_HANDLE hd;
KEYDB_SEARCH_DESC *desc = NULL;
strlist_t sl;
int ndesc;
ksba_cert_t cert = NULL;
ksba_cert_t lastcert = NULL;
gpg_error_t rc = 0;
const char *lastresname, *resname;
int have_secret;
int want_ephemeral = ctrl->with_ephemeral_keys;
hd = keydb_new (ctrl);
if (!hd)
{
log_error ("keydb_new failed\n");
rc = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
if (!names)
ndesc = 1;
else
{
for (sl=names, ndesc=0; sl; sl = sl->next, ndesc++)
;
}
desc = xtrycalloc (ndesc, sizeof *desc);
if (!ndesc)
{
rc = gpg_error_from_syserror ();
log_error ("out of core\n");
goto leave;
}
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_FIRST;
else
{
for (ndesc=0, sl=names; sl; sl = sl->next)
{
rc = classify_user_id (sl->d, desc+ndesc, 0);
if (rc)
{
log_error ("key '%s' not found: %s\n",
sl->d, gpg_strerror (rc));
rc = 0;
}
else
ndesc++;
}
}
/* If all specifications are done by fingerprint or keygrip, we
switch to ephemeral mode so that _all_ currently available and
matching certificates are listed. */
if (!want_ephemeral && names && ndesc)
{
int i;
for (i=0; (i < ndesc
&& (desc[i].mode == KEYDB_SEARCH_MODE_FPR
|| desc[i].mode == KEYDB_SEARCH_MODE_KEYGRIP)); i++)
;
if (i == ndesc)
want_ephemeral = 1;
}
if (want_ephemeral)
keydb_set_ephemeral (hd, 1);
/* It would be nice to see which of the given users did actually
match one in the keyring. To implement this we need to have a
found flag for each entry in desc and to set this we must check
all those entries after a match to mark all matched one -
currently we stop at the first match. To do this we need an
extra flag to enable this feature so */
/* Suppress duplicates at least when they follow each other. */
lastresname = NULL;
while (!(rc = keydb_search (ctrl, hd, desc, ndesc)))
{
unsigned int validity;
if (!names)
desc[0].mode = KEYDB_SEARCH_MODE_NEXT;
rc = keydb_get_flags (hd, KEYBOX_FLAG_VALIDITY, 0, &validity);
if (rc)
{
log_error ("keydb_get_flags failed: %s\n", gpg_strerror (rc));
goto leave;
}
rc = keydb_get_cert (hd, &cert);
if (rc)
{
log_error ("keydb_get_cert failed: %s\n", gpg_strerror (rc));
goto leave;
}
/* Skip duplicated certificates, at least if they follow each
others. This works best if a single key is searched for and
expected. FIXME: Non-sequential duplicates remain. */
if (gpgsm_certs_identical_p (cert, lastcert))
{
ksba_cert_release (cert);
cert = NULL;
continue;
}
resname = keydb_get_resource_name (hd);
if (lastresname != resname )
{
int i;
if (ctrl->no_server)
{
es_fprintf (fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
es_putc ('-', fp);
es_putc ('\n', fp);
lastresname = resname;
}
}
have_secret = 0;
if (mode)
{
char *p = gpgsm_get_keygrip_hexstring (cert);
if (p)
{
rc = gpgsm_agent_havekey (ctrl, p);
if (!rc)
have_secret = 1;
else if ( gpg_err_code (rc) != GPG_ERR_NO_SECKEY)
goto leave;
rc = 0;
xfree (p);
}
}
if (!mode || ((mode & 1) && !have_secret)
|| ((mode & 2) && have_secret) )
{
if (ctrl->with_colons)
list_cert_colon (ctrl, cert, validity, fp, have_secret);
else if (ctrl->with_chain)
list_cert_chain (ctrl, hd, cert,
raw_mode, fp, ctrl->with_validation);
else
{
if (raw_mode)
list_cert_raw (ctrl, hd, cert, fp, have_secret,
ctrl->with_validation);
else
list_cert_std (ctrl, cert, fp, have_secret,
ctrl->with_validation);
es_putc ('\n', fp);
}
}
ksba_cert_release (lastcert);
lastcert = cert;
cert = NULL;
}
if (gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = 0;
if (rc)
log_error ("keydb_search failed: %s\n", gpg_strerror (rc));
leave:
ksba_cert_release (cert);
ksba_cert_release (lastcert);
xfree (desc);
keydb_release (hd);
return rc;
}
static void
list_external_cb (void *cb_value, ksba_cert_t cert)
{
struct list_external_parm_s *parm = cb_value;
if (keydb_store_cert (parm->ctrl, cert, 1, NULL))
log_error ("error storing certificate as ephemeral\n");
if (parm->print_header)
{
const char *resname = "[external keys]";
int i;
es_fprintf (parm->fp, "%s\n", resname );
for (i=strlen(resname); i; i-- )
es_putc('-', parm->fp);
es_putc ('\n', parm->fp);
parm->print_header = 0;
}
if (parm->with_colons)
list_cert_colon (parm->ctrl, cert, 0, parm->fp, 0);
else if (parm->with_chain)
list_cert_chain (parm->ctrl, NULL, cert, parm->raw_mode, parm->fp, 0);
else
{
if (parm->raw_mode)
list_cert_raw (parm->ctrl, NULL, cert, parm->fp, 0, 0);
else
list_cert_std (parm->ctrl, cert, parm->fp, 0, 0);
es_putc ('\n', parm->fp);
}
}
/* List external keys similar to internal one. Note: mode does not
make sense here because it would be unwise to list external secret
keys */
static gpg_error_t
list_external_keys (ctrl_t ctrl, strlist_t names, estream_t fp, int raw_mode)
{
int rc;
struct list_external_parm_s parm;
parm.fp = fp;
parm.ctrl = ctrl,
parm.print_header = ctrl->no_server;
parm.with_colons = ctrl->with_colons;
parm.with_chain = ctrl->with_chain;
parm.raw_mode = raw_mode;
rc = gpgsm_dirmngr_lookup (ctrl, names, NULL, 0, list_external_cb, &parm);
if (gpg_err_code (rc) == GPG_ERR_EOF || rc == -1
|| gpg_err_code (rc) == GPG_ERR_NOT_FOUND)
rc = 0; /* "Not found" is not an error here. */
if (rc)
log_error ("listing external keys failed: %s\n", gpg_strerror (rc));
return rc;
}
/* List all keys or just the key given as NAMES.
MODE controls the operation mode:
Bit 0-2:
0 = list all public keys but don't flag secret ones
1 = list only public keys
2 = list only secret keys
3 = list secret and public keys
Bit 6: list internal keys
Bit 7: list external keys
Bit 8: Do a raw format dump.
*/
gpg_error_t
gpgsm_list_keys (ctrl_t ctrl, strlist_t names, estream_t fp,
unsigned int mode)
{
gpg_error_t err = 0;
if ((mode & (1<<6)))
err = list_internal_keys (ctrl, names, fp, (mode & 3), (mode&256));
if (!err && (mode & (1<<7)))
err = list_external_keys (ctrl, names, fp, (mode&256));
return err;
}
static gpg_error_t
do_show_certs (ctrl_t ctrl, const char *fname, estream_t outfp)
{
gpg_error_t err;
gnupg_ksba_io_t b64reader = NULL;
ksba_reader_t reader;
ksba_cert_t cert = NULL;
estream_t fp;
int any = 0;
if (!fname || (fname[0] == '-' && !fname[1]))
{
fp = es_stdin;
fname = "[stdin]";
}
else
{
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
}
err = gnupg_ksba_create_reader
(&b64reader, ((ctrl->is_pem? GNUPG_KSBA_IO_PEM : 0)
| (ctrl->is_base64? GNUPG_KSBA_IO_BASE64 : 0)
| (ctrl->autodetect_encoding? GNUPG_KSBA_IO_AUTODETECT : 0)
| GNUPG_KSBA_IO_MULTIPEM),
fp, &reader);
if (err)
{
log_error ("can't create reader: %s\n", gpg_strerror (err));
goto leave;
}
/* We need to loop here to handle multiple PEM objects per file. */
do
{
ksba_cert_release (cert); cert = NULL;
err = ksba_cert_new (&cert);
if (err)
goto leave;
err = ksba_cert_read_der (cert, reader);
if (err)
goto leave;
es_fprintf (outfp, "File ........: %s\n", fname);
list_cert_raw (ctrl, NULL, cert, outfp, 0, 0);
es_putc ('\n', outfp);
any = 1;
ksba_reader_clear (reader, NULL, NULL);
}
while (!gnupg_ksba_reader_eof_seen (b64reader));
leave:
if (any && gpg_err_code (err) == GPG_ERR_EOF)
err = 0;
ksba_cert_release (cert);
gnupg_ksba_destroy_reader (b64reader);
if (fp != es_stdin)
es_fclose (fp);
return err;
}
/* Show a raw dump of the certificates found in the files given in
* the arrag FILES. Write output to FP. */
gpg_error_t
gpgsm_show_certs (ctrl_t ctrl, int nfiles, char **files, estream_t fp)
{
gpg_error_t saveerr = 0;
gpg_error_t err;
if (!nfiles)
saveerr = do_show_certs (ctrl, NULL, fp);
else
{
for (; nfiles; nfiles--, files++)
{
err = do_show_certs (ctrl, *files, fp);
if (err && !saveerr)
saveerr = err;
}
}
return saveerr;
}
diff --git a/sm/minip12.c b/sm/minip12.c
index 4a1fab050..c95240b23 100644
--- a/sm/minip12.c
+++ b/sm/minip12.c
@@ -1,3141 +1,3141 @@
/* minip12.c - A minimal pkcs-12 implementation.
* Copyright (C) 2002, 2003, 2004, 2006, 2011 Free Software Foundation, Inc.
* Copyright (C) 2014 Werner Koch
* Copyright (C) 2022, 2023 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/* References:
* RFC-7292 - PKCS #12: Personal Information Exchange Syntax v1.1
* RFC-8351 - The PKCS #8 EncryptedPrivateKeyInfo Media Type
* RFC-5958 - Asymmetric Key Packages
* RFC-3447 - PKCS #1: RSA Cryptography Specifications Version 2.1
* RFC-5915 - Elliptic Curve Private Key Structure
*/
#ifdef HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <gcrypt.h>
#include <errno.h>
#include <ksba.h>
#include "../common/util.h"
#include "../common/logging.h"
#include "../common/utf8conv.h"
#include "../common/tlv.h"
#include "../common/openpgpdefs.h" /* Only for openpgp_curve_to_oid. */
#include "minip12.h"
#ifndef DIM
#define DIM(v) (sizeof(v)/sizeof((v)[0]))
#endif
/* Enable the next macro to dump stuff for debugging. */
#undef ENABLE_DER_STRUCT_DUMPING
static unsigned char const oid_data[9] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x01 };
static unsigned char const oid_encryptedData[9] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x07, 0x06 };
static unsigned char const oid_pkcs_12_keyBag[11] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x01 };
static unsigned char const oid_pkcs_12_pkcs_8ShroudedKeyBag[11] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x02 };
static unsigned char const oid_pkcs_12_CertBag[11] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x03 };
static unsigned char const oid_pkcs_12_CrlBag[11] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x0A, 0x01, 0x04 };
static unsigned char const oid_pbeWithSHAAnd3_KeyTripleDES_CBC[10] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03 };
static unsigned char const oid_pbeWithSHAAnd40BitRC2_CBC[10] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06 };
static unsigned char const oid_x509Certificate_for_pkcs_12[10] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x16, 0x01 };
static unsigned char const oid_pkcs5PBKDF2[9] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0C };
static unsigned char const oid_pkcs5PBES2[9] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x0D };
static unsigned char const oid_aes128_CBC[9] = {
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x02 };
static unsigned char const oid_aes256_CBC[9] = {
0x60, 0x86, 0x48, 0x01, 0x65, 0x03, 0x04, 0x01, 0x2A };
static unsigned char const oid_hmacWithSHA1[8] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x07 };
static unsigned char const oid_hmacWithSHA224[8] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x08 };
static unsigned char const oid_hmacWithSHA256[8] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x09 };
static unsigned char const oid_hmacWithSHA384[8] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x0A };
static unsigned char const oid_hmacWithSHA512[8] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x0B };
static unsigned char const oid_rsaEncryption[9] = {
0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 };
static unsigned char const oid_pcPublicKey[7] = {
0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 };
static unsigned char const data_3desiter2048[30] = {
0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86,
0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x03, 0x30, 0x0E,
0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 };
#define DATA_3DESITER2048_SALT_OFF 18
static unsigned char const data_rc2iter2048[30] = {
0x30, 0x1C, 0x06, 0x0A, 0x2A, 0x86, 0x48, 0x86,
0xF7, 0x0D, 0x01, 0x0C, 0x01, 0x06, 0x30, 0x0E,
0x04, 0x08, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
0xFF, 0xFF, 0x02, 0x02, 0x08, 0x00 };
#define DATA_RC2ITER2048_SALT_OFF 18
static unsigned char const data_mactemplate[51] = {
0x30, 0x31, 0x30, 0x21, 0x30, 0x09, 0x06, 0x05,
0x2b, 0x0e, 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04,
0x14, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0x04, 0x08, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x02,
0x02, 0x08, 0x00 };
#define DATA_MACTEMPLATE_MAC_OFF 17
#define DATA_MACTEMPLATE_SALT_OFF 39
/* Note that the BMP String in this template reads:
* "GnuPG exported certificate ffffffff" */
static unsigned char const data_attrtemplate[106] = {
0x31, 0x7c, 0x30, 0x55, 0x06, 0x09, 0x2a, 0x86,
0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x14, 0x31,
0x48, 0x1e, 0x46, 0x00, 0x47, 0x00, 0x6e, 0x00,
0x75, 0x00, 0x50, 0x00, 0x47, 0x00, 0x20, 0x00,
0x65, 0x00, 0x78, 0x00, 0x70, 0x00, 0x6f, 0x00,
0x72, 0x00, 0x74, 0x00, 0x65, 0x00, 0x64, 0x00,
0x20, 0x00, 0x63, 0x00, 0x65, 0x00, 0x72, 0x00,
0x74, 0x00, 0x69, 0x00, 0x66, 0x00, 0x69, 0x00,
0x63, 0x00, 0x61, 0x00, 0x74, 0x00, 0x65, 0x00,
0x20, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
0x66, 0x00, 0x66, 0x00, 0x66, 0x00, 0x66, 0x00,
0x66, 0x30, 0x23, 0x06, 0x09, 0x2a, 0x86, 0x48,
0x86, 0xf7, 0x0d, 0x01, 0x09, 0x15, 0x31, 0x16,
0x04, 0x14 }; /* Need to append SHA-1 digest. */
#define DATA_ATTRTEMPLATE_KEYID_OFF 73
struct buffer_s
{
unsigned char *buffer;
size_t length;
};
/* Parser communication object. */
struct p12_parse_ctx_s
{
/* The callback for parsed certificates and its arg. */
void (*certcb)(void*, const unsigned char*, size_t);
void *certcbarg;
/* The supplied parseword. */
const char *password;
/* Set to true if the password was wrong. */
int badpass;
/* Malloced name of the curve. */
char *curve;
/* The private key as an MPI array. */
gcry_mpi_t *privatekey;
/* A second private key as an MPI array. */
gcry_mpi_t *privatekey2;
};
static int opt_verbose;
void
p12_set_verbosity (int verbose, int debug)
{
opt_verbose = !!verbose;
if (debug)
opt_verbose = 2;
}
static int
digest_algo_from_oid (unsigned char const *oid, size_t oidlen)
{
int algo;
if (oidlen == DIM(oid_hmacWithSHA1) &&
!memcmp (oid, oid_hmacWithSHA1, oidlen))
algo = GCRY_MD_SHA1;
else if (oidlen == DIM(oid_hmacWithSHA224) &&
!memcmp (oid, oid_hmacWithSHA224, oidlen))
algo = GCRY_MD_SHA224;
else if (oidlen == DIM(oid_hmacWithSHA256) &&
!memcmp (oid, oid_hmacWithSHA256, oidlen))
algo = GCRY_MD_SHA256;
else if (oidlen == DIM(oid_hmacWithSHA384) &&
!memcmp (oid, oid_hmacWithSHA384, oidlen))
algo = GCRY_MD_SHA384;
else if (oidlen == DIM(oid_hmacWithSHA512) &&
!memcmp (oid, oid_hmacWithSHA512, oidlen))
algo = GCRY_MD_SHA512;
else
algo = 0;
return algo;
}
/* Wrapper around tlv_builder_add_ptr to add an OID. When we
* eventually put the whole tlv_builder stuff into Libksba, we can add
* such a function there. Right now we don't do this to avoid a
* dependency on Libksba. Function return 1 on error. */
static int
builder_add_oid (tlv_builder_t tb, int class, const char *oid)
{
gpg_error_t err;
unsigned char *der;
size_t derlen;
err = ksba_oid_from_str (oid, &der, &derlen);
if (err)
{
log_error ("%s: error converting '%s' to DER: %s\n",
__func__, oid, gpg_strerror (err));
return 1;
}
tlv_builder_add_val (tb, class, TAG_OBJECT_ID, der, derlen);
ksba_free (der);
return 0;
}
/* Wrapper around tlv_builder_add_ptr to add an MPI. TAG may either
* be OCTET_STRING or BIT_STRING. When we eventually put the whole
* tlv_builder stuff into Libksba, we can add such a function there.
* Right now we don't do this to avoid a dependency on Libksba.
* Function return 1 on error. STRIP is a hack to remove the first
* octet from the value. */
static int
builder_add_mpi (tlv_builder_t tb, int class, int tag, gcry_mpi_t mpi,
int strip)
{
int returncode;
gpg_error_t err;
const unsigned char *s;
unsigned char *freethis = NULL;
unsigned char *freethis2 = NULL;
unsigned int nbits;
size_t n;
if (gcry_mpi_get_flag (mpi, GCRYMPI_FLAG_OPAQUE))
{
s = gcry_mpi_get_opaque (mpi, &nbits);
n = (nbits+7)/8;
}
else
{
err = gcry_mpi_aprint (GCRYMPI_FMT_USG, &freethis, &n, mpi);
if (err)
{
log_error ("%s: error converting MPI: %s\n",
__func__, gpg_strerror (err));
returncode = 1;
goto leave;
}
s = freethis;
}
if (tag == TAG_BIT_STRING)
{
freethis2 = xtrymalloc_secure (n + 1);
if (!freethis2)
{
err = gpg_error_from_syserror ();
log_error ("%s: error converting MPI: %s\n",
__func__, gpg_strerror (err));
returncode = 1;
goto leave;
}
freethis2[0] = 0;
memcpy (freethis2+1, s, n);
s = freethis2;
n++;
}
strip = !!strip;
if (strip && n < 2)
strip = 0;
tlv_builder_add_val (tb, class, tag, s+strip, n-strip);
returncode = 0;
leave:
xfree (freethis);
xfree (freethis2);
return returncode;
}
static int
string_to_key (int id, char *salt, size_t saltlen, int iter, const char *pw,
int req_keylen, unsigned char *keybuf)
{
int rc, i, j;
gcry_md_hd_t md;
gcry_mpi_t num_b1 = NULL;
int pwlen;
unsigned char hash[20], buf_b[64], buf_i[128], *p;
size_t cur_keylen;
size_t n;
cur_keylen = 0;
pwlen = strlen (pw);
if (pwlen > 63/2)
{
log_error ("password too long\n");
return -1;
}
if (saltlen < 8)
{
log_error ("salt too short\n");
return -1;
}
/* Store salt and password in BUF_I */
p = buf_i;
for(i=0; i < 64; i++)
*p++ = salt [i%saltlen];
for(i=j=0; i < 64; i += 2)
{
*p++ = 0;
*p++ = pw[j];
if (++j > pwlen) /* Note, that we include the trailing zero */
j = 0;
}
for (;;)
{
rc = gcry_md_open (&md, GCRY_MD_SHA1, 0);
if (rc)
{
log_error ( "gcry_md_open failed: %s\n", gpg_strerror (rc));
return rc;
}
for(i=0; i < 64; i++)
gcry_md_putc (md, id);
gcry_md_write (md, buf_i, 128);
memcpy (hash, gcry_md_read (md, 0), 20);
gcry_md_close (md);
for (i=1; i < iter; i++)
gcry_md_hash_buffer (GCRY_MD_SHA1, hash, hash, 20);
for (i=0; i < 20 && cur_keylen < req_keylen; i++)
keybuf[cur_keylen++] = hash[i];
if (cur_keylen == req_keylen)
{
gcry_mpi_release (num_b1);
return 0; /* ready */
}
/* need more bytes. */
for(i=0; i < 64; i++)
buf_b[i] = hash[i % 20];
rc = gcry_mpi_scan (&num_b1, GCRYMPI_FMT_USG, buf_b, 64, &n);
if (rc)
{
log_error ( "gcry_mpi_scan failed: %s\n", gpg_strerror (rc));
return -1;
}
gcry_mpi_add_ui (num_b1, num_b1, 1);
for (i=0; i < 128; i += 64)
{
gcry_mpi_t num_ij;
rc = gcry_mpi_scan (&num_ij, GCRYMPI_FMT_USG, buf_i + i, 64, &n);
if (rc)
{
log_error ( "gcry_mpi_scan failed: %s\n",
gpg_strerror (rc));
return -1;
}
gcry_mpi_add (num_ij, num_ij, num_b1);
gcry_mpi_clear_highbit (num_ij, 64*8);
rc = gcry_mpi_print (GCRYMPI_FMT_USG, buf_i + i, 64, &n, num_ij);
if (rc)
{
log_error ( "gcry_mpi_print failed: %s\n",
gpg_strerror (rc));
return -1;
}
gcry_mpi_release (num_ij);
}
}
}
static int
set_key_iv (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter,
const char *pw, int keybytes)
{
unsigned char keybuf[24];
int rc;
log_assert (keybytes == 5 || keybytes == 24);
if (string_to_key (1, salt, saltlen, iter, pw, keybytes, keybuf))
return -1;
rc = gcry_cipher_setkey (chd, keybuf, keybytes);
if (rc)
{
log_error ( "gcry_cipher_setkey failed: %s\n", gpg_strerror (rc));
return -1;
}
if (string_to_key (2, salt, saltlen, iter, pw, 8, keybuf))
return -1;
rc = gcry_cipher_setiv (chd, keybuf, 8);
if (rc)
{
log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc));
return -1;
}
return 0;
}
static int
set_key_iv_pbes2 (gcry_cipher_hd_t chd, char *salt, size_t saltlen, int iter,
const void *iv, size_t ivlen, const char *pw,
int cipher_algo, int digest_algo)
{
unsigned char *keybuf;
size_t keylen;
int rc;
keylen = gcry_cipher_get_algo_keylen (cipher_algo);
if (!keylen)
return -1;
keybuf = gcry_malloc_secure (keylen);
if (!keybuf)
return -1;
rc = gcry_kdf_derive (pw, strlen (pw),
GCRY_KDF_PBKDF2, digest_algo,
salt, saltlen, iter, keylen, keybuf);
if (rc)
{
log_error ("gcry_kdf_derive failed: %s\n", gpg_strerror (rc));
gcry_free (keybuf);
return -1;
}
rc = gcry_cipher_setkey (chd, keybuf, keylen);
gcry_free (keybuf);
if (rc)
{
log_error ("gcry_cipher_setkey failed: %s\n", gpg_strerror (rc));
return -1;
}
rc = gcry_cipher_setiv (chd, iv, ivlen);
if (rc)
{
log_error ("gcry_cipher_setiv failed: %s\n", gpg_strerror (rc));
return -1;
}
return 0;
}
static void
crypt_block (unsigned char *buffer, size_t length, char *salt, size_t saltlen,
int iter, const void *iv, size_t ivlen,
const char *pw, int cipher_algo, int digest_algo, int encrypt)
{
gcry_cipher_hd_t chd;
int rc;
rc = gcry_cipher_open (&chd, cipher_algo, GCRY_CIPHER_MODE_CBC, 0);
if (rc)
{
log_error ( "gcry_cipher_open failed: %s\n", gpg_strerror(rc));
wipememory (buffer, length);
return;
}
if ((cipher_algo == GCRY_CIPHER_AES128 || cipher_algo == GCRY_CIPHER_AES256)
? set_key_iv_pbes2 (chd, salt, saltlen, iter, iv, ivlen, pw,
cipher_algo, digest_algo)
: set_key_iv (chd, salt, saltlen, iter, pw,
cipher_algo == GCRY_CIPHER_RFC2268_40? 5:24))
{
wipememory (buffer, length);
goto leave;
}
rc = encrypt? gcry_cipher_encrypt (chd, buffer, length, NULL, 0)
: gcry_cipher_decrypt (chd, buffer, length, NULL, 0);
if (rc)
{
wipememory (buffer, length);
log_error ("%scrytion failed (%zu bytes): %s\n",
encrypt?"en":"de", length, gpg_strerror (rc));
goto leave;
}
leave:
gcry_cipher_close (chd);
}
/* Decrypt a block of data and try several encodings of the key.
CIPHERTEXT is the encrypted data of size LENGTH bytes; PLAINTEXT is
a buffer of the same size to receive the decryption result. SALT,
SALTLEN, ITER and PW are the information required for decryption
and CIPHER_ALGO is the algorithm id to use. CHECK_FNC is a
function called with the plaintext and used to check whether the
decryption succeeded; i.e. that a correct passphrase has been
given. The function returns the length of the unpadded plaintext
or 0 on error. */
static size_t
decrypt_block (const void *ciphertext, unsigned char *plaintext, size_t length,
char *salt, size_t saltlen,
int iter, const void *iv, size_t ivlen,
const char *pw, int cipher_algo, int digest_algo,
int (*check_fnc) (const void *, size_t))
{
static const char * const charsets[] = {
"", /* No conversion - use the UTF-8 passphrase direct. */
"ISO-8859-1",
"ISO-8859-15",
"ISO-8859-2",
"ISO-8859-3",
"ISO-8859-4",
"ISO-8859-5",
"ISO-8859-6",
"ISO-8859-7",
"ISO-8859-8",
"ISO-8859-9",
"KOI8-R",
"IBM437",
"IBM850",
"EUC-JP",
"BIG5",
NULL
};
int charsetidx = 0;
char *convertedpw = NULL; /* Malloced and converted password or NULL. */
size_t convertedpwsize = 0; /* Allocated length. */
size_t plainlen = 0;
for (charsetidx=0; charsets[charsetidx]; charsetidx++)
{
if (*charsets[charsetidx])
{
jnlib_iconv_t cd;
const char *inptr;
char *outptr;
size_t inbytes, outbytes;
if (!convertedpw)
{
/* We assume one byte encodings. Thus we can allocate
the buffer of the same size as the original
passphrase; the result will actually be shorter
then. */
convertedpwsize = strlen (pw) + 1;
convertedpw = gcry_malloc_secure (convertedpwsize);
if (!convertedpw)
{
log_info ("out of secure memory while"
" converting passphrase\n");
break; /* Give up. */
}
}
cd = jnlib_iconv_open (charsets[charsetidx], "utf-8");
if (cd == (jnlib_iconv_t)(-1))
continue;
inptr = pw;
inbytes = strlen (pw);
outptr = convertedpw;
outbytes = convertedpwsize - 1;
if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
&outptr, &outbytes) == (size_t)-1)
{
jnlib_iconv_close (cd);
continue;
}
*outptr = 0;
jnlib_iconv_close (cd);
log_info ("decryption failed; trying charset '%s'\n",
charsets[charsetidx]);
}
memcpy (plaintext, ciphertext, length);
crypt_block (plaintext, length, salt, saltlen, iter, iv, ivlen,
convertedpw? convertedpw:pw, cipher_algo, digest_algo, 0);
if (check_fnc (plaintext, length))
{
/* Strip the pkcs#7 padding. */
if (length)
{
int n, i;
n = plaintext[length-1];
if (n >= length || n > 16)
log_info ("decryption failed; invalid padding size\n");
else
{
for (i=1; i < n; i++)
if (plaintext[length-i-1] != n)
break;
if (i < n)
log_info ("decryption failed; invalid padding octet\n");
else
plainlen = length - n;
}
}
break; /* Decryption probably succeeded. */
}
}
gcry_free (convertedpw);
return plainlen;
}
/* Return true if the decryption of an bag_encrypted_data object has
likely succeeded. */
static int
bag_decrypted_data_p (const void *plaintext, size_t length)
{
struct tag_info ti;
const unsigned char *p = plaintext;
size_t n = length;
#ifdef ENABLE_DER_STRUCT_DUMPING
{
# warning debug code is enabled
FILE *fp = fopen ("tmp-minip12-plain-data.der", "wb");
if (!fp || fwrite (p, n, 1, fp) != 1)
exit (2);
fclose (fp);
}
#endif /*ENABLE_DER_STRUCT_DUMPING*/
if (tlv_parse_tag (&p, &n, &ti))
return 0;
if (ti.class || ti.tag != TAG_SEQUENCE)
return 0;
if (tlv_parse_tag (&p, &n, &ti))
return 0;
return 1;
}
static int
parse_bag_encrypted_data (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv)
{
gpg_error_t err = 0;
const char *where;
const unsigned char *oid;
size_t oidlen;
const unsigned char *data;
size_t datalen;
int intval;
char salt[32];
size_t saltlen;
char iv[16];
unsigned int iter;
unsigned char *plain = NULL;
int is_3des = 0;
int is_pbes2 = 0;
int is_aes256 = 0;
int keyelem_count;
int renewed_tlv = 0;
int loopcount;
unsigned int startlevel, startlevel2;
int digest_algo = GCRY_MD_SHA1;
where = "bag.encryptedData";
if (opt_verbose)
log_info ("processing %s\n", where);
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_context_tag (tlv, &intval) || intval != 0 )
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
where = "bag.encryptedData.version";
if (tlv_next (tlv))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)))
goto bailout;
if (intval)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
where = "bag.encryptedData.data";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen != DIM(oid_data) || memcmp (oid, oid_data, DIM(oid_data)))
goto bailout;
where = "bag.encryptedData.keyinfo";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen == DIM(oid_pbeWithSHAAnd40BitRC2_CBC)
&& !memcmp (oid, oid_pbeWithSHAAnd40BitRC2_CBC,
DIM(oid_pbeWithSHAAnd40BitRC2_CBC)))
;
else if (oidlen == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
&& !memcmp (oid, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
is_3des = 1;
else if (oidlen == DIM(oid_pkcs5PBES2)
&& !memcmp (oid, oid_pkcs5PBES2, oidlen))
is_pbes2 = 1;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
goto bailout;
}
/*FIXME: This code is duplicated in parse_shrouded_key_bag. */
if (is_pbes2)
{
size_t parmlen; /* Remaining length of the parameter sequence. */
where = "pkcs5PBES2-params";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen != DIM(oid_pkcs5PBKDF2)
|| memcmp (oid, oid_pkcs5PBKDF2, oidlen))
{
err = gpg_error (GPG_ERR_INV_BER); /* Not PBKDF2. */
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
parmlen = tlv_parser_tag_length (tlv, 0);
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
parmlen -= tlv_parser_tag_length (tlv, 1);
if (datalen < 8 || datalen > sizeof salt)
{
log_info ("bad length of salt (%zu)\n", datalen);
err = gpg_error (GPG_ERR_INV_LENGTH);
goto bailout;
}
saltlen = datalen;
memcpy (salt, data, saltlen);
if (tlv_next (tlv))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)))
goto bailout;
parmlen -= tlv_parser_tag_length (tlv, 1);
if (!intval) /* Not a valid iteration count. */
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
iter = intval;
if (parmlen > 2) /* There is the optional prf. */
{
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
digest_algo = digest_algo_from_oid (oid, oidlen);
if (!digest_algo)
{
gpgrt_log_printhex (oid, oidlen, "kdf digest algo:");
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto bailout;
}
if (opt_verbose > 1)
log_debug ("kdf digest algo = %d\n", digest_algo);
if (tlv_peek_null (tlv))
{
/* Read the optional Null tag. */
if (tlv_next (tlv))
goto bailout;
}
}
else
digest_algo = GCRY_MD_SHA1;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen == DIM(oid_aes128_CBC)
&& !memcmp (oid, oid_aes128_CBC, oidlen))
;
else if (oidlen == DIM(oid_aes256_CBC)
&& !memcmp (oid, oid_aes256_CBC, oidlen))
is_aes256 = 1;
else
{
gpgrt_log_printhex (oid, oidlen, "cipher algo:");
err = gpg_error (GPG_ERR_CIPHER_ALGO);
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
if (datalen != sizeof iv)
{
err = gpg_error (GPG_ERR_INV_LENGTH);
goto bailout; /* Bad IV. */
}
memcpy (iv, data, datalen);
}
else
{
where = "rc2or3des-params";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
if (datalen < 8 || datalen > 20)
{
log_info ("bad length of salt (%zu) for 3DES\n", datalen);
err = gpg_error (GPG_ERR_INV_LENGTH);
goto bailout;
}
saltlen = datalen;
memcpy (salt, data, saltlen);
if (tlv_next (tlv))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)))
goto bailout;
if (!intval)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
iter = intval;
}
where = "rc2or3desoraes-ciphertext";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object (tlv, CLASS_CONTEXT, 0, &data, &datalen))
goto bailout;
if (opt_verbose)
log_info ("%zu bytes of %s encrypted text\n", datalen,
is_pbes2?(is_aes256?"AES256":"AES128"):is_3des?"3DES":"RC2");
plain = gcry_malloc_secure (datalen);
if (!plain)
{
err = gpg_error_from_syserror ();
log_error ("error allocating decryption buffer\n");
goto bailout;
}
datalen = decrypt_block (data, plain, datalen, salt, saltlen, iter,
iv, is_pbes2?16:0, ctx->password,
is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128) :
is_3des ? GCRY_CIPHER_3DES : GCRY_CIPHER_RFC2268_40,
digest_algo,
bag_decrypted_data_p);
if (!datalen)
{
err = gpg_error (GPG_ERR_DECRYPT_FAILED);
- ctx->badpass = 1; /* This is the most likley reason. */
+ ctx->badpass = 1; /* This is the most likely reason. */
goto bailout;
}
/* We do not need the TLV anymore and allocated a new one. */
where = "bag.encryptedData.decrypted-text";
tlv = tlv_parser_new (plain, datalen, opt_verbose);
if (!tlv)
{
err = gpg_error_from_syserror ();
goto bailout;
}
renewed_tlv = 1;
if (tlv_next (tlv))
{
ctx->badpass = 1;
goto bailout;
}
if (tlv_expect_sequence (tlv))
{
ctx->badpass = 1;
goto bailout;
}
/* Loop over all certificates inside the bag. */
loopcount = 0;
startlevel = tlv_parser_level (tlv);
while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel)
{
int iscrlbag = 0;
int iskeybag = 0;
loopcount++;
where = "certbag.nextcert";
if (tlv_expect_sequence (tlv))
goto bailout;
where = "certbag.oid";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen == DIM(oid_pkcs_12_CertBag)
&& !memcmp (oid, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag)))
;
else if (oidlen == DIM(oid_pkcs_12_CrlBag)
&& !memcmp (oid, oid_pkcs_12_CrlBag, DIM(oid_pkcs_12_CrlBag)))
iscrlbag = 1;
else if (oidlen == DIM(oid_pkcs_12_keyBag)
&& !memcmp (oid, oid_pkcs_12_keyBag, DIM(oid_pkcs_12_keyBag)))
{
/* The TrustedMIME plugin for MS Outlook started to create
files with just one outer 3DES encrypted container and
inside the certificates as well as the key. */
iskeybag = 1;
}
else
{
gpgrt_log_printhex (oid, oidlen, "cert bag type OID:");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto bailout;
}
where = "certbag.before.certheader";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_context_tag (tlv, &intval) || intval != 0 )
goto bailout;
if (iscrlbag)
{
log_info ("skipping unsupported crlBag\n");
}
else if (iskeybag && ctx->privatekey)
{
log_info ("one keyBag already processed; skipping this one\n");
}
else if (iskeybag)
{
if (opt_verbose)
log_info ("processing simple keyBag\n");
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)))
goto bailout;
if (intval)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen != DIM(oid_rsaEncryption)
|| memcmp (oid, oid_rsaEncryption, oidlen))
{
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
goto bailout;
}
/* We ignore the next octet string. */
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (ctx->privatekey)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
log_error ("a private key has already been received\n");
goto bailout;
}
ctx->privatekey = gcry_calloc (10, sizeof *ctx->privatekey);
if (!ctx->privatekey)
{
err = gpg_error_from_syserror ();
log_error ("error allocating private key element array\n");
goto bailout;
}
where = "reading.keybag.key-parameters";
keyelem_count = 0;
startlevel2 = tlv_parser_level (tlv);
while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel2)
{
if (keyelem_count >= 9)
{
err = gpg_error (GPG_ERR_TOO_MANY);
goto bailout;
}
err = tlv_expect_mpinteger (tlv, !keyelem_count,
ctx->privatekey+keyelem_count);
if (!keyelem_count && gpg_err_code (err) == GPG_ERR_FALSE)
; /* Ignore the first value iff it is zero. */
else if (err)
{
log_error ("error parsing RSA key parameter %d: %s\n",
keyelem_count, gpg_strerror (err));
goto bailout;
}
if (opt_verbose > 1)
log_debug ("RSA key parameter %d found\n", keyelem_count);
keyelem_count++;
}
if (!err)
tlv_parser_set_pending (tlv);
else if (err && gpg_err_code (err) != GPG_ERR_EOF)
goto bailout;
err = 0;
}
else
{
if (opt_verbose)
log_info ("processing certBag\n");
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen != DIM(oid_x509Certificate_for_pkcs_12)
|| memcmp (oid, oid_x509Certificate_for_pkcs_12,
DIM(oid_x509Certificate_for_pkcs_12)))
{
err = gpg_error (GPG_ERR_UNSUPPORTED_CERT);
goto bailout;
}
where = "certbag.before.octetstring";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_context_tag (tlv, &intval))
goto bailout;
if (intval)
{
err = gpg_error (GPG_ERR_BAD_BER);
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
/* Return the certificate. */
if (ctx->certcb)
ctx->certcb (ctx->certcbarg, data, datalen);
}
/* Skip the optional SET with the pkcs12 cert attributes. */
where = "bag.attribute_set";
if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_SET))
{
if (tlv_next (tlv))
goto bailout;
err = tlv_expect_set (tlv);
if (err)
goto bailout;
tlv_parser_skip (tlv);
if (opt_verbose)
log_info ("skipping %s\n", where);
}
}
if (!err)
tlv_parser_set_pending (tlv);
else if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
if (!loopcount) /* The first while(tlv_next) failed. */
ctx->badpass = 1;
goto bailout;
}
err = 0;
leave:
if (renewed_tlv)
tlv_parser_release (tlv);
gcry_free (plain);
if (ctx->badpass)
{
/* Note, that the following string might be used by other programs
to check for a bad passphrase; it should therefore not be
translated or changed. */
log_error ("possibly bad passphrase given\n");
}
return err;
bailout:
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
log_error ("%s(%s): lvl=%u (%s): %s - %s\n",
__func__, where,
tlv_parser_level (tlv),
tlv_parser_lastfunc (tlv),
tlv_parser_lasterrstr (tlv),
gpg_strerror (err));
goto leave;
}
/* Return true if the decryption of a bag_data object has likely
succeeded. */
static int
bag_data_p (const void *plaintext, size_t length)
{
struct tag_info ti;
const unsigned char *p = plaintext;
size_t n = length;
#ifdef ENABLE_DER_STRUCT_DUMPING
{
# warning debug code is enabled
FILE *fp = fopen ("tmp-minip12-plain-key.der", "wb");
if (!fp || fwrite (p, n, 1, fp) != 1)
exit (2);
fclose (fp);
}
#endif /*ENABLE_DER_STRUCT_DUMPING*/
if (tlv_parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_SEQUENCE)
return 0;
if (tlv_parse_tag (&p, &n, &ti) || ti.class || ti.tag != TAG_INTEGER
|| ti.length != 1 || *p)
return 0;
return 1;
}
static gpg_error_t
parse_shrouded_key_bag (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv)
{
gpg_error_t err = 0;
const char *where;
const unsigned char *oid;
size_t oidlen;
const unsigned char *data;
size_t datalen;
int intval;
char salt[20];
size_t saltlen;
char iv[16];
unsigned int iter;
tlv_parser_t saved_tlv = NULL;
int renewed_tlv = 0; /* True if the TLV must be released. */
unsigned char *plain = NULL;
int is_pbes2 = 0;
int is_aes256 = 0;
int digest_algo = GCRY_MD_SHA1;
gcry_mpi_t *privatekey;
where = "shrouded_key_bag";
if (opt_verbose)
log_info ("processing %s\n", where);
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_context_tag (tlv, &intval) || intval != 0 )
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
where = "shrouded_key_bag.cipherinfo";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen == DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)
&& !memcmp (oid, oid_pbeWithSHAAnd3_KeyTripleDES_CBC,
DIM(oid_pbeWithSHAAnd3_KeyTripleDES_CBC)))
; /* Standard cipher. */
else if (oidlen == DIM(oid_pkcs5PBES2)
&& !memcmp (oid, oid_pkcs5PBES2, DIM(oid_pkcs5PBES2)))
is_pbes2 = 1;
else
{
err = gpg_error (GPG_ERR_UNKNOWN_ALGORITHM);
goto bailout;
}
if (is_pbes2)
{
size_t parmlen; /* Remaining length of the parameter sequence. */
where = "shrouded_key_bag.pkcs5PBES2-params";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (!(oidlen == DIM(oid_pkcs5PBKDF2)
&& !memcmp (oid, oid_pkcs5PBKDF2, oidlen)))
goto bailout; /* Not PBKDF2. */
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
parmlen = tlv_parser_tag_length (tlv, 0);
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
parmlen -= tlv_parser_tag_length (tlv, 1);
if (datalen < 8 || datalen > sizeof salt)
{
log_info ("bad length of salt (%zu) for AES\n", datalen);
err = gpg_error (GPG_ERR_INV_LENGTH);
goto bailout;
}
saltlen = datalen;
memcpy (salt, data, saltlen);
if (tlv_next (tlv))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)))
goto bailout;
parmlen -= tlv_parser_tag_length (tlv, 1);
if (!intval) /* Not a valid iteration count. */
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
iter = intval;
if (parmlen > 2) /* There is the optional prf. */
{
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
digest_algo = digest_algo_from_oid (oid, oidlen);
if (!digest_algo)
{
gpgrt_log_printhex (oid, oidlen, "kdf digest algo:");
err = gpg_error (GPG_ERR_DIGEST_ALGO);
goto bailout;
}
if (opt_verbose > 1)
log_debug ("kdf digest algo = %d\n", digest_algo);
if (tlv_peek_null (tlv))
{
/* Read the optional Null tag. */
if (tlv_next (tlv))
goto bailout;
}
}
else
digest_algo = GCRY_MD_SHA1;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen == DIM(oid_aes128_CBC)
&& !memcmp (oid, oid_aes128_CBC, oidlen))
;
else if (oidlen == DIM(oid_aes256_CBC)
&& !memcmp (oid, oid_aes256_CBC, oidlen))
is_aes256 = 1;
else
{
gpgrt_log_printhex (oid, oidlen, "cipher is:");
err = gpg_error (GPG_ERR_CIPHER_ALGO);
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
if (datalen != sizeof iv)
goto bailout; /* Bad IV. */
memcpy (iv, data, datalen);
}
else
{
where = "shrouded_key_bag.3des-params";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
if (datalen < 8 || datalen > 20)
{
log_info ("bad length of salt (%zu) for 3DES\n", datalen);
err = gpg_error (GPG_ERR_INV_LENGTH);
goto bailout;
}
saltlen = datalen;
memcpy (salt, data, saltlen);
if (tlv_next (tlv))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)))
goto bailout;
if (!intval)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
iter = intval;
}
where = "shrouded_key_bag.3desoraes-ciphertext";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
if (opt_verbose)
log_info ("%zu bytes of %s encrypted text\n",
datalen, is_pbes2? (is_aes256?"AES256":"AES128"):"3DES");
plain = gcry_malloc_secure (datalen);
if (!plain)
{
err = gpg_error_from_syserror ();
log_error ("error allocating decryption buffer\n");
goto bailout;
}
datalen = decrypt_block (data, plain, datalen, salt, saltlen, iter,
iv, is_pbes2? 16:0, ctx->password,
is_pbes2 ? (is_aes256?GCRY_CIPHER_AES256:GCRY_CIPHER_AES128)
: GCRY_CIPHER_3DES,
digest_algo,
bag_data_p);
if (!datalen)
{
err = gpg_error (GPG_ERR_DECRYPT_FAILED);
ctx->badpass = 1;
goto bailout;
}
/* We do not need the TLV anymore and allocated a new one. */
where = "shrouded_key_bag.decrypted-text";
saved_tlv = tlv;
tlv = tlv_parser_new (plain, datalen, opt_verbose);
if (!tlv)
{
err = gpg_error_from_syserror ();
goto bailout;
}
renewed_tlv = 1;
if (opt_verbose > 1)
log_debug ("new parser context\n");
if (tlv_next (tlv))
{
ctx->badpass = 1;
goto bailout;
}
if (tlv_expect_sequence (tlv))
{
ctx->badpass = 1;
goto bailout;
}
if (tlv_next (tlv))
{
ctx->badpass = 1;
goto bailout;
}
if ((err = tlv_expect_integer (tlv, &intval)))
{
ctx->badpass = 1;
goto bailout;
}
if (intval)
{
ctx->badpass = 1;
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen == DIM(oid_rsaEncryption)
&& !memcmp (oid, oid_rsaEncryption, oidlen))
{
if (opt_verbose > 1)
log_debug ("RSA parameters\n");
if (tlv_peek_null (tlv))
{
/* Read the optional Null tag. */
if (tlv_next (tlv))
goto bailout;
}
}
else if (oidlen == DIM(oid_pcPublicKey)
&& !memcmp (oid, oid_pcPublicKey, oidlen))
{
/* See RFC-5915 for the format. */
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
ksba_free (ctx->curve);
ctx->curve = ksba_oid_to_str (oid, oidlen);
if (!ctx->curve)
{
err = gpg_error (GPG_ERR_INV_OID_STRING);
goto bailout;
}
if (opt_verbose > 1)
log_debug ("OID of curve is: %s\n", ctx->curve);
}
else /* Unknown key format */
{
gpgrt_log_printhex (oid, oidlen, "key format OID:");
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
goto bailout;
}
/* An octet string to encapsulate the key elements. */
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 1, &data, &datalen))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (ctx->privatekey2)
{
err = gpg_error (GPG_ERR_DUP_VALUE);
log_error ("two private kesy have already been received\n");
goto bailout;
}
privatekey = gcry_calloc (10, sizeof *privatekey);
if (!privatekey)
{
err = gpg_error_from_syserror ();
log_error ("error allocating privatekey element array\n");
goto bailout;
}
if (ctx->privatekey)
{
log_info ("a private key has already been received - reading second\n");
ctx->privatekey2 = privatekey;
}
else
ctx->privatekey = privatekey;
where = "shrouded_key_bag.reading.key-parameters";
if (ctx->curve) /* ECC case. */
{
if (tlv_next (tlv))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)))
goto bailout;
if (intval != 1)
{
err = gpg_error (GPG_ERR_INV_VALUE);
log_error ("error parsing private ecPublicKey parameter: %s\n",
"bad version");
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
if (opt_verbose > 1)
log_printhex (data, datalen, "ecc q=");
err = gcry_mpi_scan (privatekey, GCRYMPI_FMT_USG,
data, datalen, NULL);
if (err)
{
log_error ("error parsing key parameter: %s\n", gpg_strerror (err));
goto bailout;
}
}
else /* RSA case */
{
int keyelem_count = 0;
int firstparam = 1;
unsigned int startlevel = tlv_parser_level (tlv);
while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel)
{
if (keyelem_count >= 9)
{
err = gpg_error (GPG_ERR_TOO_MANY);
goto bailout;
}
err = tlv_expect_mpinteger (tlv, firstparam,
privatekey+keyelem_count);
if (firstparam && gpg_err_code (err) == GPG_ERR_FALSE)
; /* Ignore the first value iff it is zero. */
else if (err)
{
log_error ("error parsing RSA key parameter %d: %s\n",
keyelem_count, gpg_strerror (err));
goto bailout;
}
else
{
if (opt_verbose > 1)
log_debug ("RSA key parameter %d found\n", keyelem_count);
keyelem_count++;
}
firstparam = 0;
}
if (!err)
tlv_parser_set_pending (tlv);
else if (err && gpg_err_code (err) != GPG_ERR_EOF)
goto bailout;
err = 0;
}
if (opt_verbose > 1)
log_debug ("restoring parser context\n");
tlv_parser_release (tlv);
renewed_tlv = 0;
tlv = saved_tlv;
where = "shrouded_key_bag.attribute_set";
/* Check for an optional set of attributes. */
if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_SET))
{
if (tlv_next (tlv))
goto bailout;
err = tlv_expect_set (tlv);
if (err)
goto bailout;
tlv_parser_skip (tlv);
if (opt_verbose)
log_info ("skipping %s\n", where);
}
leave:
gcry_free (plain);
if (renewed_tlv)
{
tlv_parser_release (tlv);
if (opt_verbose > 1)
log_debug ("parser context released\n");
}
return err;
bailout:
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
log_error ("%s(%s): lvl=%u (%s): %s - %s\n",
__func__, where,
tlv_parser_level (tlv),
tlv_parser_lastfunc (tlv),
tlv_parser_lasterrstr (tlv),
gpg_strerror (err));
goto leave;
}
static gpg_error_t
parse_cert_bag (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv)
{
gpg_error_t err = 0;
const char *where;
int intval;
const unsigned char *oid;
size_t oidlen;
const unsigned char *data;
size_t datalen;
if (opt_verbose)
log_info ("processing certBag\n");
/* Expect:
* [0]
* SEQUENCE
* OBJECT IDENTIFIER pkcs-12-certBag
*/
where = "certbag.before.certheader";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_context_tag (tlv, &intval))
goto bailout;
if (intval)
{
err = gpg_error (GPG_ERR_INV_VALUE);
goto bailout;
}
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
if (oidlen != DIM(oid_x509Certificate_for_pkcs_12)
|| memcmp (oid, oid_x509Certificate_for_pkcs_12, oidlen))
goto bailout;
/* Expect:
* [0]
* OCTET STRING encapsulates -- the certificates
*/
where = "certbag.before.octetstring";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_context_tag (tlv, &intval) || intval != 0 )
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 0, &data, &datalen))
goto bailout;
/* Return the certificate from the octet string. */
if (ctx->certcb)
ctx->certcb (ctx->certcbarg, data, datalen);
/* Expect optional:
* SET
* SEQUENCE -- we actually ignore this.
*/
where = "certbag.attribute_set";
/* Check for an optional set of attributes. */
if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_SET))
{
if (tlv_next (tlv))
goto bailout;
err = tlv_expect_set (tlv);
if (err)
goto bailout;
tlv_parser_skip (tlv);
if (opt_verbose)
log_info ("skipping %s\n", where);
}
leave:
return err;
bailout:
log_error ("%s(%s): lvl=%u (%s): %s - %s\n",
__func__, where,
tlv_parser_level (tlv),
tlv_parser_lastfunc (tlv),
tlv_parser_lasterrstr (tlv),
gpg_strerror (err));
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
goto leave;
}
static gpg_error_t
parse_bag_data (struct p12_parse_ctx_s *ctx, tlv_parser_t tlv)
{
gpg_error_t err = 0;
const char *where;
int intval;
const unsigned char *oid;
size_t oidlen;
unsigned int startlevel;
if (opt_verbose)
log_info ("processing bag data\n");
/* Expect:
* [0]
* OCTET STRING, encapsulates
*/
where = "data";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_context_tag (tlv, &intval) || intval != 0 )
goto bailout;
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_octet_string (tlv, 1, NULL, NULL))
goto bailout;
if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_OCTET_STRING))
{
if (tlv_next (tlv))
goto bailout;
err = tlv_expect_octet_string (tlv, 1, NULL, NULL);
if (err)
goto bailout;
}
/* Expect:
* SEQUENCE
*/
where = "data.outerseqs";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_sequence (tlv))
goto bailout;
startlevel = tlv_parser_level (tlv);
tlv_parser_dump_state ("data.outerseqs", "beginloop", tlv);
while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel)
{
/* Expect:
* SEQUENCE
*/
where = "data.innerseqs";
if (tlv_expect_sequence (tlv))
goto bailout;
/* Expect:
* OBJECT IDENTIFIER
*/
where = "data.oid";
if (tlv_next (tlv))
goto bailout;
if (tlv_expect_object_id (tlv, &oid, &oidlen))
goto bailout;
/* Divert to the actual parser. */
if (oidlen == DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)
&& !memcmp (oid, oid_pkcs_12_pkcs_8ShroudedKeyBag,
DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)))
{
if ((err = parse_shrouded_key_bag (ctx, tlv)))
goto bailout;
}
else if (oidlen == DIM(oid_pkcs_12_CertBag)
&& !memcmp (oid, oid_pkcs_12_CertBag, DIM(oid_pkcs_12_CertBag)))
{
if ((err = parse_cert_bag (ctx, tlv)))
goto bailout;
}
else
{
tlv_parser_skip (tlv);
log_info ("unknown inner data type - skipped\n");
}
}
tlv_parser_dump_state ("data.outerseqs", "endloop", tlv);
if (!err)
tlv_parser_set_pending (tlv);
else if (err && gpg_err_code (err) != GPG_ERR_EOF)
goto bailout;
err = 0;
leave:
return err;
bailout:
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
log_error ("%s(%s): lvl=%u (%s): %s - %s\n",
__func__, where,
tlv_parser_level (tlv),
tlv_parser_lastfunc (tlv),
tlv_parser_lasterrstr (tlv),
gpg_strerror (err));
goto leave;
}
/* Parse a PKCS12 object and return an array of MPI representing the
secret key parameters. This is a very limited implementation in
that it is only able to look for 3DES encoded encryptedData and
tries to extract the first private key object it finds. In case of
an error NULL is returned. CERTCB and CERTCBARG are used to pass
X.509 certificates back to the caller. If R_CURVE is not NULL and
an ECC key was found the OID of the curve is stored there. */
gcry_mpi_t *
p12_parse (const unsigned char *buffer, size_t length, const char *pw,
void (*certcb)(void*, const unsigned char*, size_t),
void *certcbarg, int *r_badpass, char **r_curve)
{
gpg_error_t err;
const char *where = "";
tlv_parser_t tlv;
struct p12_parse_ctx_s ctx = { NULL };
const unsigned char *oid;
size_t oidlen;
int intval;
unsigned int startlevel;
int i;
*r_badpass = 0;
ctx.certcb = certcb;
ctx.certcbarg = certcbarg;
ctx.password = pw;
tlv = tlv_parser_new (buffer, length, opt_verbose);
if (!tlv)
{
err = gpg_error_from_syserror ();
goto bailout;
}
where = "pfx";
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_sequence (tlv)))
goto bailout;
where = "pfxVersion";
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_integer (tlv, &intval)) || intval != 3)
goto bailout;
where = "authSave";
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_sequence (tlv)))
goto bailout;
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_object_id (tlv, &oid, &oidlen)))
goto bailout;
if (oidlen != DIM(oid_data) || memcmp (oid, oid_data, DIM(oid_data)))
{
err = gpg_error (GPG_ERR_INV_OBJ);
goto bailout;
}
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_context_tag (tlv, &intval)) || intval != 0 )
goto bailout;
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_octet_string (tlv, 1, NULL, NULL)))
goto bailout;
if (tlv_peek (tlv, CLASS_UNIVERSAL, TAG_OCTET_STRING))
{
if ((err = tlv_next (tlv)))
goto bailout;
err = tlv_expect_octet_string (tlv, 1, NULL, NULL);
if (err)
goto bailout;
}
where = "bags";
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_sequence (tlv)))
goto bailout;
startlevel = tlv_parser_level (tlv);
tlv_parser_dump_state ("bags", "beginloop", tlv);
while (!(err = tlv_next (tlv)) && tlv_parser_level (tlv) == startlevel)
{
where = "bag-sequence";
tlv_parser_dump_state (where, NULL, tlv);
if ((err = tlv_expect_sequence (tlv)))
goto bailout;
if ((err = tlv_next (tlv)))
goto bailout;
if ((err = tlv_expect_object_id (tlv, &oid, &oidlen)))
goto bailout;
if (oidlen == DIM(oid_encryptedData)
&& !memcmp (oid, oid_encryptedData, DIM(oid_encryptedData)))
{
where = "bag.encryptedData";
if ((err=parse_bag_encrypted_data (&ctx, tlv)))
goto bailout;
}
else if (oidlen == DIM(oid_data)
&& !memcmp (oid, oid_data, DIM(oid_data)))
{
where = "bag.data";
if ((err=parse_bag_data (&ctx, tlv)))
goto bailout;
}
else if (oidlen == DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)
&& !memcmp (oid, oid_pkcs_12_pkcs_8ShroudedKeyBag,
DIM(oid_pkcs_12_pkcs_8ShroudedKeyBag)))
{
where = "bag.shroudedkeybag";
if ((err = parse_shrouded_key_bag (&ctx, tlv)))
goto bailout;
}
else
{
tlv_parser_skip (tlv);
log_info ("unknown outer bag type - skipped\n");
}
}
tlv_parser_dump_state ("bags", "endloop", tlv);
if (!err)
tlv_parser_set_pending (tlv);
else if (err && gpg_err_code (err) != GPG_ERR_EOF)
goto bailout;
err = 0;
tlv_parser_release (tlv);
if (r_curve)
*r_curve = ctx.curve;
else
gcry_free (ctx.curve);
/* We have no way yet to return the second private key. */
if (ctx.privatekey2)
{
for (i=0; ctx.privatekey2[i]; i++)
gcry_mpi_release (ctx.privatekey2[i]);
gcry_free (ctx.privatekey2);
ctx.privatekey2 = NULL;
}
return ctx.privatekey;
bailout:
*r_badpass = ctx.badpass;
log_error ("%s(%s): @%04zu lvl=%u %s: %s - %s\n",
__func__, where,
tlv_parser_offset (tlv),
tlv_parser_level (tlv),
tlv_parser_lastfunc (tlv),
tlv_parser_lasterrstr (tlv),
gpg_strerror (err));
if (ctx.privatekey)
{
for (i=0; ctx.privatekey[i]; i++)
gcry_mpi_release (ctx.privatekey[i]);
gcry_free (ctx.privatekey);
ctx.privatekey = NULL;
}
if (ctx.privatekey2)
{
for (i=0; ctx.privatekey2[i]; i++)
gcry_mpi_release (ctx.privatekey2[i]);
gcry_free (ctx.privatekey2);
ctx.privatekey2 = NULL;
}
tlv_parser_release (tlv);
gcry_free (ctx.curve);
if (r_curve)
*r_curve = NULL;
return NULL;
}
/* Free the parameters as returned by p12_parse. */
void
p12_parse_free_kparms (gcry_mpi_t *kparms)
{
int i;
if (kparms)
{
for (i=0; i < 8; i++)
gcry_mpi_release (kparms[i]);
gcry_free (kparms);
}
}
static size_t
compute_tag_length (size_t n)
{
int needed = 0;
if (n < 128)
needed += 2; /* tag and one length byte */
else if (n < 256)
needed += 3; /* tag, number of length bytes, 1 length byte */
else if (n < 65536)
needed += 4; /* tag, number of length bytes, 2 length bytes */
else
{
log_error ("object too larger to encode\n");
return 0;
}
return needed;
}
static unsigned char *
store_tag_length (unsigned char *p, int tag, size_t n)
{
if (tag == TAG_SEQUENCE)
tag |= 0x20; /* constructed */
*p++ = tag;
if (n < 128)
*p++ = n;
else if (n < 256)
{
*p++ = 0x81;
*p++ = n;
}
else if (n < 65536)
{
*p++ = 0x82;
*p++ = n >> 8;
*p++ = n;
}
return p;
}
/* Create the final PKCS-12 object from the sequences contained in
SEQLIST. PW is the password. That array is terminated with an NULL
object. */
static unsigned char *
create_final (struct buffer_s *sequences, const char *pw, size_t *r_length)
{
int i;
size_t needed = 0;
size_t len[8], n;
unsigned char *macstart;
size_t maclen;
unsigned char *result, *p;
size_t resultlen;
char salt[8];
unsigned char keybuf[20];
gcry_md_hd_t md;
int rc;
int with_mac = 1;
/* 9 steps to create the pkcs#12 Krampf. */
/* 8. The MAC. */
/* We add this at step 0. */
/* 7. All the buffers. */
for (i=0; sequences[i].buffer; i++)
needed += sequences[i].length;
/* 6. This goes into a sequences. */
len[6] = needed;
n = compute_tag_length (needed);
needed += n;
/* 5. Encapsulate all in an octet string. */
len[5] = needed;
n = compute_tag_length (needed);
needed += n;
/* 4. And tag it with [0]. */
len[4] = needed;
n = compute_tag_length (needed);
needed += n;
/* 3. Prepend an data OID. */
needed += 2 + DIM (oid_data);
/* 2. Put all into a sequences. */
len[2] = needed;
n = compute_tag_length (needed);
needed += n;
/* 1. Prepend the version integer 3. */
needed += 3;
/* 0. And the final outer sequence. */
if (with_mac)
needed += DIM (data_mactemplate);
len[0] = needed;
n = compute_tag_length (needed);
needed += n;
/* Allocate a buffer. */
result = gcry_malloc (needed);
if (!result)
{
log_error ("error allocating buffer\n");
return NULL;
}
p = result;
/* 0. Store the very outer sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[0]);
/* 1. Store the version integer 3. */
*p++ = TAG_INTEGER;
*p++ = 1;
*p++ = 3;
/* 2. Store another sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[2]);
/* 3. Store the data OID. */
p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
memcpy (p, oid_data, DIM (oid_data));
p += DIM (oid_data);
/* 4. Next comes a context tag. */
p = store_tag_length (p, 0xa0, len[4]);
/* 5. And an octet string. */
p = store_tag_length (p, TAG_OCTET_STRING, len[5]);
/* 6. And the inner sequence. */
macstart = p;
p = store_tag_length (p, TAG_SEQUENCE, len[6]);
/* 7. Append all the buffers. */
for (i=0; sequences[i].buffer; i++)
{
memcpy (p, sequences[i].buffer, sequences[i].length);
p += sequences[i].length;
}
if (with_mac)
{
/* Intermezzo to compute the MAC. */
maclen = p - macstart;
gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
if (string_to_key (3, salt, 8, 2048, pw, 20, keybuf))
{
gcry_free (result);
return NULL;
}
rc = gcry_md_open (&md, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC);
if (rc)
{
log_error ("gcry_md_open failed: %s\n", gpg_strerror (rc));
gcry_free (result);
return NULL;
}
rc = gcry_md_setkey (md, keybuf, 20);
if (rc)
{
log_error ("gcry_md_setkey failed: %s\n", gpg_strerror (rc));
gcry_md_close (md);
gcry_free (result);
return NULL;
}
gcry_md_write (md, macstart, maclen);
/* 8. Append the MAC template and fix it up. */
memcpy (p, data_mactemplate, DIM (data_mactemplate));
memcpy (p + DATA_MACTEMPLATE_SALT_OFF, salt, 8);
memcpy (p + DATA_MACTEMPLATE_MAC_OFF, gcry_md_read (md, 0), 20);
p += DIM (data_mactemplate);
gcry_md_close (md);
}
/* Ready. */
resultlen = p - result;
if (needed != resultlen)
log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)resultlen);
*r_length = resultlen;
return result;
}
/* Build a DER encoded SEQUENCE with the key:
*
* SEQUENCE { -- OneAsymmetricKey (RFC-5958)
* INTEGER 0
* SEQUENCE {
* OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
* NULL
* }
* OCTET STRING, encapsulates {
* SEQUENCE { -- RSAPrivateKey (RFC-3447)
* INTEGER 0 -- Version
* INTEGER -- n
* INTEGER -- e
* INTEGER -- d
* INTEGER -- p
* INTEGER -- q
* INTEGER -- d mod (p-1)
* INTEGER -- d mod (q-1)
* INTEGER -- q^-1 mod p
* }
* }
* }
*
* MODE controls what is being generated:
* 0 - As described above
* 1 - Ditto but without the padding
* 2 - Only the inner part (pkcs#1)
*/
static unsigned char *
build_rsa_key_sequence (gcry_mpi_t *kparms, int mode, size_t *r_length)
{
int rc, i;
size_t needed, n;
unsigned char *plain, *p;
size_t plainlen;
size_t outseqlen, oidseqlen, octstrlen, inseqlen;
needed = 3; /* The version integer with value 0. */
for (i=0; kparms[i]; i++)
{
n = 0;
rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]);
if (rc)
{
log_error ("error formatting parameter: %s\n", gpg_strerror (rc));
return NULL;
}
needed += n;
n = compute_tag_length (n);
if (!n)
return NULL;
needed += n;
}
if (i != 8)
{
log_error ("invalid parameters for p12_build\n");
return NULL;
}
/* Now this all goes into a sequence. */
inseqlen = needed;
n = compute_tag_length (needed);
if (!n)
return NULL;
needed += n;
if (mode != 2)
{
/* Encapsulate all into an octet string. */
octstrlen = needed;
n = compute_tag_length (needed);
if (!n)
return NULL;
needed += n;
/* Prepend the object identifier sequence. */
oidseqlen = 2 + DIM (oid_rsaEncryption) + 2;
needed += 2 + oidseqlen;
/* The version number. */
needed += 3;
/* And finally put the whole thing into a sequence. */
outseqlen = needed;
n = compute_tag_length (needed);
if (!n)
return NULL;
needed += n;
}
/* allocate 8 extra bytes for padding */
plain = gcry_malloc_secure (needed+8);
if (!plain)
{
log_error ("error allocating encryption buffer\n");
return NULL;
}
/* And now fill the plaintext buffer. */
p = plain;
if (mode != 2)
{
p = store_tag_length (p, TAG_SEQUENCE, outseqlen);
/* Store version. */
*p++ = TAG_INTEGER;
*p++ = 1;
*p++ = 0;
/* Store object identifier sequence. */
p = store_tag_length (p, TAG_SEQUENCE, oidseqlen);
p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_rsaEncryption));
memcpy (p, oid_rsaEncryption, DIM (oid_rsaEncryption));
p += DIM (oid_rsaEncryption);
*p++ = TAG_NULL;
*p++ = 0;
/* Start with the octet string. */
p = store_tag_length (p, TAG_OCTET_STRING, octstrlen);
}
p = store_tag_length (p, TAG_SEQUENCE, inseqlen);
/* Store the key parameters. */
*p++ = TAG_INTEGER;
*p++ = 1;
*p++ = 0;
for (i=0; kparms[i]; i++)
{
n = 0;
rc = gcry_mpi_print (GCRYMPI_FMT_STD, NULL, 0, &n, kparms[i]);
if (rc)
{
log_error ("oops: error formatting parameter: %s\n",
gpg_strerror (rc));
gcry_free (plain);
return NULL;
}
p = store_tag_length (p, TAG_INTEGER, n);
n = plain + needed - p;
rc = gcry_mpi_print (GCRYMPI_FMT_STD, p, n, &n, kparms[i]);
if (rc)
{
log_error ("oops: error storing parameter: %s\n",
gpg_strerror (rc));
gcry_free (plain);
return NULL;
}
p += n;
}
plainlen = p - plain;
log_assert (needed == plainlen);
if (!mode)
{
/* Append some pad characters; we already allocated extra space. */
n = 8 - plainlen % 8;
for (i=0; i < n; i++, plainlen++)
*p++ = n;
}
*r_length = plainlen;
return plain;
}
/* Build a DER encoded SEQUENCE for an ECC key:
*
* SEQUENCE { -- OneAsymmetricKey (RFC-5958)
* INTEGER 0
* SEQUENCE {
* OBJECT IDENTIFIER ecPublicKey (1 2 840 10045 2 1)
* OBJECT IDENTIFIER -- curvename
* }
* OCTET STRING, encapsulates {
* SEQUENCE { -- ECPrivateKey
* INTEGER 1 -- version
* OCTET STRING -- privateKey
* [1] {
* BIT STRING - publicKey
* }
* }
* }
* }
*
* For details see RFC-5480 and RFC-5915 (ECparameters are not created).
*
* KPARMS[0] := Opaque MPI with the curve name as dotted-decimal string.
* KPARMS[1] := Opaque MPI with the public key (q)
* KPARMS[2] := Opaque MPI with the private key (d)
* MODE controls what is being generated:
* 0 - As described above
* 1 - Ditto but without the extra padding needed for pcsk#12
* 2 - Only the octet string (ECPrivateKey)
*/
static unsigned char *
build_ecc_key_sequence (gcry_mpi_t *kparms, int mode, size_t *r_length)
{
gpg_error_t err;
unsigned int nbits, n;
const unsigned char *s;
char *p;
tlv_builder_t tb;
void *result;
size_t resultlen;
const char *curve;
unsigned int curvebits;
int e;
int i;
int strip_one;
for (i=0; kparms[i]; i++)
;
if (i != 3)
{
log_error ("%s: invalid number of parameters\n", __func__);
return NULL;
}
s = gcry_mpi_get_opaque (kparms[0], &nbits);
n = (nbits+7)/8;
p = xtrymalloc (n + 1);
if (!p)
{
err = gpg_error_from_syserror ();
log_error ("%s:%d: error getting parameter: %s\n",
__func__, __LINE__, gpg_strerror (err));
return NULL;
}
memcpy (p, s, n);
p[n] = 0;
/* We need to use our OpenPGP mapping to turn a curve name into its
* canonical numerical OID. We should have a Libgcrypt function to
* do this; see bug report #4926. */
curve = openpgp_curve_to_oid (p, &curvebits, NULL);
xfree (p);
if (!curve)
{
err = gpg_error (GPG_ERR_UNKNOWN_CURVE);
log_error ("%s:%d: error getting parameter: %s\n",
__func__, __LINE__, gpg_strerror (err));
return NULL;
}
/* Unfortunately the private key D may come with a single leading
- * zero byte. This is becuase at some point it was treated as
+ * zero byte. This is because at some point it was treated as
* signed MPI and the code made sure that it is always interpreted
* as unsigned. Fortunately we got the size of the curve and can
* detect such a case reliable. */
s = gcry_mpi_get_opaque (kparms[2], &nbits);
n = (nbits+7)/8;
strip_one = (n == (curvebits+7)/8 + 1 && !*s);
tb = tlv_builder_new (1);
if (!tb)
{
err = gpg_error_from_syserror ();
log_error ("%s:%d: error creating new TLV builder: %s\n",
__func__, __LINE__, gpg_strerror (err));
return NULL;
}
e = 0;
tlv_builder_add_tag (tb, 0, TAG_SEQUENCE);
tlv_builder_add_ptr (tb, 0, TAG_INTEGER, "\0", 1);
tlv_builder_add_tag (tb, 0, TAG_SEQUENCE);
e|= builder_add_oid (tb, 0, "1.2.840.10045.2.1");
e|= builder_add_oid (tb, 0, curve);
tlv_builder_add_end (tb);
tlv_builder_add_tag (tb, 0, TAG_OCTET_STRING);
tlv_builder_add_tag (tb, 0, TAG_SEQUENCE);
tlv_builder_add_ptr (tb, 0, TAG_INTEGER, "\x01", 1);
e|= builder_add_mpi (tb, 0, TAG_OCTET_STRING, kparms[2], strip_one);
tlv_builder_add_tag (tb, CLASS_CONTEXT, 1);
e|= builder_add_mpi (tb, 0, TAG_BIT_STRING, kparms[1], 0);
tlv_builder_add_end (tb);
tlv_builder_add_end (tb);
tlv_builder_add_end (tb);
tlv_builder_add_end (tb);
err = tlv_builder_finalize (tb, &result, &resultlen);
if (err || e)
{
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
log_error ("%s:%d: tlv building failed: %s\n",
__func__, __LINE__, gpg_strerror (err));
return NULL;
}
/* Append some pad characters if needed. */
if (!mode && (n = 8 - resultlen % 8))
{
p = xtrymalloc_secure (resultlen + n);
if (!p)
{
err = gpg_error_from_syserror ();
log_error ("%s:%d: error allocating buffer: %s\n",
__func__, __LINE__, gpg_strerror (err));
xfree (result);
return NULL;
}
memcpy (p, result, resultlen);
xfree (result);
result = p;
p = (unsigned char*)result + resultlen;
for (i=0; i < n; i++, resultlen++)
*p++ = n;
}
*r_length = resultlen;
return result;
}
static unsigned char *
build_key_bag (unsigned char *buffer, size_t buflen, char *salt,
const unsigned char *sha1hash, const char *keyidstr,
size_t *r_length)
{
size_t len[11], needed;
unsigned char *p, *keybag;
size_t keybaglen;
/* Walk 11 steps down to collect the info: */
/* 10. The data goes into an octet string. */
needed = compute_tag_length (buflen);
needed += buflen;
/* 9. Prepend the algorithm identifier. */
needed += DIM (data_3desiter2048);
/* 8. Put a sequence around. */
len[8] = needed;
needed += compute_tag_length (needed);
/* 7. Prepend a [0] tag. */
len[7] = needed;
needed += compute_tag_length (needed);
/* 6b. The attributes which are appended at the end. */
if (sha1hash)
needed += DIM (data_attrtemplate) + 20;
/* 6. Prepend the shroudedKeyBag OID. */
needed += 2 + DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
/* 5+4. Put all into two sequences. */
len[5] = needed;
needed += compute_tag_length ( needed);
len[4] = needed;
needed += compute_tag_length (needed);
/* 3. This all goes into an octet string. */
len[3] = needed;
needed += compute_tag_length (needed);
/* 2. Prepend another [0] tag. */
len[2] = needed;
needed += compute_tag_length (needed);
/* 1. Prepend the data OID. */
needed += 2 + DIM (oid_data);
/* 0. Prepend another sequence. */
len[0] = needed;
needed += compute_tag_length (needed);
/* Now that we have all length information, allocate a buffer. */
p = keybag = gcry_malloc (needed);
if (!keybag)
{
log_error ("error allocating buffer\n");
return NULL;
}
/* Walk 11 steps up to store the data. */
/* 0. Store the first sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[0]);
/* 1. Store the data OID. */
p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
memcpy (p, oid_data, DIM (oid_data));
p += DIM (oid_data);
/* 2. Store a [0] tag. */
p = store_tag_length (p, 0xa0, len[2]);
/* 3. And an octet string. */
p = store_tag_length (p, TAG_OCTET_STRING, len[3]);
/* 4+5. Two sequences. */
p = store_tag_length (p, TAG_SEQUENCE, len[4]);
p = store_tag_length (p, TAG_SEQUENCE, len[5]);
/* 6. Store the shroudedKeyBag OID. */
p = store_tag_length (p, TAG_OBJECT_ID,
DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag));
memcpy (p, oid_pkcs_12_pkcs_8ShroudedKeyBag,
DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag));
p += DIM (oid_pkcs_12_pkcs_8ShroudedKeyBag);
/* 7. Store a [0] tag. */
p = store_tag_length (p, 0xa0, len[7]);
/* 8. Store a sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[8]);
/* 9. Now for the pre-encoded algorithm identifier and the salt. */
memcpy (p, data_3desiter2048, DIM (data_3desiter2048));
memcpy (p + DATA_3DESITER2048_SALT_OFF, salt, 8);
p += DIM (data_3desiter2048);
/* 10. And the octet string with the encrypted data. */
p = store_tag_length (p, TAG_OCTET_STRING, buflen);
memcpy (p, buffer, buflen);
p += buflen;
/* Append the attributes whose length we calculated at step 2b. */
if (sha1hash)
{
int i;
memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
for (i=0; i < 8; i++)
p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
p += DIM (data_attrtemplate);
memcpy (p, sha1hash, 20);
p += 20;
}
keybaglen = p - keybag;
if (needed != keybaglen)
log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)keybaglen);
*r_length = keybaglen;
return keybag;
}
static unsigned char *
build_cert_bag (unsigned char *buffer, size_t buflen, char *salt,
size_t *r_length)
{
size_t len[9], needed;
unsigned char *p, *certbag;
size_t certbaglen;
/* Walk 9 steps down to collect the info: */
/* 8. The data goes into an octet string. */
needed = compute_tag_length (buflen);
needed += buflen;
/* 7. The algorithm identifier. */
needed += DIM (data_rc2iter2048);
/* 6. The data OID. */
needed += 2 + DIM (oid_data);
/* 5. A sequence. */
len[5] = needed;
needed += compute_tag_length ( needed);
/* 4. An integer. */
needed += 3;
/* 3. A sequence. */
len[3] = needed;
needed += compute_tag_length (needed);
/* 2. A [0] tag. */
len[2] = needed;
needed += compute_tag_length (needed);
/* 1. The encryptedData OID. */
needed += 2 + DIM (oid_encryptedData);
/* 0. The first sequence. */
len[0] = needed;
needed += compute_tag_length (needed);
/* Now that we have all length information, allocate a buffer. */
p = certbag = gcry_malloc (needed);
if (!certbag)
{
log_error ("error allocating buffer\n");
return NULL;
}
/* Walk 9 steps up to store the data. */
/* 0. Store the first sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[0]);
/* 1. Store the encryptedData OID. */
p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_encryptedData));
memcpy (p, oid_encryptedData, DIM (oid_encryptedData));
p += DIM (oid_encryptedData);
/* 2. Store a [0] tag. */
p = store_tag_length (p, 0xa0, len[2]);
/* 3. Store a sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[3]);
/* 4. Store the integer 0. */
*p++ = TAG_INTEGER;
*p++ = 1;
*p++ = 0;
/* 5. Store a sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[5]);
/* 6. Store the data OID. */
p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_data));
memcpy (p, oid_data, DIM (oid_data));
p += DIM (oid_data);
/* 7. Now for the pre-encoded algorithm identifier and the salt. */
memcpy (p, data_rc2iter2048, DIM (data_rc2iter2048));
memcpy (p + DATA_RC2ITER2048_SALT_OFF, salt, 8);
p += DIM (data_rc2iter2048);
/* 8. And finally the [0] tag with the encrypted data. */
p = store_tag_length (p, 0x80, buflen);
memcpy (p, buffer, buflen);
p += buflen;
certbaglen = p - certbag;
if (needed != certbaglen)
log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)certbaglen);
*r_length = certbaglen;
return certbag;
}
static unsigned char *
build_cert_sequence (const unsigned char *buffer, size_t buflen,
const unsigned char *sha1hash, const char *keyidstr,
size_t *r_length)
{
size_t len[8], needed, n;
unsigned char *p, *certseq;
size_t certseqlen;
int i;
log_assert (strlen (keyidstr) == 8);
/* Walk 8 steps down to collect the info: */
/* 7. The data goes into an octet string. */
needed = compute_tag_length (buflen);
needed += buflen;
/* 6. A [0] tag. */
len[6] = needed;
needed += compute_tag_length (needed);
/* 5. An OID. */
needed += 2 + DIM (oid_x509Certificate_for_pkcs_12);
/* 4. A sequence. */
len[4] = needed;
needed += compute_tag_length (needed);
/* 3. A [0] tag. */
len[3] = needed;
needed += compute_tag_length (needed);
/* 2b. The attributes which are appended at the end. */
if (sha1hash)
needed += DIM (data_attrtemplate) + 20;
/* 2. An OID. */
needed += 2 + DIM (oid_pkcs_12_CertBag);
/* 1. A sequence. */
len[1] = needed;
needed += compute_tag_length (needed);
/* 0. The first sequence. */
len[0] = needed;
needed += compute_tag_length (needed);
/* Now that we have all length information, allocate a buffer. */
p = certseq = gcry_malloc (needed + 8 /*(for padding)*/);
if (!certseq)
{
log_error ("error allocating buffer\n");
return NULL;
}
/* Walk 8 steps up to store the data. */
/* 0. Store the first sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[0]);
/* 1. Store the second sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[1]);
/* 2. Store the pkcs12-cert-bag OID. */
p = store_tag_length (p, TAG_OBJECT_ID, DIM (oid_pkcs_12_CertBag));
memcpy (p, oid_pkcs_12_CertBag, DIM (oid_pkcs_12_CertBag));
p += DIM (oid_pkcs_12_CertBag);
/* 3. Store a [0] tag. */
p = store_tag_length (p, 0xa0, len[3]);
/* 4. Store a sequence. */
p = store_tag_length (p, TAG_SEQUENCE, len[4]);
/* 5. Store the x509Certificate OID. */
p = store_tag_length (p, TAG_OBJECT_ID,
DIM (oid_x509Certificate_for_pkcs_12));
memcpy (p, oid_x509Certificate_for_pkcs_12,
DIM (oid_x509Certificate_for_pkcs_12));
p += DIM (oid_x509Certificate_for_pkcs_12);
/* 6. Store a [0] tag. */
p = store_tag_length (p, 0xa0, len[6]);
/* 7. And the octet string with the actual certificate. */
p = store_tag_length (p, TAG_OCTET_STRING, buflen);
memcpy (p, buffer, buflen);
p += buflen;
/* Append the attributes whose length we calculated at step 2b. */
if (sha1hash)
{
memcpy (p, data_attrtemplate, DIM (data_attrtemplate));
for (i=0; i < 8; i++)
p[DATA_ATTRTEMPLATE_KEYID_OFF+2*i+1] = keyidstr[i];
p += DIM (data_attrtemplate);
memcpy (p, sha1hash, 20);
p += 20;
}
certseqlen = p - certseq;
if (needed != certseqlen)
log_debug ("p12_parse: warning: length mismatch: %lu, %lu\n",
(unsigned long)needed, (unsigned long)certseqlen);
/* Append some pad characters; we already allocated extra space. */
n = 8 - certseqlen % 8;
for (i=0; i < n; i++, certseqlen++)
*p++ = n;
*r_length = certseqlen;
return certseq;
}
/* Expect the RSA key parameters in KPARMS and a password in PW.
Create a PKCS structure from it and return it as well as the length
in R_LENGTH; return NULL in case of an error. If CHARSET is not
NULL, re-encode PW to that character set. */
unsigned char *
p12_build (gcry_mpi_t *kparms, const void *cert, size_t certlen,
const char *pw, const char *charset, size_t *r_length)
{
unsigned char *buffer = NULL;
size_t n, buflen;
char salt[8];
struct buffer_s seqlist[3];
int seqlistidx = 0;
unsigned char sha1hash[20];
char keyidstr[8+1];
char *pwbuf = NULL;
size_t pwbufsize = 0;
n = buflen = 0; /* (avoid compiler warning). */
memset (sha1hash, 0, 20);
*keyidstr = 0;
if (charset && pw && *pw)
{
jnlib_iconv_t cd;
const char *inptr;
char *outptr;
size_t inbytes, outbytes;
/* We assume that the converted passphrase is at max 2 times
longer than its utf-8 encoding. */
pwbufsize = strlen (pw)*2 + 1;
pwbuf = gcry_malloc_secure (pwbufsize);
if (!pwbuf)
{
log_error ("out of secure memory while converting passphrase\n");
goto failure;
}
cd = jnlib_iconv_open (charset, "utf-8");
if (cd == (jnlib_iconv_t)(-1))
{
log_error ("can't convert passphrase to"
" requested charset '%s': %s\n",
charset, strerror (errno));
goto failure;
}
inptr = pw;
inbytes = strlen (pw);
outptr = pwbuf;
outbytes = pwbufsize - 1;
if ( jnlib_iconv (cd, (const char **)&inptr, &inbytes,
&outptr, &outbytes) == (size_t)-1)
{
log_error ("error converting passphrase to"
" requested charset '%s': %s\n",
charset, strerror (errno));
jnlib_iconv_close (cd);
goto failure;
}
*outptr = 0;
jnlib_iconv_close (cd);
pw = pwbuf;
}
if (cert && certlen)
{
/* Calculate the hash value we need for the bag attributes. */
gcry_md_hash_buffer (GCRY_MD_SHA1, sha1hash, cert, certlen);
sprintf (keyidstr, "%02x%02x%02x%02x",
sha1hash[16], sha1hash[17], sha1hash[18], sha1hash[19]);
/* Encode the certificate. */
buffer = build_cert_sequence (cert, certlen, sha1hash, keyidstr,
&buflen);
if (!buffer)
goto failure;
/* Encrypt it. */
gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
crypt_block (buffer, buflen, salt, 8, 2048, NULL, 0, pw,
GCRY_CIPHER_RFC2268_40, GCRY_MD_SHA1, 1);
/* Encode the encrypted stuff into a bag. */
seqlist[seqlistidx].buffer = build_cert_bag (buffer, buflen, salt, &n);
seqlist[seqlistidx].length = n;
gcry_free (buffer);
buffer = NULL;
if (!seqlist[seqlistidx].buffer)
goto failure;
seqlistidx++;
}
if (kparms)
{
/* Encode the key. */
int i;
/* Right, that is a stupid way to distinguish ECC from RSA. */
for (i=0; kparms[i]; i++)
;
if (i == 3 && gcry_mpi_get_flag (kparms[0], GCRYMPI_FLAG_OPAQUE))
buffer = build_ecc_key_sequence (kparms, 0, &buflen);
else
buffer = build_rsa_key_sequence (kparms, 0, &buflen);
if (!buffer)
goto failure;
/* Encrypt it. */
gcry_randomize (salt, 8, GCRY_STRONG_RANDOM);
crypt_block (buffer, buflen, salt, 8, 2048, NULL, 0,
pw, GCRY_CIPHER_3DES, GCRY_MD_SHA1, 1);
/* Encode the encrypted stuff into a bag. */
if (cert && certlen)
seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt,
sha1hash, keyidstr, &n);
else
seqlist[seqlistidx].buffer = build_key_bag (buffer, buflen, salt,
NULL, NULL, &n);
seqlist[seqlistidx].length = n;
gcry_free (buffer);
buffer = NULL;
if (!seqlist[seqlistidx].buffer)
goto failure;
seqlistidx++;
}
seqlist[seqlistidx].buffer = NULL;
seqlist[seqlistidx].length = 0;
buffer = create_final (seqlist, pw, &buflen);
failure:
if (pwbuf)
{
/* Note that wipememory is not really needed due to the use of
gcry_malloc_secure. */
wipememory (pwbuf, pwbufsize);
gcry_free (pwbuf);
}
for ( ; seqlistidx; seqlistidx--)
gcry_free (seqlist[seqlistidx].buffer);
*r_length = buffer? buflen : 0;
return buffer;
}
/* This is actually not a PKCS#12 function but one which creates an
* unencrypted PKCS#1 private key. */
unsigned char *
p12_raw_build (gcry_mpi_t *kparms, int rawmode, size_t *r_length)
{
unsigned char *buffer;
size_t buflen;
int i;
log_assert (rawmode == 1 || rawmode == 2);
/* Right, that is a stupid way to distinguish ECC from RSA. */
for (i=0; kparms[i]; i++)
;
if (gcry_mpi_get_flag (kparms[0], GCRYMPI_FLAG_OPAQUE))
buffer = build_ecc_key_sequence (kparms, rawmode, &buflen);
else
buffer = build_rsa_key_sequence (kparms, rawmode, &buflen);
if (!buffer)
return NULL;
*r_length = buflen;
return buffer;
}
diff --git a/sm/misc.c b/sm/misc.c
index 3fdfd769d..ec6aeb751 100644
--- a/sm/misc.c
+++ b/sm/misc.c
@@ -1,397 +1,397 @@
/* misc.c - Miscellaneous functions
* Copyright (C) 2004, 2009, 2011 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <unistd.h>
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#include "gpgsm.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/tlv.h"
#include "../common/sexp-parse.h"
/* Print a message
* "(further info: %s)\n
* in verbose mode to further explain an error. That message is
* intended to help debug a problem and should not be translated.
*/
void
gpgsm_print_further_info (const char *format, ...)
{
va_list arg_ptr;
if (!opt.verbose)
return;
log_info (_("(further info: "));
va_start (arg_ptr, format);
log_logv (GPGRT_LOGLVL_CONT, format, arg_ptr);
va_end (arg_ptr);
log_printf (")\n");
}
/* Setup the environment so that the pinentry is able to get all
required information. This is used prior to an exec of the
protect-tool. */
void
setup_pinentry_env (void)
{
#ifndef HAVE_W32_SYSTEM
char *lc;
const char *name, *value;
int iterator;
/* Try to make sure that GPG_TTY has been set. This is needed if we
call for example the protect-tools with redirected stdin and thus
it won't be able to ge a default by itself. Try to do it here
but print a warning. */
value = session_env_getenv (opt.session_env, "GPG_TTY");
if (value)
gnupg_setenv ("GPG_TTY", value, 1);
else if (!(lc=getenv ("GPG_TTY")) || !*lc)
{
log_error (_("GPG_TTY has not been set - "
"using maybe bogus default\n"));
lc = gnupg_ttyname (0);
if (!lc)
lc = "/dev/tty";
gnupg_setenv ("GPG_TTY", lc, 1);
}
if (opt.lc_ctype)
gnupg_setenv ("LC_CTYPE", opt.lc_ctype, 1);
#if defined(HAVE_SETLOCALE) && defined(LC_CTYPE)
else if ( (lc = setlocale (LC_CTYPE, "")) )
gnupg_setenv ("LC_CTYPE", lc, 1);
#endif
if (opt.lc_messages)
gnupg_setenv ("LC_MESSAGES", opt.lc_messages, 1);
#if defined(HAVE_SETLOCALE) && defined(LC_MESSAGES)
else if ( (lc = setlocale (LC_MESSAGES, "")) )
gnupg_setenv ("LC_MESSAGES", lc, 1);
#endif
iterator = 0;
while ((name = session_env_list_stdenvnames (&iterator, NULL)))
{
if (!strcmp (name, "GPG_TTY"))
continue; /* Already set. */
value = session_env_getenv (opt.session_env, name);
if (value)
gnupg_setenv (name, value, 1);
}
#endif /*!HAVE_W32_SYSTEM*/
}
/* Transform a sig-val style s-expression as returned by Libgcrypt to
one which includes an algorithm identifier encoding the public key
and the hash algorithm. The public key algorithm is taken directly
from SIGVAL and the hash algorithm is given by MDALGO. This is
required because X.509 merges the public key algorithm and the hash
algorithm into one OID but Libgcrypt is not aware of that. The
function ignores missing parameters so that it can also be used to
create an siginfo value as expected by ksba_certreq_set_siginfo.
To create a siginfo s-expression a public-key s-expression may be
used instead of a sig-val. */
gpg_error_t
transform_sigval (const unsigned char *sigval, size_t sigvallen, int mdalgo,
unsigned char **r_newsigval, size_t *r_newsigvallen)
{
gpg_error_t err;
const unsigned char *buf, *tok;
size_t buflen, toklen;
int depth, last_depth1, last_depth2, pkalgo;
int is_pubkey = 0;
const unsigned char *rsa_s, *ecc_r, *ecc_s;
size_t rsa_s_len, ecc_r_len, ecc_s_len;
const char *oid;
gcry_sexp_t sexp;
const char *eddsa_curve = NULL;
rsa_s = ecc_r = ecc_s = NULL;
rsa_s_len = ecc_r_len = ecc_s_len = 0;
*r_newsigval = NULL;
if (r_newsigvallen)
*r_newsigvallen = 0;
buf = sigval;
buflen = sigvallen;
depth = 0;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 7 && !memcmp ("sig-val", tok, toklen))
;
else if (tok && toklen == 10 && !memcmp ("public-key", tok, toklen))
is_pubkey = 1;
else
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (!tok)
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
if (toklen == 3 && !memcmp ("rsa", tok, 3))
pkalgo = GCRY_PK_RSA;
else if (toklen == 3 && !memcmp ("ecc", tok, 3))
pkalgo = GCRY_PK_ECC;
else if (toklen == 5 && !memcmp ("ecdsa", tok, 5))
pkalgo = GCRY_PK_ECC;
else if (toklen == 5 && !memcmp ("eddsa", tok, 5))
pkalgo = GCRY_PK_EDDSA;
else
return gpg_error (GPG_ERR_WRONG_PUBKEY_ALGO);
last_depth1 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth1)
{
if (tok)
return gpg_error (GPG_ERR_UNKNOWN_SEXP);
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && toklen == 1)
{
const unsigned char **mpi = NULL;
size_t *mpi_len = NULL;
switch (*tok)
{
case 's':
if (pkalgo == GCRY_PK_RSA)
{
mpi = &rsa_s;
mpi_len = &rsa_s_len;
}
else if (pkalgo == GCRY_PK_ECC || pkalgo == GCRY_PK_EDDSA)
{
mpi = &ecc_s;
mpi_len = &ecc_s_len;
}
break;
case 'r': mpi = &ecc_r; mpi_len = &ecc_r_len; break;
default: mpi = NULL; mpi_len = NULL; break;
}
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if (tok && mpi)
{
*mpi = tok;
*mpi_len = toklen;
}
}
else if (toklen == 5 && !memcmp (tok, "curve", 5))
{
if ((err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen)))
return err;
if ((toklen == 7 && !memcmp (tok, "Ed25519", 7))
|| (toklen == 22 && !memcmp (tok, "1.3.6.1.4.1.11591.15.1", 22))
|| (toklen == 11 && !memcmp (tok, "1.3.101.112", 11)))
eddsa_curve = "1.3.101.112";
else if ((toklen == 5 && !memcmp (tok, "Ed448", 5))
|| (toklen == 11 && !memcmp (tok, "1.3.101.113", 11)))
eddsa_curve = "1.3.101.113";
}
/* Skip to the end of the list. */
last_depth2 = depth;
while (!(err = parse_sexp (&buf, &buflen, &depth, &tok, &toklen))
&& depth && depth >= last_depth2)
;
if (err)
return err;
}
if (err)
return err;
if (eddsa_curve)
oid = eddsa_curve;
else
{
/* Map the hash algorithm to an OID. */
if (mdalgo < 0 || mdalgo > (1<<15) || pkalgo < 0 || pkalgo > (1<<15))
return gpg_error (GPG_ERR_DIGEST_ALGO);
switch (mdalgo | (pkalgo << 16))
{
case GCRY_MD_SHA1 | (GCRY_PK_RSA << 16):
oid = "1.2.840.113549.1.1.5"; /* sha1WithRSAEncryption */
break;
case GCRY_MD_SHA256 | (GCRY_PK_RSA << 16):
oid = "1.2.840.113549.1.1.11"; /* sha256WithRSAEncryption */
break;
case GCRY_MD_SHA384 | (GCRY_PK_RSA << 16):
oid = "1.2.840.113549.1.1.12"; /* sha384WithRSAEncryption */
break;
case GCRY_MD_SHA512 | (GCRY_PK_RSA << 16):
oid = "1.2.840.113549.1.1.13"; /* sha512WithRSAEncryption */
break;
case GCRY_MD_SHA224 | (GCRY_PK_ECC << 16):
oid = "1.2.840.10045.4.3.1"; /* ecdsa-with-sha224 */
break;
case GCRY_MD_SHA256 | (GCRY_PK_ECC << 16):
oid = "1.2.840.10045.4.3.2"; /* ecdsa-with-sha256 */
break;
case GCRY_MD_SHA384 | (GCRY_PK_ECC << 16):
oid = "1.2.840.10045.4.3.3"; /* ecdsa-with-sha384 */
break;
case GCRY_MD_SHA512 | (GCRY_PK_ECC << 16):
oid = "1.2.840.10045.4.3.4"; /* ecdsa-with-sha512 */
break;
case GCRY_MD_SHA512 | (GCRY_PK_EDDSA << 16):
oid = "1.3.101.112"; /* ed25519 */
break;
default:
return gpg_error (GPG_ERR_DIGEST_ALGO);
}
}
if (is_pubkey)
err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s))", oid);
else if (pkalgo == GCRY_PK_RSA)
err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(s%b)))", oid,
(int)rsa_s_len, rsa_s);
else if (pkalgo == GCRY_PK_ECC || pkalgo == GCRY_PK_EDDSA)
err = gcry_sexp_build (&sexp, NULL, "(sig-val(%s(r%b)(s%b)))", oid,
(int)ecc_r_len, ecc_r, (int)ecc_s_len, ecc_s);
if (err)
return err;
err = make_canon_sexp (sexp, r_newsigval, r_newsigvallen);
gcry_sexp_release (sexp);
return err;
}
/* Wrapper around ksba_cms_get_sig_val to return a gcrypt object
- * instaed of ksba's canonical s-expression. On errror NULL is return
+ * instead of ksba's canonical s-expression. On error NULL is return
* and in some cases an error message is printed. */
gcry_sexp_t
gpgsm_ksba_cms_get_sig_val (ksba_cms_t cms, int idx)
{
gpg_error_t err;
ksba_sexp_t sigval;
gcry_sexp_t s_sigval;
size_t n;
sigval = ksba_cms_get_sig_val (cms, idx);
if (!sigval)
return NULL;
n = gcry_sexp_canon_len (sigval, 0, NULL, NULL);
if (!n)
{
log_error ("%s: libksba did not return a proper S-Exp\n", __func__);
ksba_free (sigval);
return NULL;
}
err = gcry_sexp_sscan (&s_sigval, NULL, (char*)sigval, n);
ksba_free (sigval);
if (err)
{
log_error ("%s: gcry_sexp_scan failed: %s\n",
__func__, gpg_strerror (err));
s_sigval = NULL;
}
return s_sigval;
}
/* Return the hash algorithm from the S-expression SIGVAL. Returns 0
* if the hash algorithm is not encoded in SIGVAL or it is not
* supported by libgcrypt. It further stores flag values for the
* public key algorithm at R_PKALGO_FLAGS; the only flag we currently
* support is PK_ALGO_FLAG_RSAPSS. */
int
gpgsm_get_hash_algo_from_sigval (gcry_sexp_t sigval_arg,
unsigned int *r_pkalgo_flags)
{
gcry_sexp_t sigval, l1;
size_t n;
const char *s;
char *string;
int hashalgo;
int i;
*r_pkalgo_flags = 0;
sigval = gcry_sexp_find_token (sigval_arg, "sig-val", 0);
if (!sigval)
return 0; /* Not a sig-val. */
/* First check whether this is a rsaPSS signature and return that as
* additional info. */
l1 = gcry_sexp_find_token (sigval, "flags", 0);
if (l1)
{
/* Note that the flag parser assumes that the list of flags
* contains only strings and in particular not a sub-list. This
* is always the case for the current libksba. */
for (i=1; (s = gcry_sexp_nth_data (l1, i, &n)); i++)
if (n == 3 && !memcmp (s, "pss", 3))
{
*r_pkalgo_flags |= PK_ALGO_FLAG_RSAPSS;
break;
}
gcry_sexp_release (l1);
}
l1 = gcry_sexp_find_token (sigval, "hash", 0);
if (!l1)
{
gcry_sexp_release (sigval);
return 0; /* hash algorithm not given in sigval. */
}
string = gcry_sexp_nth_string (l1, 1);
gcry_sexp_release (sigval);
if (!string)
return 0; /* hash algorithm has no value. */
hashalgo = gcry_md_map_name (string);
gcry_free (string);
return hashalgo;
}
diff --git a/tests/asschk.c b/tests/asschk.c
index e278069bf..b32f9361c 100644
--- a/tests/asschk.c
+++ b/tests/asschk.c
@@ -1,1094 +1,1094 @@
/* asschk.c - Assuan Server Checker
* Copyright (C) 2002 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
/* This is a simple stand-alone Assuan server test program. We don't
want to use the assuan library because we don't want to hide errors
in that library.
The script language is line based. Empty lines or lines containing
only white spaces are ignored, line with a hash sign as first non
white space character are treated as comments.
- A simple macro mechanism is implemnted. Macros are expanded before
+ A simple macro mechanism is implemented. Macros are expanded before
a line is processed but after comment processing. Macros are only
expanded once and non existing macros expand to the empty string.
A macro is dereferenced by prefixing its name with a dollar sign;
the end of the name is currently indicated by a white space, a
dollar sign or a slash. To use a dollar sign verbatim, double it.
A macro is assigned by prefixing a statement with the macro name
and an equal sign. The value is assigned verbatim if it does not
resemble a command, otherwise the return value of the command will
get assigned. The command "let" may be used to assign values
- unambigiously and it should be used if the value starts with a
+ unambiguously and it should be used if the value starts with a
letter.
Conditions are not yes implemented except for a simple evaluation
which yields false for an empty string or the string "0". The
result may be negated by prefixing with a '!'.
The general syntax of a command is:
[<name> =] <statement> [<args>]
If NAME is not specified but the statement returns a value it is
assigned to the name "?" so that it can be referenced using "$?".
The following commands are implemented:
let <value>
Return VALUE.
echo <value>
Print VALUE.
openfile <filename>
Open file FILENAME for read access and return the file descriptor.
createfile <filename>
Create file FILENAME, open for write access and return the file
descriptor.
pipeserver <program>
Connect to the Assuan server PROGRAM.
send <line>
Send LINE to the server.
expect-ok
Expect an OK response from the server. Status and data out put
is ignored.
expect-err
Expect an ERR response from the server. Status and data out put
is ignored.
count-status <code>
Initialize the assigned variable to 0 and assign it as an counter for
status code CODE. This command must be called with an assignment.
quit
Terminate the process.
quit-if <condition>
Terminate the process if CONDITION evaluates to true.
fail-if <condition>
Terminate the process with an exit code of 1 if CONDITION
evaluates to true.
cmpfiles <first> <second>
Returns true when the content of the files FIRST and SECOND match.
getenv <name>
Return the value of the environment variable NAME.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <assert.h>
#include <unistd.h>
#include <fcntl.h>
#if __GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 )
# define ATTR_PRINTF(f,a) __attribute__ ((format (printf,f,a)))
#else
# define ATTR_PRINTF(f,a)
#endif
#if __STDC_VERSION__ < 199901L
# if __GNUC__ >= 2 && !defined (__func__)
# define __func__ __FUNCTION__
# else
/* Let's try our luck here. Some systems may provide __func__ without
providing __STDC_VERSION__ 199901L. */
# if 0
# define __func__ "<unknown>"
# endif
# endif
#endif
#define spacep(p) (*(p) == ' ' || *(p) == '\t')
#define MAX_LINELEN 2048
typedef enum {
LINE_OK = 0,
LINE_ERR,
LINE_STAT,
LINE_DATA,
LINE_END,
} LINETYPE;
typedef enum {
VARTYPE_SIMPLE = 0,
VARTYPE_FD,
VARTYPE_COUNTER
} VARTYPE;
struct variable_s {
struct variable_s *next;
VARTYPE type;
unsigned int count;
char *value;
char name[1];
};
typedef struct variable_s *VARIABLE;
static void die (const char *format, ...) ATTR_PRINTF(1,2);
/* Name of this program to be printed in error messages. */
static const char *invocation_name;
/* Talk a bit about what is going on. */
static int opt_verbose;
/* Option to ignore the echo command. */
static int opt_no_echo;
/* File descriptors used to communicate with the current server. */
static int server_send_fd = -1;
static int server_recv_fd = -1;
/* The Assuan protocol limits the line length to 1024, so we can
safely use a (larger) buffer. The buffer is filled using the
read_assuan(). */
static char recv_line[MAX_LINELEN];
/* Tell the status of the current line. */
static LINETYPE recv_type;
/* This is our variable storage. */
static VARIABLE variable_list;
static void
die (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
fprintf (stderr, "%s: ", invocation_name);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
exit (1);
}
#define die_0(format) (die) ("%s: " format, __func__)
#define die_1(format, a) (die) ("%s: " format, __func__, (a))
#define die_2(format, a, b) (die) ("%s: " format, __func__, (a),(b))
#define die_3(format, a, b, c) (die) ("%s: " format, __func__, (a),(b),(c))
static void
err (const char *format, ...)
{
va_list arg_ptr;
fflush (stdout);
fprintf (stderr, "%s: ", invocation_name);
va_start (arg_ptr, format);
vfprintf (stderr, format, arg_ptr);
va_end (arg_ptr);
putc ('\n', stderr);
}
static void *
xmalloc (size_t n)
{
void *p = malloc (n);
if (!p)
die ("out of core");
return p;
}
static void *
xcalloc (size_t n, size_t m)
{
void *p = calloc (n, m);
if (!p)
die ("out of core");
return p;
}
static char *
xstrdup (const char *s)
{
char *p = xmalloc (strlen (s)+1);
strcpy (p, s);
return p;
}
/* Write LENGTH bytes from BUFFER to FD. */
static int
writen (int fd, const char *buffer, size_t length)
{
while (length)
{
int nwritten = write (fd, buffer, length);
if (nwritten < 0)
{
if (errno == EINTR)
continue;
return -1; /* write error */
}
length -= nwritten;
buffer += nwritten;
}
return 0; /* okay */
}
/* Assuan specific stuff. */
/* Read a line from FD, store it in the global recv_line, analyze the
type and store that in recv_type. The function terminates on a
communication error. Returns a pointer into the inputline to the
first byte of the arguments. The parsing is very strict to match
exactly what we want to send. */
static char *
read_assuan (int fd)
{
/* FIXME: For general robustness, the pending stuff needs to be
associated with FD. */
static char pending[MAX_LINELEN];
static size_t pending_len;
size_t nleft = sizeof recv_line;
char *buf = recv_line;
char *p;
while (nleft > 0)
{
int n;
if (pending_len)
{
if (pending_len >= nleft)
die_0 ("received line too large");
memcpy (buf, pending, pending_len);
n = pending_len;
pending_len = 0;
}
else
{
do
{
n = read (fd, buf, nleft);
}
while (n < 0 && errno == EINTR);
}
if (opt_verbose && n >= 0 )
{
int i;
printf ("%s: read \"", __func__);
for (i = 0; i < n; i ++)
putc (buf[i], stdout);
printf ("\"\n");
}
if (n < 0)
die_2 ("reading fd %d failed: %s", fd, strerror (errno));
else if (!n)
die_1 ("received incomplete line on fd %d", fd);
p = buf;
nleft -= n;
buf += n;
for (; n && *p != '\n'; n--, p++)
;
if (n)
{
if (n>1)
{
n--;
memcpy (pending, p + 1, n);
pending_len = n;
}
*p = '\0';
break;
}
}
if (!nleft)
die_0 ("received line too large");
p = recv_line;
if (p[0] == 'O' && p[1] == 'K' && (p[2] == ' ' || !p[2]))
{
recv_type = LINE_OK;
p += 3;
}
else if (p[0] == 'E' && p[1] == 'R' && p[2] == 'R'
&& (p[3] == ' ' || !p[3]))
{
recv_type = LINE_ERR;
p += 4;
}
else if (p[0] == 'S' && (p[1] == ' ' || !p[1]))
{
recv_type = LINE_STAT;
p += 2;
}
else if (p[0] == 'D' && p[1] == ' ')
{
recv_type = LINE_DATA;
p += 2;
}
else if (p[0] == 'E' && p[1] == 'N' && p[2] == 'D' && !p[3])
{
recv_type = LINE_END;
p += 3;
}
else
die_1 ("invalid line type (%.5s)", p);
return p;
}
/* Write LINE to the server using FD. It is expected that the line
contains the terminating linefeed as last character. */
static void
write_assuan (int fd, const char *line)
{
char buffer[1026];
size_t n = strlen (line);
if (n > 1024)
die_0 ("line too long for Assuan protocol");
strcpy (buffer, line);
if (!n || buffer[n-1] != '\n')
buffer[n++] = '\n';
if (writen (fd, buffer, n))
die_3 ("sending line (\"%s\") to %d failed: %s", buffer, fd,
strerror (errno));
}
/* Start the server with path PGMNAME and connect its stdout and
strerr to a newly created pipes; the file descriptors are then
store in the global variables SERVER_SEND_FD and
SERVER_RECV_FD. The initial handcheck is performed.*/
static void
start_server (const char *pgmname)
{
int rp[2];
int wp[2];
pid_t pid;
if (pipe (rp) < 0)
die_1 ("pipe creation failed: %s", strerror (errno));
if (pipe (wp) < 0)
die_1 ("pipe creation failed: %s", strerror (errno));
fflush (stdout);
fflush (stderr);
pid = fork ();
if (pid < 0)
die_0 ("fork failed");
if (!pid)
{
const char *arg0;
arg0 = strrchr (pgmname, '/');
if (arg0)
arg0++;
else
arg0 = pgmname;
if (wp[0] != STDIN_FILENO)
{
if (dup2 (wp[0], STDIN_FILENO) == -1)
die_1 ("dup2 failed in child: %s", strerror (errno));
close (wp[0]);
}
if (rp[1] != STDOUT_FILENO)
{
if (dup2 (rp[1], STDOUT_FILENO) == -1)
die_1 ("dup2 failed in child: %s", strerror (errno));
close (rp[1]);
}
if (!opt_verbose)
{
int fd = open ("/dev/null", O_WRONLY);
if (fd == -1)
die_1 ("can't open '/dev/null': %s", strerror (errno));
if (dup2 (fd, STDERR_FILENO) == -1)
die_1 ("dup2 failed in child: %s", strerror (errno));
close (fd);
}
close (wp[1]);
close (rp[0]);
execl (pgmname, arg0, "--server", NULL);
die_2 ("exec failed for '%s': %s", pgmname, strerror (errno));
}
close (wp[0]);
close (rp[1]);
server_send_fd = wp[1];
server_recv_fd = rp[0];
read_assuan (server_recv_fd);
if (recv_type != LINE_OK)
die_0 ("no greating message");
}
/* Script interpreter. */
static void
unset_var (const char *name)
{
VARIABLE var;
for (var=variable_list; var && strcmp (var->name, name); var = var->next)
;
if (!var)
return;
/* fprintf (stderr, "unsetting '%s'\n", name); */
if (var->type == VARTYPE_FD && var->value)
{
int fd;
fd = atoi (var->value);
if (fd != -1 && fd != 0 && fd != 1 && fd != 2)
close (fd);
}
free (var->value);
var->value = NULL;
var->type = 0;
var->count = 0;
}
static void
set_type_var (const char *name, const char *value, VARTYPE type)
{
VARIABLE var;
if (!name)
name = "?";
for (var=variable_list; var && strcmp (var->name, name); var = var->next)
;
if (!var)
{
var = xcalloc (1, sizeof *var + strlen (name));
strcpy (var->name, name);
var->next = variable_list;
variable_list = var;
}
else
{
free (var->value);
var->value = NULL;
}
if (var->type == VARTYPE_FD && var->value)
{
int fd;
fd = atoi (var->value);
if (fd != -1 && fd != 0 && fd != 1 && fd != 2)
close (fd);
}
var->type = type;
var->count = 0;
if (var->type == VARTYPE_COUNTER)
{
/* We need some extra sapce as scratch area for get_var. */
var->value = xmalloc (strlen (value) + 1 + 20);
strcpy (var->value, value);
}
else
var->value = xstrdup (value);
}
static void
set_var (const char *name, const char *value)
{
set_type_var (name, value, 0);
}
static const char *
get_var (const char *name)
{
VARIABLE var;
for (var=variable_list; var && strcmp (var->name, name); var = var->next)
;
if (!var)
return NULL;
if (var->type == VARTYPE_COUNTER && var->value)
{ /* Use the scratch space allocated by set_var. */
char *p = var->value + strlen(var->value)+1;
sprintf (p, "%u", var->count);
return p;
}
else
return var->value;
}
/* Incremente all counter type variables with NAME in their VALUE. */
static void
inc_counter (const char *name)
{
VARIABLE var;
if (!*name)
return;
for (var=variable_list; var; var = var->next)
{
if (var->type == VARTYPE_COUNTER
&& var->value && !strcmp (var->value, name))
var->count++;
}
}
/* Expand variables in LINE and return a new allocated buffer if
required. The function might modify LINE if the expanded version
fits into it. */
static char *
expand_line (char *buffer)
{
char *line = buffer;
char *p, *pend;
const char *value;
size_t valuelen, n;
char *result = NULL;
while (*line)
{
p = strchr (line, '$');
if (!p)
return result; /* nothing more to expand */
if (p[1] == '$') /* quoted */
{
memmove (p, p+1, strlen (p+1)+1);
line = p + 1;
continue;
}
for (pend=p+1; *pend && !spacep (pend)
&& *pend != '$' && *pend != '/'; pend++)
;
if (*pend)
{
int save = *pend;
*pend = 0;
value = get_var (p+1);
*pend = save;
}
else
value = get_var (p+1);
if (!value)
value = "";
valuelen = strlen (value);
if (valuelen <= pend - p)
{
memcpy (p, value, valuelen);
p += valuelen;
n = pend - p;
if (n)
memmove (p, p+n, strlen (p+n)+1);
line = p;
}
else
{
char *src = result? result : buffer;
char *dst;
dst = xmalloc (strlen (src) + valuelen + 1);
n = p - src;
memcpy (dst, src, n);
memcpy (dst + n, value, valuelen);
n += valuelen;
strcpy (dst + n, pend);
line = dst + n;
free (result);
result = dst;
}
}
return result;
}
/* Evaluate COND and return the result. */
static int
eval_boolean (const char *cond)
{
int tru = 1;
for ( ; *cond == '!'; cond++)
tru = !tru;
if (!*cond || (*cond == '0' && !cond[1]))
return !tru;
return tru;
}
static void
cmd_let (const char *assign_to, char *arg)
{
set_var (assign_to, arg);
}
static void
cmd_echo (const char *assign_to, char *arg)
{
(void)assign_to;
if (!opt_no_echo)
printf ("%s\n", arg);
}
static void
cmd_send (const char *assign_to, char *arg)
{
(void)assign_to;
if (opt_verbose)
fprintf (stderr, "sending '%s'\n", arg);
write_assuan (server_send_fd, arg);
}
static void
handle_status_line (char *arg)
{
char *p;
for (p=arg; *p && !spacep (p); p++)
;
if (*p)
{
int save = *p;
*p = 0;
inc_counter (arg);
*p = save;
}
else
inc_counter (arg);
}
static void
cmd_expect_ok (const char *assign_to, char *arg)
{
(void)assign_to;
(void)arg;
if (opt_verbose)
fprintf (stderr, "expecting OK\n");
do
{
char *p = read_assuan (server_recv_fd);
if (opt_verbose > 1)
fprintf (stderr, "got line '%s'\n", recv_line);
if (recv_type == LINE_STAT)
handle_status_line (p);
}
while (recv_type != LINE_OK && recv_type != LINE_ERR);
if (recv_type != LINE_OK)
die_1 ("expected OK but got '%s'", recv_line);
}
static void
cmd_expect_err (const char *assign_to, char *arg)
{
(void)assign_to;
(void)arg;
if (opt_verbose)
fprintf (stderr, "expecting ERR\n");
do
{
char *p = read_assuan (server_recv_fd);
if (opt_verbose > 1)
fprintf (stderr, "got line '%s'\n", recv_line);
if (recv_type == LINE_STAT)
handle_status_line (p);
}
while (recv_type != LINE_OK && recv_type != LINE_ERR);
if (recv_type != LINE_ERR)
die_1 ("expected ERR but got '%s'", recv_line);
}
static void
cmd_count_status (const char *assign_to, char *arg)
{
char *p;
if (!*assign_to || !*arg)
die_0 ("syntax error: count-status requires an argument and a variable");
for (p=arg; *p && !spacep (p); p++)
;
if (*p)
{
for (*p++ = 0; spacep (p); p++)
;
if (*p)
die_0 ("cmpfiles: syntax error");
}
set_type_var (assign_to, arg, VARTYPE_COUNTER);
}
static void
cmd_openfile (const char *assign_to, char *arg)
{
int fd;
char numbuf[20];
do
fd = open (arg, O_RDONLY);
while (fd == -1 && errno == EINTR);
if (fd == -1)
die_2 ("error opening '%s': %s", arg, strerror (errno));
sprintf (numbuf, "%d", fd);
set_type_var (assign_to, numbuf, VARTYPE_FD);
}
static void
cmd_createfile (const char *assign_to, char *arg)
{
int fd;
char numbuf[20];
do
fd = open (arg, O_WRONLY|O_CREAT|O_TRUNC, 0666);
while (fd == -1 && errno == EINTR);
if (fd == -1)
die_2 ("error creating '%s': %s", arg, strerror (errno));
sprintf (numbuf, "%d", fd);
set_type_var (assign_to, numbuf, VARTYPE_FD);
}
static void
cmd_pipeserver (const char *assign_to, char *arg)
{
(void)assign_to;
if (!*arg)
die_0 ("syntax error: servername missing");
start_server (arg);
}
static void
cmd_quit_if(const char *assign_to, char *arg)
{
(void)assign_to;
if (eval_boolean (arg))
exit (0);
}
static void
cmd_fail_if(const char *assign_to, char *arg)
{
(void)assign_to;
if (eval_boolean (arg))
exit (1);
}
static void
cmd_cmpfiles (const char *assign_to, char *arg)
{
char *p = arg;
char *second;
FILE *fp1, *fp2;
char buffer1[2048]; /* note: both must be of equal size. */
char buffer2[2048];
size_t nread1, nread2;
int rc = 0;
set_var (assign_to, "0");
for (p=arg; *p && !spacep (p); p++)
;
if (!*p)
die_0 ("cmpfiles: syntax error");
for (*p++ = 0; spacep (p); p++)
;
second = p;
for (; *p && !spacep (p); p++)
;
if (*p)
{
for (*p++ = 0; spacep (p); p++)
;
if (*p)
die_0 ("cmpfiles: syntax error");
}
fp1 = fopen (arg, "rb");
if (!fp1)
{
err ("can't open '%s': %s", arg, strerror (errno));
return;
}
fp2 = fopen (second, "rb");
if (!fp2)
{
err ("can't open '%s': %s", second, strerror (errno));
fclose (fp1);
return;
}
while ( (nread1 = fread (buffer1, 1, sizeof buffer1, fp1)))
{
if (ferror (fp1))
break;
nread2 = fread (buffer2, 1, sizeof buffer2, fp2);
if (ferror (fp2))
break;
if (nread1 != nread2 || memcmp (buffer1, buffer2, nread1))
{
rc = 1;
break;
}
}
if (feof (fp1) && feof (fp2) && !rc)
{
if (opt_verbose)
err ("files match");
set_var (assign_to, "1");
}
else if (!rc)
err ("cmpfiles: read error: %s", strerror (errno));
else
err ("cmpfiles: mismatch");
fclose (fp1);
fclose (fp2);
}
static void
cmd_getenv (const char *assign_to, char *arg)
{
const char *s;
s = *arg? getenv (arg):"";
set_var (assign_to, s? s:"");
}
/* Process the current script line LINE. */
static int
interpreter (char *line)
{
static struct {
const char *name;
void (*fnc)(const char*, char*);
} cmdtbl[] = {
{ "let" , cmd_let },
{ "echo" , cmd_echo },
{ "send" , cmd_send },
{ "expect-ok" , cmd_expect_ok },
{ "expect-err", cmd_expect_err },
{ "count-status", cmd_count_status },
{ "openfile" , cmd_openfile },
{ "createfile", cmd_createfile },
{ "pipeserver", cmd_pipeserver },
{ "quit" , NULL },
{ "quit-if" , cmd_quit_if },
{ "fail-if" , cmd_fail_if },
{ "cmpfiles" , cmd_cmpfiles },
{ "getenv" , cmd_getenv },
{ NULL }
};
char *p, *save_p;
int i, save_c;
char *stmt = NULL;
char *assign_to = NULL;
char *must_free = NULL;
for ( ;spacep (line); line++)
;
if (!*line || *line == '#')
return 0; /* empty or comment */
p = expand_line (line);
if (p)
{
must_free = p;
line = p;
for ( ;spacep (line); line++)
;
if (!*line || *line == '#')
{
free (must_free);
return 0; /* empty or comment */
}
}
for (p=line; *p && !spacep (p) && *p != '='; p++)
;
if (*p == '=')
{
*p = 0;
assign_to = line;
}
else if (*p)
{
for (*p++ = 0; spacep (p); p++)
;
if (*p == '=')
assign_to = line;
}
if (!*line)
die_0 ("syntax error");
stmt = line;
save_c = 0;
save_p = NULL;
if (assign_to)
{ /* this is an assignment */
for (p++; spacep (p); p++)
;
if (!*p)
{
unset_var (assign_to);
free (must_free);
return 0;
}
stmt = p;
for (; *p && !spacep (p); p++)
;
if (*p)
{
save_p = p;
save_c = *p;
for (*p++ = 0; spacep (p); p++)
;
}
}
for (i=0; cmdtbl[i].name && strcmp (stmt, cmdtbl[i].name); i++)
;
if (!cmdtbl[i].name)
{
if (!assign_to)
die_1 ("invalid statement '%s'\n", stmt);
if (save_p)
*save_p = save_c;
set_var (assign_to, stmt);
free (must_free);
return 0;
}
if (cmdtbl[i].fnc)
cmdtbl[i].fnc (assign_to, p);
free (must_free);
return cmdtbl[i].fnc? 0:1;
}
int
main (int argc, char **argv)
{
char buffer[2048];
char *p, *pend;
if (!argc)
invocation_name = "asschk";
else
{
invocation_name = *argv++;
argc--;
p = strrchr (invocation_name, '/');
if (p)
invocation_name = p+1;
}
set_var ("?","1"); /* defaults to true */
for (; argc; argc--, argv++)
{
p = *argv;
if (*p != '-')
break;
if (!strcmp (p, "--verbose"))
opt_verbose++;
else if (!strcmp (p, "--no-echo"))
opt_no_echo++;
else if (*p == '-' && p[1] == 'D')
{
p += 2;
pend = strchr (p, '=');
if (pend)
{
int tmp = *pend;
*pend = 0;
set_var (p, pend+1);
*pend = tmp;
}
else
set_var (p, "1");
}
else if (*p == '-' && p[1] == '-' && !p[2])
{
argc--; argv++;
break;
}
else
break;
}
if (argc)
die ("usage: asschk [--verbose] {-D<name>[=<value>]}");
while (fgets (buffer, sizeof buffer, stdin))
{
p = strchr (buffer,'\n');
if (!p)
die_0 ("incomplete script line");
*p = 0;
if (interpreter (buffer))
break;
fflush (stdout);
}
return 0;
}
diff --git a/tests/openpgp/all-tests.scm b/tests/openpgp/all-tests.scm
index e40e02dc3..3c6ae5eab 100644
--- a/tests/openpgp/all-tests.scm
+++ b/tests/openpgp/all-tests.scm
@@ -1,91 +1,91 @@
;; Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
(export all-tests
;; Parse the Makefile.am to find all tests.
(load (with-path "makefile.scm"))
(define (expander filename port key)
(parse-makefile port key))
(define (parse filename key)
(parse-makefile-expand filename expander key))
(define setup
(make-environment-cache
(test::scm
#f
#f
(path-join "tests" "openpgp" "setup.scm")
(in-srcdir "tests" "openpgp" "setup.scm"))))
(define (qualify path variant)
(string-append "<" variant ">" path))
(define (setup* variant)
(make-environment-cache
(test::scm
#f
variant
(path-join "tests" "openpgp" "setup.scm")
(in-srcdir "tests" "openpgp" "setup.scm")
(string-append "--" variant))))
(define setup-use-keyring (setup* "use-keyring"))
(define setup-use-keyboxd (setup* "use-keyboxd"))
(define all-tests
(parse-makefile-expand (in-srcdir "tests" "openpgp" "Makefile.am")
(lambda (filename port key) (parse-makefile port key))
"XTESTS"))
(define keyboxd-enabled?
;; Parse the variable "libexec_PROGRAMS" in kbx/Makefile
(not (null?
(parse-makefile-expand (in-objdir "kbx" "Makefile")
(lambda (filename port key) (parse-makefile port key))
"libexec_PROGRAMS"))))
(define tests
(map (lambda (name)
(test::scm setup
"standard"
(path-join "tests" "openpgp" name)
(in-srcdir "tests" "openpgp" name))) all-tests))
(when *run-all-tests*
(set! tests
(append
tests
;; The second pass uses the keyboxd
(if keyboxd-enabled?
(map (lambda (name)
(test::scm setup-use-keyboxd
"keyboxd"
(path-join "tests" "openpgp" name)
(in-srcdir "tests" "openpgp" name)
"--use-keyboxd")) all-tests))
- ;; The third pass uses the legact pubring.gpg
+ ;; The third pass uses the legacy pubring.gpg
(map (lambda (name)
(test::scm setup-use-keyring
"keyring"
(path-join "tests" "openpgp" name)
(in-srcdir "tests" "openpgp" name)
"--use-keyring")) all-tests))))
tests)
diff --git a/tests/openpgp/defs.scm b/tests/openpgp/defs.scm
index 1ac25bf65..686439a36 100644
--- a/tests/openpgp/defs.scm
+++ b/tests/openpgp/defs.scm
@@ -1,533 +1,533 @@
;; Common definitions for the OpenPGP test scripts.
;;
;; Copyright (C) 2016, 2017 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 <http://www.gnu.org/licenses/>.
;;
;; Constants.
;;
(define usrname1 "one@example.com")
(define usrpass1 "def")
(define usrname2 "two@example.com")
(define usrpass2 "")
(define usrname3 "three@example.com")
(define usrpass3 "")
(define dsa-usrname1 "pgp5")
;; we use the sub key because we do not yet have the logic to derive
;; the first encryption key from a keyblock (I guess) (Well of course
;; we have this by now and the notation below will lookup the primary
;; first and then search for the encryption subkey.)
(define dsa-usrname2 "0xCB879DE9")
(define keys
(package
(define (new fpr grip uids subkeys)
(package))
(define (subkey fpr grip)
(package))
(define alfa (new "A0FF4590BB6122EDEF6E3C542D727CC768697734"
"76F7E2B35832976B50A27A282D9B87E44577EB66"
'("alfa@example.net" "alpha@example.net")
(list
(subkey "3B3FBC948FE59301ED629EFB6AE6D7EE46A871F8"
"A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD"))))
(define one (new "289B0EF1D105E124B6F626020EF77096D74C5F22"
"50B2D4FA4122C212611048BC5FC31BD44393626E"
'("one@example.com")
(list
(subkey "EB467DCA4AD7676A6A62B2ABABAB28A247BE2775"
"7E201E28B6FEB2927B321F443205F4724EBE637E"))))
(define two (new "C1DEBB34EA8B71009EAFA474973D50E1C40FDECF"
"343D8AF79796EE107D645A2787A9D9252F924E6F"
'("two@example.com")
(list
(subkey "CD3D0F5701CBFCACB2A4907305A37887B27907AA"
"8B5ABF3EF9EB8D96B91A0B8C2C4401C91C834C34"))))))
(define key-file1 "samplekeys/rsa-rsa-sample-1.asc")
(define key-file2 "samplekeys/ed25519-cv25519-sample-1.asc")
(define plain-files '("plain-1" "plain-2" "plain-3" "plain-large"))
(define data-files '("data-500" "data-9000" "data-32000" "data-80000"))
(define exp-files '())
(define all-files (append plain-files data-files))
(let ((verbose (string->number (getenv "verbose"))))
(if (number? verbose)
(*set-verbose!* verbose)))
(define (qualify executable)
(string-append executable (getenv "EXEEXT")))
(define (getenv' key default)
(let ((value (getenv key)))
(if (string=? "" value)
default
value)))
(define (percent-decode s)
(define (decode c)
(if (and (> (length c) 2) (char=? #\% (car c)))
(integer->char (string->number (string #\# #\x (cadr c) (caddr c))))
#f))
(let loop ((i 0) (c (string->list s)) (r (make-string (string-length s))))
(if (null? c)
(substring r 0 i)
(let ((decoded (decode c)))
(string-set! r i (if decoded decoded (car c)))
(loop (+ 1 i) (if decoded (cdddr c) (cdr c)) r)))))
(assert (equal? (percent-decode "") ""))
(assert (equal? (percent-decode "%61") "a"))
(assert (equal? (percent-decode "foob%61r") "foobar"))
(define (percent-encode s)
(define (encode c)
`(#\% ,@(string->list (number->string (char->integer c) 16))))
(let loop ((acc '()) (cs (reverse (string->list s))))
(if (null? cs)
(list->string acc)
(case (car cs)
((#\: #\%)
(loop (append (encode (car cs)) acc) (cdr cs)))
(else
(loop (cons (car cs) acc) (cdr cs)))))))
(assert (equal? (percent-encode "") ""))
(assert (equal? (percent-encode "%61") "%2561"))
(assert (equal? (percent-encode "foob%61r") "foob%2561r"))
;; Note that the entry for pinentry relies on the fact that
;; GNUPG_BUILD_ROOT is the top of the build root. The second element
-;; in each list is an envvar which can be used to specifiy a different
+;; in each list is an envvar which can be used to specify a different
;; tool than the installed one.
(define tools
'((gpgv "GPGV" "g10/gpgv")
(gpg-connect-agent "GPG_CONNECT_AGENT" "tools/gpg-connect-agent")
(gpgconf "GPGCONF" "tools/gpgconf")
(gpg-preset-passphrase "GPG_PRESET_PASSPHRASE"
"agent/gpg-preset-passphrase")
(gpgtar "GPGTAR" "tools/gpgtar")
(pinentry "PINENTRY" "tests/openpgp/fake-pinentry")))
(define (tool-hardcoded which)
(let ((t (assoc which tools)))
(getenv' (cadr t)
(qualify (string-append (getenv "GNUPG_BUILD_ROOT")
"/" (caddr t))))))
(define with-valgrind? (not (string=? (getenv "with_valgrind") "")))
;; You can splice VALGRIND into your argument vector to run programs
;; under valgrind. For example, to run valgrind on gpg, you may want
;; to redefine gpg:
;;
;; (set! gpg `(,@valgrind ,@gpg))
;;
(define valgrind
'("/usr/bin/valgrind" -q --leak-check=no --track-origins=yes
--error-exitcode=154 --exit-on-first-error=yes))
(define (gpg-conf . args)
(gpg-conf' "" args))
(define (gpg-conf' input args)
(let ((s (call-popen `(,(tool-hardcoded 'gpgconf)
,@(if *win32*
(list '--build-prefix (getenv "objdir"))
'())
,@args) input)))
(map (lambda (line) (map percent-decode (string-split line #\:)))
(string-split-newlines s))))
(define :gc:c:name car)
(define :gc:c:description cadr)
(define :gc:c:pgmname caddr)
(define (:gc:o:name x) (list-ref x 0))
(define (:gc:o:flags x) (string->number (list-ref x 1)))
(define (:gc:o:level x) (string->number (list-ref x 2)))
(define (:gc:o:description x) (list-ref x 3))
(define (:gc:o:type x) (string->number (list-ref x 4)))
(define (:gc:o:alternate-type x) (string->number (list-ref x 5)))
(define (:gc:o:argument-name x) (list-ref x 6))
(define (:gc:o:default-value x) (list-ref x 7))
(define (:gc:o:default-argument x) (list-ref x 8))
(define (:gc:o:value x) (if (< (length x) 10) "" (list-ref x 9)))
(define (gpg-config component key)
(package
(define (value)
(let* ((conf (assoc key (gpg-conf '--list-options component)))
(type (:gc:o:type conf))
(value (:gc:o:value conf)))
(case type
((0 2 3) (string->number value))
((1 32) (substring value 1 (string-length value))))))
(define (update value)
(let ((value' (cond
((string? value) (string-append "\"" value))
((number? value) (number->string value))
(else (throw "Unsupported value" value)))))
(gpg-conf' (string-append key ":0:" (percent-encode value'))
`(--change-options ,component))))
(define (clear)
(gpg-conf' (string-append key ":16:")
`(--change-options ,component)))))
(define gpg-components (apply gpg-conf '(--list-components)))
(define (tool which)
(case which
((gpg gpg-agent scdaemon gpgsm keyboxd dirmngr)
(:gc:c:pgmname (assoc (symbol->string which) gpg-components)))
(else
(tool-hardcoded which))))
(define (gpg-has-option? option)
(string-contains? (call-popen `(,(tool 'gpg) --dump-options) "")
option))
(define have-opt-always-trust
(catch #f
(with-ephemeral-home-directory (lambda ()) (lambda ())
(call-check `(,(tool 'gpg) --gpgconf-test --always-trust)))
#t))
(define GPG `(,(tool 'gpg) --no-permission-warning
,@(if have-opt-always-trust '(--always-trust) '())))
(define GPGV `(,(tool 'gpgv)))
(define PINENTRY (tool 'pinentry))
(define (tr:gpg input args)
(tr:spawn input `(,@GPG --output **out** ,@args **in**)))
(define (tr:gpgstatus input args)
(tr:spawn input `(,@GPG --output dummy --status-file **out** ,@args **in**)))
(define (pipe:gpg args)
(pipe:spawn `(,@GPG --output - ,@args)))
(define (gpg-with-colons args)
(let ((s (call-popen `(,@GPG --with-colons ,@args) "")))
(map (lambda (line) (string-split line #\:))
(string-split-newlines s))))
;; Convenient accessors for the colon output.
(define (:type x) (string->symbol (list-ref x 0)))
(define (:length x) (string->number (list-ref x 2)))
(define (:alg x) (string->number (list-ref x 3)))
(define (:expire x) (list-ref x 6))
(define (:fpr x) (list-ref x 9))
(define (:cap x) (list-ref x 11))
(define (have-public-key? key)
(catch #f
(pair? (filter (lambda (l) (and (equal? 'fpr (:type l))
(equal? key::fpr (:fpr l))))
(gpg-with-colons `(--list-keys ,key::fpr))))))
(define (have-secret-key? key)
(catch #f
(pair? (filter (lambda (l) (and (equal? 'fpr (:type l))
(equal? key::fpr (:fpr l))))
(gpg-with-colons `(--list-secret-keys ,key::fpr))))))
(define (have-secret-key-file? key)
(file-exists? (path-join (getenv "GNUPGHOME") "private-keys-v1.d"
(string-append key::grip ".key"))))
(define (get-config what)
(string-split (caddar (gpg-with-colons `(--list-config ,what))) #\;))
(define all-pubkey-algos (delay (get-config "pubkeyname")))
(define all-hash-algos (delay (get-config "digestname")))
(define all-cipher-algos (delay (get-config "ciphername")))
(define all-compression-algos (delay (get-config "compressname")))
(define (have-pubkey-algo? x)
(not (not (member x (force all-pubkey-algos)))))
(define (have-hash-algo? x)
(not (not (member x (force all-hash-algos)))))
(define (have-cipher-algo? x)
(not (not (member x (force all-cipher-algos)))))
(define (have-compression-algo? x)
(not (not (member x (force all-compression-algos)))))
(define (gpg-pipe args0 args1 errfd)
(lambda (source sink)
(let* ((p (pipe))
(task0 (process-spawn-fd `(,@GPG ,@args0)
source (:write-end p) errfd))
(_ (close (:write-end p)))
(task1 (process-spawn-fd `(,@GPG ,@args1)
(:read-end p) sink errfd)))
(close (:read-end p))
(process-wait task0 #t)
(process-wait task1 #t))))
(setenv "GPG_AGENT_INFO" "" #t)
(setenv "GNUPGHOME" (getcwd) #t)
(define GNUPGHOME (getcwd))
;;
;; GnuPG helper.
;;
;; Call GPG to obtain the hash sums. Either specify an input file in
;; ARGS, or an string in INPUT. Returns a list of (<algo>
;; "<hashsum>") lists.
(define (gpg-hash-string args input)
(map
(lambda (line)
(let ((p (string-split line #\:)))
(list (string->number (cadr p)) (caddr p))))
(string-split-newlines
(call-popen `(,@GPG --with-colons ,@args) input))))
;; Dearmor a file.
(define (dearmor source-name sink-name)
(pipe:do
(pipe:open source-name (logior O_RDONLY O_BINARY))
(pipe:spawn `(,@GPG --dearmor))
(pipe:write-to sink-name (logior O_WRONLY O_CREAT O_BINARY) #o600)))
(define (gpg-dump-packets source-name sink-name)
(pipe:do
(pipe:open source-name (logior O_RDONLY O_BINARY))
(pipe:spawn `(,@GPG --list-packets))
(pipe:write-to sink-name (logior O_WRONLY O_CREAT O_BINARY) #o600)))
;;
;; Support for test environment creation and teardown.
;;
(define (make-test-data filename size)
(call-with-binary-output-file
filename
(lambda (port)
(display (make-random-string size) port))))
(define (create-file name . lines)
(catch #f (unlink name))
(letfd ((fd (open name (logior O_WRONLY O_CREAT O_BINARY) #o600)))
(let ((port (fdopen fd "wb")))
(for-each (lambda (line) (display line port) (newline port))
lines))))
(define (create-gpghome)
(log "Creating test environment...")
(srandom (getpid))
(make-test-data "random_seed" 600)
(log "Creating configuration files")
(if (flag "--use-keyring" *args*)
(create-file "pubring.gpg"))
(create-file "common.conf"
(if (flag "--use-keyboxd" *args*)
"use-keyboxd" "#use-keyboxd")
(string-append "keyboxd-program " (tool 'keyboxd))
)
(create-file "gpg.conf"
;;"log-file socket:///tmp/S.wklog"
;;"verbose"
"no-greeting"
"no-secmem-warning"
"no-permission-warning"
"batch"
"no-auto-key-retrieve"
"no-auto-key-locate"
"allow-weak-digest-algos"
"allow-old-cipher-algos"
"ignore-mdc-error"
(if have-opt-always-trust
"no-auto-check-trustdb" "#no-auto-check-trustdb")
(string-append "agent-program "
(tool 'gpg-agent)
"|--debug-quick-random\n")
)
(create-file "keyboxd.conf"
;;"log-file socket:///tmp/S.wklog"
;;"verbose"
;;"debug ipc"
)
(create-file "gpg-agent.conf"
"allow-preset-passphrase"
"no-grab"
"enable-ssh-support"
"s2k-count 65536"
(string-append "pinentry-program " (tool 'pinentry))
"disable-scdaemon"))
;; Initialize the test environment, install appropriate configuration
;; and start the agent, without any keys.
(define (setup-environment)
(create-gpghome)
(start-agent))
(define (setup-environment-no-atexit)
(create-gpghome)
(start-agent #t))
(define (create-sample-files)
(log "Creating sample data files")
(for-each
(lambda (size)
(make-test-data (string-append "data-" (number->string size))
size))
'(500 9000 32000 80000))
(log "Unpacking samples")
(for-each
(lambda (name)
(dearmor (in-srcdir "tests" "openpgp" (string-append name "o.asc")) name))
plain-files))
(define (create-legacy-gpghome)
(create-sample-files)
(log "Storing private keys")
(for-each
(lambda (name)
(dearmor (in-srcdir "tests" "openpgp" "privkeys" (string-append name ".asc"))
(string-append "private-keys-v1.d/" name ".key")))
'("50B2D4FA4122C212611048BC5FC31BD44393626E"
"7E201E28B6FEB2927B321F443205F4724EBE637E"
"13FDB8809B17C5547779F9D205C45F47CE0217CE"
"343D8AF79796EE107D645A2787A9D9252F924E6F"
"8B5ABF3EF9EB8D96B91A0B8C2C4401C91C834C34"
"0D6F6AD4C4C803B25470F9104E9F4E6A4CA64255"
"FD692BD59D6640A84C8422573D469F84F3B98E53"
"76F7E2B35832976B50A27A282D9B87E44577EB66"
"A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD"
"00FE67F28A52A8AA08FFAED20AF832DA916D1985"
"1DF48228FEFF3EC2481B106E0ACA8C465C662CC5"
"A2832820DC9F40751BDCD375BB0945BA33EC6B4C"
"ADE710D74409777B7729A7653373D820F67892E0"
"CEFC51AF91F68A2904FBFF62C4F075A4785B803F"
"1E28F20E41B54C2D1234D896096495FF57E08D18"
"EB33B687EB8581AB64D04852A54453E85F3DF62D"
"C6A6390E9388CDBAD71EAEA698233FE5E04F001E"
"D69102E0F5AC6B6DB8E4D16DA8E18CF46D88CAE3"))
(for-each
(lambda (name)
(file-copy (in-srcdir "tests" "openpgp" "privkeys"
(string-append name ".key"))
(string-append "private-keys-v1.d/" name ".key")))
'("891067FFFC6D67D37BD4BFC399191C5F3989D1B5"
"F27FC04CB01723A4CB6F5399F7B86CCD82C0169C"))
(log "Importing public demo and test keys")
(for-each
(lambda (file)
(call-check `(,@GPG --yes --import ,(in-srcdir "tests" "openpgp" file))))
(list "pubdemo.asc" "pubring.asc" key-file1))
(pipe:do
(pipe:open (in-srcdir "tests" "openpgp" "pubring.pkr.asc") (logior O_RDONLY O_BINARY))
(pipe:spawn `(,@GPG --dearmor))
(pipe:spawn `(,@GPG --yes --import))))
(define (preset-passphrases)
(log "Presetting passphrases")
;; one@example.com
(call-check `(,(tool 'gpg-preset-passphrase)
--preset --passphrase def
"50B2D4FA4122C212611048BC5FC31BD44393626E"))
(call-check `(,(tool 'gpg-preset-passphrase)
--preset --passphrase def
"7E201E28B6FEB2927B321F443205F4724EBE637E"))
;; alpha@example.net
(call-check `(,(tool 'gpg-preset-passphrase)
--preset --passphrase abc
"76F7E2B35832976B50A27A282D9B87E44577EB66"))
(call-check `(,(tool 'gpg-preset-passphrase)
--preset --passphrase abc
"A0747D5F9425E6664F4FFBEED20FBCA79FDED2BD")))
;; Initialize the test environment, install appropriate configuration
;; and start the agent, with the keys from the legacy test suite.
(define (setup-legacy-environment)
(create-gpghome)
(if (member "--unpack-tarball" *args*)
(begin
(call-check `(,(tool 'gpgtar) --extract --directory=. ,(cadr *args*)))
(start-agent))
(begin
(start-agent)
(create-legacy-gpghome)))
(preset-passphrases))
;; Create the socket dir and start the agent.
(define (start-agent . args)
(log "Starting gpg-agent...")
(let ((gnupghome (getenv "GNUPGHOME")))
(if (null? args)
(atexit (lambda ()
(with-home-directory gnupghome (stop-agent))))))
(catch (log "Warning: Creating socket directory failed:" (car *error*))
(gpg-conf '--create-socketdir))
(call-check `(,(tool 'gpg-connect-agent) --verbose
,(string-append "--agent-program=" (tool 'gpg-agent)
"|--debug-quick-random")
/bye)))
;; Stop the agent and other daemons and remove the socket dir.
(define (stop-agent)
(log "Stopping gpg-agent...")
(gpg-conf '--kill 'all)
(catch (log "Warning: Removing socket directory failed.")
(gpg-conf '--remove-socketdir)))
;; Get the trust level for KEYID. Any remaining arguments are simply
;; passed to GPG.
;;
;; This function only supports keys with a single user id.
(define (gettrust keyid . args)
(let ((trust
(list-ref (assoc "pub" (gpg-with-colons
`(,@args
--list-keys ,keyid))) 1)))
(unless (and (= 1 (string-length trust))
(member (string-ref trust 0) (string->list "oidreqnmfuws-")))
(fail "Bad trust value:" trust))
trust))
;; Check that KEYID's trust level matches EXPECTED-TRUST. Any
;; remaining arguments are simply passed to GPG.
;;
;; This function only supports keys with a single user id.
(define (checktrust keyid expected-trust . args)
(let ((trust (apply gettrust `(,keyid ,@args))))
(unless (string=? trust expected-trust)
(fail keyid ": Expected trust to be" expected-trust
"but got" trust))))
;;
;; Enable checking with valgrind if the envvar "with_valgrind" is set
;;
(when with-valgrind?
(set! gpg `(,@valgrind ,@gpg)))
;;(set! *args* (append *args* (list "--use-keyboxd")))
;; end
diff --git a/tests/openpgp/fake-pinentry.c b/tests/openpgp/fake-pinentry.c
index 18b60574b..b460a1da3 100644
--- a/tests/openpgp/fake-pinentry.c
+++ b/tests/openpgp/fake-pinentry.c
@@ -1,321 +1,321 @@
/* Fake pinentry program for the OpenPGP test suite.
*
* Copyright (C) 2016 g10 code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <errno.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include <unistd.h>
static FILE *log_stream;
static int
reply (const char *fmt, ...)
{
int result;
va_list ap;
if (log_stream)
{
fprintf (log_stream, "> ");
va_start (ap, fmt);
vfprintf (log_stream, fmt, ap);
va_end (ap);
}
va_start (ap, fmt);
result = vprintf (fmt, ap);
va_end (ap);
fflush (stdout);
return result;
}
/* Return the first line from FNAME, removing it from the file. */
static char *
get_passphrase (const char *fname)
{
char *passphrase = NULL;
size_t fname_len;
char *fname_new;
FILE *source, *sink;
char linebuf[80];
fname_len = strlen (fname);
fname_new = malloc (fname_len + 5);
if (fname_new == NULL)
{
perror ("malloc");
exit (1);
}
snprintf (fname_new, fname_len + 5, "%s.new", fname);
source = fopen (fname, "r");
if (! source)
{
perror (fname);
exit (1);
}
sink = fopen (fname_new, "w");
if (! sink)
{
perror (fname_new);
exit (1);
}
while (fgets (linebuf, sizeof linebuf, source))
{
linebuf[sizeof linebuf - 1] = 0;
if (passphrase == NULL)
{
passphrase = strdup (linebuf);
if (passphrase == NULL)
{
perror ("strdup");
exit (1);
}
}
else
fputs (linebuf, sink);
}
if (ferror (source))
{
perror (fname);
exit (1);
}
if (ferror (sink))
{
perror (fname_new);
exit (1);
}
fclose (source);
fclose (sink);
if (remove (fname))
{
fprintf (stderr, "Failed to remove %s: %s",
fname, strerror (errno));
exit (1);
}
if (rename (fname_new, fname))
{
fprintf (stderr, "Failed to rename %s to %s: %s",
fname, fname_new, strerror (errno));
exit (1);
}
free (fname_new);
return passphrase;
}
#define whitespacep(p) (*(p) == ' ' || *(p) == '\t' \
|| *(p) == '\r' || *(p) == '\n')
/* rstrip line. */
static void
rstrip (char *buffer)
{
char *p;
if (!*buffer)
return; /* This is to avoid p = buffer - 1 */
for (p = buffer + strlen (buffer) - 1; p >= buffer; p--)
{
if (! whitespacep (p))
break;
*p = 0;
}
}
/* Skip over options in LINE.
Blanks after the options are also removed. Options are indicated
by two leading dashes followed by a string consisting of non-space
characters. The special option "--" indicates an explicit end of
options; all what follows will not be considered an option. The
first no-option string also indicates the end of option parsing. */
char *
skip_options (const char *line)
{
while (whitespacep (line))
line++;
while (*line == '-' && line[1] == '-')
{
while (*line && !whitespacep (line))
line++;
while (whitespacep (line))
line++;
}
return (char*) line;
}
/* Return a pointer to the argument of the option with NAME. If such
an option is not given, NULL is returned. */
char *
option_value (const char *line, const char *name)
{
char *s;
int n = strlen (name);
s = strstr (line, name);
if (s && s >= skip_options (line))
return NULL;
if (s && (s == line || whitespacep (s-1))
&& s[n] && (whitespacep (s+n) || s[n] == '='))
{
s += n + 1;
s += strspn (s, " ");
if (*s && !whitespacep(s))
return s;
}
return NULL;
}
static int
parse_pinentry_user_data (const char *args,
char **r_passphrase)
{
char *logfile;
char *passphrasefile;
char *passphrase;
*r_passphrase = NULL;
if (log_stream)
fclose (log_stream);
log_stream = NULL;
logfile = option_value (args, "--logfile");
if (logfile)
{
char *p = logfile, more;
while (*p && ! whitespacep (p))
p++;
more = !! *p;
*p = 0;
args = more ? p+1 : p;
log_stream = fopen (logfile, "a");
if (! log_stream)
{
perror (logfile);
return -1;
}
}
passphrasefile = option_value (args, "--passphrasefile");
if (passphrasefile)
{
char *p = passphrasefile, more;
while (*p && ! whitespacep (p))
p++;
more = !! *p;
*p = 0;
args = more ? p+1 : p;
passphrase = get_passphrase (passphrasefile);
if (! passphrase)
{
reply ("# Passphrasefile '%s' is empty. Terminating.\n",
passphrasefile);
return -1;
}
rstrip (passphrase);
}
else
passphrase = strdup (skip_options (args));
*r_passphrase = passphrase;
return 0;
}
int
main (int argc, char **argv)
{
char *passphrase = NULL;
/* We get our options via PINENTRY_USER_DATA. */
(void) argc, (void) argv;
setvbuf (stdin, NULL, _IOLBF, BUFSIZ);
setvbuf (stdout, NULL, _IOLBF, BUFSIZ);
reply ("# fake-pinentry(%u) started.\n", (unsigned int)getpid ());
reply ("OK - what's up?\n");
while (! feof (stdin))
{
char buffer[1024];
if (fgets (buffer, sizeof buffer, stdin) == NULL)
break;
if (log_stream)
fprintf (log_stream, "< %s", buffer);
rstrip (buffer);
#define OPT_USER_DATA "OPTION pinentry-user-data="
if (strncmp (buffer, "GETPIN", 6) == 0)
{
if (passphrase)
reply ("D %s\n", passphrase);
else
- reply ("D deafult\n");
+ reply ("D default\n");
}
else if (strncmp (buffer, "BYE", 3) == 0)
{
reply ("OK\n");
break;
}
else if (strncmp (buffer, OPT_USER_DATA, strlen (OPT_USER_DATA)) == 0)
{
if (parse_pinentry_user_data (buffer + strlen (OPT_USER_DATA),
&passphrase) < 0)
{
/* Failure. */
return 1;
}
}
reply ("OK\n");
}
#undef OPT_USER_DATA
reply ("# Connection terminated.\n");
if (log_stream)
fclose (log_stream);
if (passphrase)
free (passphrase);
return 0;
}
diff --git a/tests/tpm2dtests/all-tests.scm b/tests/tpm2dtests/all-tests.scm
index 8934f01f2..0dcb129b7 100644
--- a/tests/tpm2dtests/all-tests.scm
+++ b/tests/tpm2dtests/all-tests.scm
@@ -1,84 +1,84 @@
;; Copyright (C) 2017 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 <http://www.gnu.org/licenses/>.
(export all-tests
;; Parse the Makefile.am to find all tests.
(load (with-path "makefile.scm"))
(define (expander filename port key)
(parse-makefile port key))
(define (parse filename key)
(parse-makefile-expand filename expander key))
(define setup
(make-environment-cache
(test::scm
#f
#f
(path-join "tests" "tpm2dtests" "setup.scm")
(in-srcdir "tests" "tpm2dtests" "setup.scm"))))
(define (qualify path variant)
(string-append "<" variant ">" path))
(define (setup* variant)
(make-environment-cache
(test::scm
#f
variant
(path-join "tests" "tpm2dtests" "setup.scm")
(in-srcdir "tests" "tpm2dtests" "setup.scm")
(string-append "--" variant))))
(define setup-use-keyring (setup* "use-keyring"))
(define setup-use-keyboxd (setup* "use-keyboxd"))
(define all-tests
(parse-makefile-expand "Makefile"
(lambda (filename port key) (parse-makefile port key))
"XTESTS"))
(define tests
(map (lambda (name)
(test::scm setup
"standards"
(path-join "tests" "tpm2dtests" name)
(in-srcdir "tests" "tpm2dtests" name))) all-tests))
(when *run-all-tests*
(set! tests
(append
tests
;; The second pass uses the keyboxd
(map (lambda (name)
(test::scm setup-use-keyboxd
"keyboxd"
(path-join "tests" "tpm2dtests" name)
(in-srcdir "tests" "tpm2dtests" name)
"--use-keyboxd")) all-tests)
- ;; The third pass uses the legact pubring.gpg
+ ;; The third pass uses the legacy pubring.gpg
(map (lambda (name)
(test::scm setup-use-keyring
"keyring"
(path-join "tests" "tpm2dtests" name)
(in-srcdir "tests" "tpm2dtests" name)
"--use-keyring")) all-tests)
)))
tests)
diff --git a/tools/call-dirmngr.c b/tools/call-dirmngr.c
index d85801530..0401af201 100644
--- a/tools/call-dirmngr.c
+++ b/tools/call-dirmngr.c
@@ -1,426 +1,426 @@
/* call-dirmngr.c - Interact with the Dirmngr.
* Copyright (C) 2016, 2022 g10 Code GmbH
* Copyright (C) 2016 Bundesamt für Sicherheit in der Informationstechnik
*
* This file is part of GnuPG.
*
* This file is free software; you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as
* published by the Free Software Foundation; either version 2.1 of
* the License, or (at your option) any later version.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <time.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#include <assuan.h>
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/asshelp.h"
#include "../common/mbox-util.h"
#include "./call-dirmngr.h"
static struct
{
int verbose;
int debug_ipc;
int autostart;
} opt;
void
set_dirmngr_options (int verbose, int debug_ipc, int autostart)
{
opt.verbose = verbose;
opt.debug_ipc = debug_ipc;
opt.autostart = autostart;
}
/* Connect to the Dirmngr and return an assuan context. */
static gpg_error_t
connect_dirmngr (assuan_context_t *r_ctx)
{
gpg_error_t err;
assuan_context_t ctx;
*r_ctx = NULL;
err = start_new_dirmngr (&ctx,
GPG_ERR_SOURCE_DEFAULT,
NULL,
opt.autostart?ASSHELP_FLAG_AUTOSTART:0,
opt.verbose, opt.debug_ipc,
NULL, NULL);
if (!opt.autostart && gpg_err_code (err) == GPG_ERR_NO_DIRMNGR)
{
static int shown;
if (!shown)
{
shown = 1;
log_info (_("no dirmngr running in this session\n"));
}
}
if (err)
assuan_release (ctx);
else
{
*r_ctx = ctx;
}
return err;
}
/* Parameter structure used with the WKD_GET command. */
struct wkd_get_parm_s
{
estream_t memfp;
};
/* Data callback for the WKD_GET command. */
static gpg_error_t
wkd_get_data_cb (void *opaque, const void *data, size_t datalen)
{
struct wkd_get_parm_s *parm = opaque;
gpg_error_t err = 0;
size_t nwritten;
if (!data)
return 0; /* Ignore END commands. */
if (!parm->memfp)
return 0; /* Data is not required. */
if (es_write (parm->memfp, data, datalen, &nwritten))
err = gpg_error_from_syserror ();
return err;
}
/* Status callback for the WKD_GET command. */
static gpg_error_t
wkd_get_status_cb (void *opaque, const char *line)
{
struct wkd_get_parm_s *parm = opaque;
gpg_error_t err = 0;
const char *s, *s2;
const char *warn = NULL;
int is_note = 0;
(void)parm;
/* Note: The code below is mostly duplicated from g10/call-dirmngr.c */
if ((s = has_leading_keyword (line, "WARNING"))
|| (is_note = !!(s = has_leading_keyword (line, "NOTE"))))
{
if ((s2 = has_leading_keyword (s, "wkd_cached_result")))
{
if (opt.verbose)
warn = _("WKD uses a cached result");
}
else if ((s2 = has_leading_keyword (s, "tor_not_running")))
warn = _("Tor is not running");
else if ((s2 = has_leading_keyword (s, "tor_config_problem")))
warn = _("Tor is not properly configured");
else if ((s2 = has_leading_keyword (s, "dns_config_problem")))
warn = _("DNS is not properly configured");
else if ((s2 = has_leading_keyword (s, "http_redirect")))
warn = _("unacceptable HTTP redirect from server");
else if ((s2 = has_leading_keyword (s, "http_redirect_cleanup")))
warn = _("unacceptable HTTP redirect from server was cleaned up");
else if ((s2 = has_leading_keyword (s, "tls_cert_error")))
warn = _("server uses an invalid certificate");
else
warn = NULL;
if (warn)
{
if (is_note)
log_info (_("Note: %s\n"), warn);
else
log_info (_("WARNING: %s\n"), warn);
if (s2 && opt.verbose)
{
while (*s2 && !spacep (s2))
s2++;
while (*s2 && spacep (s2))
s2++;
if (*s2)
log_info ("(%s)\n", s2);
}
}
}
return err;
}
/* Ask the dirmngr for the submission address of a WKD server for the
* mail address ADDRSPEC. On success the submission address is stored
* at R_ADDRSPEC. */
gpg_error_t
wkd_get_submission_address (const char *addrspec, char **r_addrspec)
{
gpg_error_t err;
assuan_context_t ctx;
struct wkd_get_parm_s parm;
char *line = NULL;
void *vp;
char *buffer = NULL;
char *p;
memset (&parm, 0, sizeof parm);
*r_addrspec = NULL;
err = connect_dirmngr (&ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET --submission-address -- %s", addrspec);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, wkd_get_data_cb, &parm,
NULL, NULL, wkd_get_status_cb, &parm);
if (err)
goto leave;
es_fputc (0, parm.memfp);
if (es_fclose_snatch (parm.memfp, &vp, NULL))
{
err = gpg_error_from_syserror ();
goto leave;
}
buffer = vp;
parm.memfp = NULL;
p = strchr (buffer, '\n');
if (p)
*p = 0;
trim_spaces (buffer);
if (!is_valid_mailbox (buffer))
{
err = gpg_error (GPG_ERR_INV_USER_ID);
goto leave;
}
*r_addrspec = xtrystrdup (buffer);
if (!*r_addrspec)
err = gpg_error_from_syserror ();
leave:
es_free (buffer);
es_fclose (parm.memfp);
xfree (line);
assuan_release (ctx);
return err;
}
/* Ask the dirmngr for the policy flags and return them as an estream
* memory stream. If no policy flags are set, NULL is stored at
* R_BUFFER. */
gpg_error_t
wkd_get_policy_flags (const char *addrspec, estream_t *r_buffer)
{
gpg_error_t err;
assuan_context_t ctx;
struct wkd_get_parm_s parm;
char *line = NULL;
char *buffer = NULL;
memset (&parm, 0, sizeof parm);
*r_buffer = NULL;
err = connect_dirmngr (&ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET --policy-flags -- %s", addrspec);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, wkd_get_data_cb, &parm,
NULL, NULL, wkd_get_status_cb, &parm);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_buffer = parm.memfp;
parm.memfp = 0;
leave:
es_free (buffer);
es_fclose (parm.memfp);
xfree (line);
assuan_release (ctx);
return err;
}
/* Ask the dirmngr for the key for ADDRSPEC. On success a stream with
* the key is stored at R_KEY. */
gpg_error_t
wkd_get_key (const char *addrspec, estream_t *r_key)
{
gpg_error_t err;
assuan_context_t ctx;
struct wkd_get_parm_s parm;
char *line = NULL;
memset (&parm, 0, sizeof parm);
*r_key = NULL;
err = connect_dirmngr (&ctx);
if (err)
return err;
line = es_bsprintf ("WKD_GET -- %s", addrspec);
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = assuan_transact (ctx, line, wkd_get_data_cb, &parm,
NULL, NULL, wkd_get_status_cb, &parm);
if (err)
goto leave;
es_rewind (parm.memfp);
*r_key = parm.memfp;
parm.memfp = NULL;
leave:
es_fclose (parm.memfp);
xfree (line);
assuan_release (ctx);
return err;
}
/* Send the KS_GET command to the dirmngr. The caller provides CB
- * which is called for each key. The callback is called wit a stream
+ * which is called for each key. The callback is called with a stream
* conveying a single key and several other informational parameters.
* DOMAIN restricts the returned keys to this domain. */
gpg_error_t
wkd_dirmngr_ks_get (const char *domain, gpg_error_t cb (estream_t key))
{
gpg_error_t err;
assuan_context_t ctx;
struct wkd_get_parm_s parm;
char *line = NULL;
int any = 0;
memset (&parm, 0, sizeof parm);
err = connect_dirmngr (&ctx);
if (err)
return err;
line = es_bsprintf ("KS_GET --ldap --first %s", domain? domain:"");
if (!line)
{
err = gpg_error_from_syserror ();
goto leave;
}
if (strlen (line) + 2 >= ASSUAN_LINELENGTH)
{
err = gpg_error (GPG_ERR_TOO_LARGE);
goto leave;
}
parm.memfp = es_fopenmem (0, "rwb");
if (!parm.memfp)
{
err = gpg_error_from_syserror ();
goto leave;
}
for (;;)
{
err = assuan_transact (ctx, any? "KS_GET --next" : line,
wkd_get_data_cb, &parm,
NULL, NULL, wkd_get_status_cb, &parm);
if (err)
{
if (gpg_err_code (err) == GPG_ERR_NO_DATA
&& gpg_err_source (err) == GPG_ERR_SOURCE_DIRMNGR)
err = any? 0 : gpg_error (GPG_ERR_NOT_FOUND);
goto leave;
}
any = 1;
es_rewind (parm.memfp);
err = cb (parm.memfp);
if (err)
break;
es_ftruncate (parm.memfp, 0);
}
leave:
es_fclose (parm.memfp);
xfree (line);
assuan_release (ctx);
return err;
}
diff --git a/tools/card-yubikey.c b/tools/card-yubikey.c
index 63d49c762..9700f2813 100644
--- a/tools/card-yubikey.c
+++ b/tools/card-yubikey.c
@@ -1,448 +1,448 @@
/* card-yubikey.c - Yubikey specific functions.
* Copyright (C) 2019 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/tlv.h"
#include "../common/ttyio.h"
#include "gpg-card.h"
/* Object to describe requested interface options. */
struct iface_s {
unsigned int usb:1;
unsigned int nfc:1;
};
/* Bit flags as used by the fields in struct ykapps_s. */
#define YKAPP_USB_SUPPORTED 0x01
#define YKAPP_USB_ENABLED 0x02
#define YKAPP_NFC_SUPPORTED 0x04
#define YKAPP_NFC_ENABLED 0x08
#define YKAPP_SELECTED 0x80 /* Selected by the command. */
/* An object to describe the applications on a Yubikey. Each field
* has 8 bits to hold the above flag values. */
struct ykapps_s {
unsigned int otp:8;
unsigned int u2f:8;
unsigned int opgp:8;
unsigned int piv:8;
unsigned int oath:8;
unsigned int fido2:8;
};
/* Helper to parse an unsigned integer config value consisting of bit
* flags. TAG select the config item and MASK is the mask ORed into
* the value for a set bit. The function modifies YK. */
static gpg_error_t
parse_ul_config_value (struct ykapps_s *yk,
const unsigned char *config, size_t configlen,
int tag, unsigned int mask)
{
const unsigned char *s;
size_t n;
unsigned long ul = 0;
int i;
s = find_tlv (config, configlen, tag, &n);
if (s && n)
{
if (n > sizeof ul)
{
log_error ("too large integer in Yubikey config tag %02x detected\n",
tag);
if (opt.verbose)
log_printhex (config, configlen, "config:");
return gpg_error (GPG_ERR_CARD);
}
for (i=0; i < n; i++)
{
ul <<=8;
ul |= s[i];
}
if (ul & 0x01)
yk->otp |= mask;
if (ul & 0x02)
yk->u2f |= mask;
if (ul & 0x08)
yk->opgp |= mask;
if (ul & 0x10)
yk->piv |= mask;
if (ul & 0x20)
yk->oath |= mask;
if (ul & 0x200)
yk->fido2 |= mask;
}
return 0;
}
/* Create an unsigned integer config value for TAG from the data in YK
* and store it the provided 4 byte buffer RESULT. If ENABLE is true
* the respective APP_SELECTED bit in YK sets the corresponding bit
* flags, it is is false that bit flag is cleared. IF APP_SELECTED is
* not set the bit flag is not changed. */
static void
set_ul_config_value (struct ykapps_s *yk,
unsigned int bitflag, int tag, unsigned int enable,
unsigned char *result)
{
unsigned long ul = 0;
/* First set the current values. */
if ((yk->otp & bitflag))
ul |= 0x01;
if ((yk->u2f & bitflag))
ul |= 0x02;
if ((yk->opgp & bitflag))
ul |= 0x08;
if ((yk->piv & bitflag))
ul |= 0x10;
if ((yk->oath & bitflag))
ul |= 0x20;
if ((yk->fido2 & bitflag))
ul |= 0x200;
/* Then enable or disable the bits according to the selection flag. */
if (enable)
{
if ((yk->otp & YKAPP_SELECTED))
ul |= 0x01;
if ((yk->u2f & YKAPP_SELECTED))
ul |= 0x02;
if ((yk->opgp & YKAPP_SELECTED))
ul |= 0x08;
if ((yk->piv & YKAPP_SELECTED))
ul |= 0x10;
if ((yk->oath & YKAPP_SELECTED))
ul |= 0x20;
if ((yk->fido2 & YKAPP_SELECTED))
ul |= 0x200;
}
else
{
if ((yk->otp & YKAPP_SELECTED))
ul &= ~0x01;
if ((yk->u2f & YKAPP_SELECTED))
ul &= ~0x02;
if ((yk->opgp & YKAPP_SELECTED))
ul &= ~0x08;
if ((yk->piv & YKAPP_SELECTED))
ul &= ~0x10;
if ((yk->oath & YKAPP_SELECTED))
ul &= ~0x20;
if ((yk->fido2 & YKAPP_SELECTED))
ul &= ~0x200;
}
/* Make sure that we do not disable the CCID transport. Without
* CCID we won't have any way to change the configuration again. We
* would instead need one of the other Yubikey tools to enable an
* application and thus its transport again. */
if (bitflag == YKAPP_USB_ENABLED && !(ul & (0x08|0x10|0x20)))
{
log_info ("Enabling PIV to have at least one CCID transport\n");
ul |= 0x10;
}
result[0] = tag;
result[1] = 2;
result[2] = ul >> 8;
result[3] = ul;
}
/* Print the info from YK. */
static void
yk_list (estream_t fp, struct ykapps_s *yk)
{
if (opt.interactive)
tty_fprintf (fp, ("Application USB NFC\n"
"-----------------------\n"));
tty_fprintf (fp, "OTP %s %s\n",
(yk->otp & YKAPP_USB_SUPPORTED)?
(yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->otp & YKAPP_NFC_SUPPORTED)?
(yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "U2F %s %s\n",
(yk->otp & YKAPP_USB_SUPPORTED)?
(yk->otp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->otp & YKAPP_NFC_SUPPORTED)?
(yk->otp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "OPGP %s %s\n",
(yk->opgp & YKAPP_USB_SUPPORTED)?
(yk->opgp & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->opgp & YKAPP_NFC_SUPPORTED)?
(yk->opgp & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "PIV %s %s\n",
(yk->piv & YKAPP_USB_SUPPORTED)?
(yk->piv & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->piv & YKAPP_NFC_SUPPORTED)?
(yk->piv & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "OATH %s %s\n",
(yk->oath & YKAPP_USB_SUPPORTED)?
(yk->oath & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->oath & YKAPP_NFC_SUPPORTED)?
(yk->oath & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
tty_fprintf (fp, "FIDO2 %s %s\n",
(yk->fido2 & YKAPP_USB_SUPPORTED)?
(yk->fido2 & YKAPP_USB_ENABLED? "yes" : "no ") : "- ",
(yk->fido2 & YKAPP_NFC_SUPPORTED)?
(yk->fido2 & YKAPP_NFC_ENABLED? "yes" : "no ") : "- ");
}
/* Enable disable the apps as marked in YK with flag YKAPP_SELECTED. */
static gpg_error_t
yk_enable_disable (struct ykapps_s *yk, struct iface_s *iface,
const unsigned char *config, size_t configlen, int enable)
{
gpg_error_t err = 0;
unsigned char apdu[100];
unsigned int apdulen;
/* const unsigned char *s; */
/* size_t n; */
char *hexapdu = NULL;
apdulen = 0;
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x1c; /* Write Config instruction. */
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x00;
apdu[apdulen++] = 0x00; /* Lc will be fixed up later. */
apdu[apdulen++] = 0x00; /* Length of data will also be fixed up later. */
/* The ykman tool has no way to set NFC and USB flags in one go.
* Reasoning about the Yubikey's firmware it seems plausible that
* combining should work. Let's try it here if the user called for
* setting both interfaces. */
if (iface->nfc)
{
set_ul_config_value (yk, YKAPP_NFC_ENABLED, 0x0e, enable, apdu+apdulen);
apdulen += 4;
}
if (iface->usb)
{
set_ul_config_value (yk, YKAPP_USB_ENABLED, 0x03, enable, apdu+apdulen);
apdulen += 4;
/* Yubikey's ykman copies parts of the config data when writing
* the config for USB. Below is a commented example on how that
* can be done. */
(void)config;
(void)configlen;
/* Copy the device flags. */
/* s = find_tlv (config, configlen, 0x08, &n); */
/* if (s && n) */
/* { */
/* s -= 2; */
/* n += 2; */
/* if (apdulen + n > sizeof apdu) */
/* { */
/* err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT); */
/* goto leave; */
/* } */
/* memcpy (apdu+apdulen, s, n); */
/* apdulen += n; */
/* } */
}
if (iface->nfc || iface->usb)
{
if (apdulen + 2 > sizeof apdu)
{
err = gpg_error (GPG_ERR_BUFFER_TOO_SHORT);
goto leave;
}
/* Disable the next two lines to let the card reboot. Not doing
* this is however more convenient for this tool because further
* commands don't end up with an error. It seems to be better
* that a "reset" command from gpg-card-tool is run at the
* user's discretion. */
/* apdu[apdulen++] = 0x0c; /\* Reboot tag *\/ */
/* apdu[apdulen++] = 0; /\* No data for reboot. *\/ */
/* Fixup the lngth bytes. */
apdu[4] = apdulen - 6 + 1;
apdu[5] = apdulen - 6;
hexapdu = bin2hex (apdu, apdulen, NULL);
if (!hexapdu)
err = gpg_error_from_syserror ();
else
err = send_apdu (hexapdu, "YK.write_config", 0, NULL, NULL);
}
leave:
xfree (hexapdu);
return err;
}
/* Implementation part of cmd_yubikey. ARGV is an array of size ARGc
- * with the argumets given to the yubikey command. Note that ARGV has
+ * with the arguments given to the yubikey command. Note that ARGV has
* no terminating NULL so that ARGC must be considered. FP is the
* stream to output information. This function must only be called on
* Yubikeys. */
gpg_error_t
yubikey_commands (card_info_t info, estream_t fp, int argc, const char *argv[])
{
gpg_error_t err;
enum {ykLIST, ykENABLE, ykDISABLE } cmd;
struct iface_s iface = {0,0};
struct ykapps_s ykapps = {0};
unsigned char *config = NULL;
size_t configlen;
int i;
if (!argc)
return gpg_error (GPG_ERR_SYNTAX);
/* Parse command. */
if (!ascii_strcasecmp (argv[0], "list"))
cmd = ykLIST;
else if (!ascii_strcasecmp (argv[0], "enable"))
cmd = ykENABLE;
else if (!ascii_strcasecmp (argv[0], "disable"))
cmd = ykDISABLE;
else
{
log_info ("Please use \"%s\" to list the available sub-commands\n",
"help yubikey");
err = gpg_error (GPG_ERR_UNKNOWN_COMMAND);
goto leave;
}
if (info->cardversion < 0x050000 && cmd != ykLIST)
{
log_info ("Sub-command '%s' is only support by Yubikey-5 and later\n",
argv[0]);
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Parse interface if needed. */
if (cmd == ykLIST)
iface.usb = iface.nfc = 1;
else if (argc < 2)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
else if (!ascii_strcasecmp (argv[1], "usb"))
iface.usb = 1;
else if (!ascii_strcasecmp (argv[1], "nfc"))
iface.nfc = 1;
else if (!ascii_strcasecmp (argv[1], "all") || !strcmp (argv[1], "*"))
iface.usb = iface.nfc = 1;
else
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* Parse list of applications. */
for (i=2; i < argc; i++)
{
if (!ascii_strcasecmp (argv[i], "otp"))
ykapps.otp = 0x80;
else if (!ascii_strcasecmp (argv[i], "u2f"))
ykapps.u2f = 0x80;
else if (!ascii_strcasecmp (argv[i], "opgp")
||!ascii_strcasecmp (argv[i], "openpgp"))
ykapps.opgp = 0x80;
else if (!ascii_strcasecmp (argv[i], "piv"))
ykapps.piv = 0x80;
else if (!ascii_strcasecmp (argv[i], "oath")
|| !ascii_strcasecmp (argv[i], "oauth"))
ykapps.oath = 0x80;
else if (!ascii_strcasecmp (argv[i], "fido2"))
ykapps.fido2 = 0x80;
else if (!ascii_strcasecmp (argv[i], "all")|| !strcmp (argv[i], "*"))
{
ykapps.otp = ykapps.u2f = ykapps.opgp = ykapps.piv = ykapps.oath
= ykapps.fido2 = 0x80;
}
else
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
}
/* Select the Yubikey Manager application. */
err = send_apdu ("00A4040008a000000527471117", "Select.YK-Manager", 0,
NULL, NULL);
if (err)
goto leave;
/* Send the read config command. */
err = send_apdu ("001D000000", "YK.read_config", 0, &config, &configlen);
if (err)
goto leave;
if (!configlen || *config > configlen - 1)
{
/* The length byte is shorter than the actual length. */
log_error ("Yubikey returned improper config data\n");
log_printhex (config, configlen, "config:");
err = gpg_error (GPG_ERR_CARD);
goto leave;
}
if (configlen-1 > *config)
{
log_info ("Extra config data ignored\n");
log_printhex (config, configlen, "config:");
}
configlen = *config;
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x01, YKAPP_USB_SUPPORTED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x03, YKAPP_USB_ENABLED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x0d, YKAPP_NFC_SUPPORTED);
if (!err)
err = parse_ul_config_value (&ykapps, config+1, configlen,
0x0e, YKAPP_NFC_ENABLED);
if (err)
goto leave;
switch (cmd)
{
case ykLIST: yk_list (fp, &ykapps); break;
case ykENABLE: err = yk_enable_disable (&ykapps, &iface,
config+1, configlen, 1); break;
case ykDISABLE: err = yk_enable_disable (&ykapps, &iface,
config+1, configlen, 0); break;
}
leave:
xfree (config);
return err;
}
diff --git a/tools/gpg-authcode-sign.sh b/tools/gpg-authcode-sign.sh
index 56b743b94..cdf8975e4 100644
--- a/tools/gpg-authcode-sign.sh
+++ b/tools/gpg-authcode-sign.sh
@@ -1,257 +1,257 @@
#!/bin/sh
# gpg-authcode-sign.sh - Wrapper for osslsigncode
# Copyright (C) 2024 g10 Code GmbH
#
# This file is free software; as a special exception the author gives
# unlimited permission to copy and/or distribute it, with or without
# modifications, as long as this notice is preserved.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY, to the extent permitted by law; without even the
# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
VERSION=2024-03-25
PGM=gpg-authcode-sign.sh
set -e
usage()
{
cat <<EOF
Usage: $PGM [OPTIONS] FILE_TO_SIGN SIGNED_FILE
Options:
[--desc=STRING] Include STRING as description (default=$desc)
[--url=STRING] Include STRING as URL (default=$url)
[--stamp] Use a stamp file to avoid double signing
[--dry-run] Do not actually run osslsigncode
[--template] Print a template for ~/.gnupg-autogenrc
[--version] Print version and exit
EOF
exit $1
}
# The information required to sign the tarballs and binaries
# are expected in the developer specific file ~/.gnupg-autogen.rc".
# Here is an example:
print_autogenrc_template()
{
cat <<EOF
# Location of the released tarball archives. Note that this is an
# internal archive and before uploading this to the public server,
# manual tests should be run and the git release tagged and pushed.
# This is greped by the Makefile.
RELEASE_ARCHIVE=foo@somehost:tarball-archive
# The key used to sign the GnuPG sources.
# This is greped by the Makefile.
RELEASE_SIGNKEY=6DAA6E64A76D2840571B4902528897B826403ADA
# The key used to sign the VERSION files of some MSI installers.
VERSION_SIGNKEY=02F38DFF731FF97CB039A1DA549E695E905BA208
# For signing Windows binaries we need to employ a Windows machine.
# We connect to this machine via ssh and take the connection
# parameters via .ssh/config. For example a VM could be specified
# like this:
#
# Host authenticode-signhost
# HostName localhost
# Port 27042
# User gpgsign
#
# Depending on the used token it might be necessary to allow single
# signon and unlock the token before running the make. The following
# variable references this entry. This is greped by the Makefile.
# To enable this use authenticode-signhost as value.
AUTHENTICODE_SIGNHOST=
# The name of the signtool as used on Windows.
# This is greped by the Makefile.
AUTHENTICODE_TOOL="C:\Program Files (x86)\Windows Kits\10\bin\signtool.exe"
# The URL for the timestamping service
AUTHENTICODE_TSURL=http://rfc3161timestamp.globalsign.com/advanced
-# To use osslsigncode the follwing entries are required and
+# To use osslsigncode the following entries are required and
# an empty string must be given for AUTHENTICODE_SIGNHOST.
# They are greped by the Makefile. For example:
#AUTHENTICODE_KEY=/home/foo/.gnupg/my-authenticode-key.p12
#AUTHENTICODE_CERTS=/home/foo/.gnupg/my-authenticode-certs.pem
# If a smartcard is used for the Authenticode signature these
# entries are required instead (remove comment).
#AUTHENTICODE_KEY=card
AUTHENTICODE_CERTS=/home/foo/.gnupg/my_authenticode_cert.pem
OSSLSIGNCODE=/usr/bin/osslsigncode
OSSLPKCS11ENGINE=/usr/lib/x86_64-linux-gnu/engines-1.1/pkcs11.so
SCUTEMODULE=/usr/local/lib/scute.so
# Signing can also be disabled:
AUTHENTICODE_KEY=none
#
EOF
}
autogenrc="$HOME/.gnupg-autogen.rc"
dryrun=
stamp=
buildtype=
-# Set defaults accrding to our build system.
+# Set defaults according to our build system.
if [ -n "$abs_top_srcdir" -a -f "$abs_top_srcdir/packages/BUILDTYPE" ]; then
buildtype=$(cat "$abs_top_srcdir/packages/BUILDTYPE")
elif [ -f "../packages/BUILDTYPE" ]; then
buildtype=$(cat "../packages/BUILDTYPE")
elif [ -f "packages/BUILDTYPE" ]; then
buildtype=$(cat "packages/BUILDTYPE")
fi
case "$buildtype" in
vsd)
desc="GnuPG VS-Desktop"
url="https://gnupg.com"
;;
gpd)
desc="GnuPG Desktop"
url="https://gnupg.com"
;;
default|gpg4win)
desc="Gpg4win"
url="https://gpg4win.org"
;;
*)
desc="GnuPG"
url="https://gnupg.org"
;;
esac
while [ $# -gt 0 ]; do
case "$1" in
--*=*)
optarg=`echo "$1" | sed 's/[-_a-zA-Z0-9]*=//'`
;;
*)
optarg=""
;;
esac
case $1 in
--desc=*)
desc="$optarg"
;;
--url=*)
url="$optarg"
;;
--dry-run|-n)
dryrun=yes
;;
--stamp)
stamp=yes
;;
--help|-h)
usage 0
;;
--version)
echo $VERSION
exit 0
;;
--template)
print_autogenrc_template
exit 0
;;
--*)
usage 1 1>&2
;;
*)
break
;;
esac
shift
done
if [ $# -ne 2 ]; then
usage 1 1>&2
fi
inname="$1"
outname="$2"
shift
if [ ! -f $autogenrc ]; then
echo >&2 "$PGM: error: '$autogenrc' missing"
echo >&2 "$PGM: hint: use option --template"
exit 1
fi
for v in AUTHENTICODE_SIGNHOST AUTHENTICODE_TOOL AUTHENTICODE_TSURL \
AUTHENTICODE_KEY AUTHENTICODE_CERTS VERSION_SIGNKEY \
OSSLSIGNCODE OSSLPKCS11ENGINE SCUTEMODULE ; do
eval $v=$(grep '^[[:blank:]]*'$v'[[:blank:]]*=' "$autogenrc"|cut -d= -f2\
|sed -e 's,\\,\\\\,g'| sed -e 's,^",'\', -e 's,"$,'\',)
done
if [ "$stamp" = yes ]; then
if [ "$outname.asig-done" -nt "$outname" ]; then
echo >&2 "$PGM: file is '$outname' is already signed"
exit 0
fi
fi
if [ -n "$dryrun" ]; then
echo >&2 "$PGM: would sign: '$inname' to '$outname'"
elif [ -n "$AUTHENTICODE_SIGNHOST" ]; then
echo >&2 "$PGM: Signing via host $AUTHENTICODE_SIGNHOST"
scp "$inname" "$AUTHENTICODE_SIGNHOST:a.exe"
# Invoke command on Windows via ssh
ssh "$AUTHENTICODE_SIGNHOST" \""$AUTHENTICODE_TOOL"\" sign \
/v /sm \
/a /n '"g10 Code GmbH"' \
/tr \""$AUTHENTICODE_TSURL"\" /td sha256 \
/d \""$desc"\" \
/fd sha256 /du https://gnupg.com a.exe
scp "$AUTHENTICODE_SIGNHOST:a.exe" "$outname"
elif [ "$AUTHENTICODE_KEY" = card ]; then
echo >&2 "$PGM: Signing using a card: '$inname'"
"$OSSLSIGNCODE" sign \
-pkcs11engine "$OSSLPKCS11ENGINE" \
-pkcs11module "$SCUTEMODULE" \
-certs "$AUTHENTICODE_CERTS" \
-h sha256 -n "$desc" -i "$url" \
-ts "$AUTHENTICODE_TSURL" \
-in "$inname" -out "$outname.tmp"
cp "$outname.tmp" "$outname"
rm "$outname.tmp"
elif [ "$AUTHENTICODE_KEY" = none ]; then
echo >&2 "$PGM: Signing disabled; would sign: '$inname'"
[ "$inname" != "$outname" ] && cp "$inname" "$outname"
else
echo >&2 "$PGM: Signing using key $AUTHENTICODE_KEY"
osslsigncode sign -certs "$AUTHENTICODE_CERTS" \
-pkcs12 "$AUTHENTICODE_KEY" -askpass \
-ts "$AUTHENTICODE_TSURL" \
-h sha256 -n "$desc" -i "$url" \
-in "$inname" -out "$outname.tmp"
cp "$outname.tmp" "$outname"
rm "$outname.tmp"
fi
if [ -z "$dryrun" ]; then
[ "$stamp" = yes ] && touch "$outname.asig-done"
echo >&2 "$PGM: signed file is '$outname'"
fi
# eof
diff --git a/tools/gpg-card.c b/tools/gpg-card.c
index 3d66033bf..f117330f8 100644
--- a/tools/gpg-card.c
+++ b/tools/gpg-card.c
@@ -1,4420 +1,4420 @@
/* gpg-card.c - An interactive tool to work with cards.
* Copyright (C) 2019--2022 g10 Code GmbH
*
* This file is part of GnuPG.
*
* This file 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.
*
* This file is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#ifdef HAVE_LIBREADLINE
# define GNUPG_LIBREADLINE_H_INCLUDED
# include <readline/readline.h>
#endif /*HAVE_LIBREADLINE*/
#define INCLUDED_BY_MAIN_MODULE 1
#include "../common/util.h"
#include "../common/status.h"
#include "../common/i18n.h"
#include "../common/init.h"
#include "../common/sysutils.h"
#include "../common/asshelp.h"
#include "../common/userids.h"
#include "../common/ccparray.h"
#include "../common/exectool.h"
#include "../common/exechelp.h"
#include "../common/ttyio.h"
#include "../common/server-help.h"
#include "../common/openpgpdefs.h"
#include "../common/tlv.h"
#include "../common/comopt.h"
#include "gpg-card.h"
#define CONTROL_D ('D' - 'A' + 1)
#define HISTORYNAME ".gpg-card_history"
/* Constants to identify the commands and options. */
enum opt_values
{
aNull = 0,
oQuiet = 'q',
oVerbose = 'v',
oDebug = 500,
oGpgProgram,
oGpgsmProgram,
oStatusFD,
oWithColons,
oNoAutostart,
oAgentProgram,
oDisplay,
oTTYname,
oTTYtype,
oXauthority,
oLCctype,
oLCmessages,
oNoKeyLookup,
oNoHistory,
oChUid,
oDummy
};
/* The list of commands and options. */
static gpgrt_opt_t opts[] = {
ARGPARSE_group (301, ("@\nOptions:\n ")),
ARGPARSE_s_n (oVerbose, "verbose", ("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", ("be somewhat more quiet")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_s (oGpgsmProgram, "gpgsm", "@"),
ARGPARSE_s_i (oStatusFD, "status-fd", N_("|FD|write status info to this FD")),
ARGPARSE_s_n (oWithColons, "with-colons", "@"),
ARGPARSE_s_n (oNoAutostart, "no-autostart", "@"),
ARGPARSE_s_s (oAgentProgram, "agent-program", "@"),
ARGPARSE_s_s (oDisplay, "display", "@"),
ARGPARSE_s_s (oTTYname, "ttyname", "@"),
ARGPARSE_s_s (oTTYtype, "ttytype", "@"),
ARGPARSE_s_s (oXauthority, "xauthority", "@"),
ARGPARSE_s_s (oLCctype, "lc-ctype", "@"),
ARGPARSE_s_s (oLCmessages, "lc-messages","@"),
ARGPARSE_s_n (oNoKeyLookup,"no-key-lookup",
"use --no-key-lookup for \"list\""),
ARGPARSE_s_n (oNoHistory,"no-history",
"do not use the command history file"),
ARGPARSE_s_s (oChUid, "chuid", "@"),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_IPC_VALUE , "ipc" },
{ DBG_EXTPROG_VALUE, "extprog" },
{ 0, NULL }
};
/* An object to create lists of labels and keyrefs. */
struct keyinfolabel_s
{
const char *label;
const char *keyref;
};
typedef struct keyinfolabel_s *keyinfolabel_t;
/* Helper for --chuid. */
static const char *changeuser;
/* Limit of size of data we read from a file for certain commands. */
#define MAX_GET_DATA_FROM_FILE 16384
/* Constants for OpenPGP cards. */
#define OPENPGP_USER_PIN_DEFAULT "123456"
#define OPENPGP_ADMIN_PIN_DEFAULT "12345678"
#define OPENPGP_KDF_DATA_LENGTH_MIN 90
#define OPENPGP_KDF_DATA_LENGTH_MAX 110
/* Local prototypes. */
static void show_keysize_warning (void);
static gpg_error_t dispatch_command (card_info_t info, const char *command);
static void interactive_loop (void);
#ifdef HAVE_LIBREADLINE
static char **command_completion (const char *text, int start, int end);
#endif /*HAVE_LIBREADLINE*/
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "gpg-card"; break;
case 12: p = "@GNUPG@"; break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = ("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = ("Usage: gpg-card"
" [options] [{[--] command [args]}] (-h for help)");
break;
case 41:
p = ("Syntax: gpg-card"
" [options] [command [args] {-- command [args]}]\n\n"
"Tool to manage cards and tokens. Without a command an interactive\n"
"mode is used. Use command \"help\" to list all commands.");
break;
default: p = NULL; break;
}
return p;
}
static void
set_opt_session_env (const char *name, const char *value)
{
gpg_error_t err;
err = session_env_setenv (opt.session_env, name, value);
if (err)
log_fatal ("error setting session environment: %s\n",
gpg_strerror (err));
}
/* Command line parsing. */
static void
parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts)
{
while (gpgrt_argparse (NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs->r.ret_str, &opt.debug, debug_flags))
{
pargs->r_opt = ARGPARSE_INVALID_ARG;
pargs->err = ARGPARSE_PRINT_ERROR;
}
break;
case oGpgProgram:
opt.gpg_program = make_filename (pargs->r.ret_str, NULL);
break;
case oGpgsmProgram:
opt.gpgsm_program = make_filename (pargs->r.ret_str, NULL);
break;
case oAgentProgram:
opt.agent_program = make_filename (pargs->r.ret_str, NULL);
break;
case oStatusFD:
gnupg_set_status_fd (translate_sys2libc_fd_int (pargs->r.ret_int, 1));
break;
case oWithColons: opt.with_colons = 1; break;
case oNoAutostart: opt.autostart = 0; break;
case oDisplay: set_opt_session_env ("DISPLAY", pargs->r.ret_str); break;
case oTTYname: set_opt_session_env ("GPG_TTY", pargs->r.ret_str); break;
case oTTYtype: set_opt_session_env ("TERM", pargs->r.ret_str); break;
case oXauthority: set_opt_session_env ("XAUTHORITY",
pargs->r.ret_str); break;
case oLCctype: opt.lc_ctype = pargs->r.ret_str; break;
case oLCmessages: opt.lc_messages = pargs->r.ret_str; break;
case oNoKeyLookup: opt.no_key_lookup = 1; break;
case oNoHistory: opt.no_history = 1; break;
case oChUid: changeuser = pargs->r.ret_str; break;
default: pargs->err = 2; break;
}
}
}
/* gpg-card main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
gpgrt_argparse_t pargs;
char **command_list = NULL;
int cmdidx;
char *command;
gnupg_reopen_std ("gpg-card");
gpgrt_set_strusage (my_strusage);
gnupg_rl_initialize ();
log_set_prefix ("gpg-card", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
setup_libassuan_logging (&opt.debug, NULL);
/* Setup default options. */
opt.autostart = 1;
opt.session_env = session_env_new ();
if (!opt.session_env)
log_fatal ("error allocating session environment block: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (changeuser && gnupg_chuid (changeuser, 0))
log_inc_errorcount (); /* Force later termination. */
if (log_get_errorcount (0))
exit (2);
/* Process common component options. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
if (parse_comopt (GNUPG_MODULE_NAME_CARD, opt.debug))
{
gnupg_status_printf (STATUS_FAILURE, "option-parser %u",
gpg_error (GPG_ERR_GENERAL));
exit(2);
}
if (comopt.no_autostart)
opt.autostart = 0;
/* Set defaults for non given options. */
if (!opt.gpg_program)
opt.gpg_program = xstrdup (gnupg_module_name (GNUPG_MODULE_NAME_GPG));
if (!opt.gpgsm_program)
opt.gpgsm_program = xstrdup (gnupg_module_name (GNUPG_MODULE_NAME_GPGSM));
/* Now build the list of commands. We guess the size of the array
* by assuming each item is a complete command. Obviously this will
* be rarely the case, but it is less code to allocate a possible
* too large array. */
command_list = xcalloc (argc+1, sizeof *command_list);
cmdidx = 0;
command = NULL;
while (argc)
{
for ( ; argc && strcmp (*argv, "--"); argc--, argv++)
{
if (!command)
command = xstrdup (*argv);
else
{
char *tmp = xstrconcat (command, " ", *argv, NULL);
xfree (command);
command = tmp;
}
}
if (argc)
{ /* Skip the double dash. */
argc--;
argv++;
}
if (command)
{
command_list[cmdidx++] = command;
command = NULL;
}
}
opt.interactive = !cmdidx;
if (!opt.interactive)
opt.no_history = 1;
if (opt.interactive)
{
interactive_loop ();
err = 0;
}
else
{
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
err = 0;
for (cmdidx=0; (command = command_list[cmdidx]); cmdidx++)
{
err = dispatch_command (info, command);
if (err)
break;
}
if (gpg_err_code (err) == GPG_ERR_EOF)
err = 0; /* This was a "quit". */
else if (command && !opt.quiet)
log_info ("stopped at command '%s'\n", command);
}
flush_keyblock_cache ();
if (command_list)
{
for (cmdidx=0; command_list[cmdidx]; cmdidx++)
xfree (command_list[cmdidx]);
xfree (command_list);
}
if (err)
gnupg_status_printf (STATUS_FAILURE, "- %u", err);
else if (log_get_errorcount (0))
gnupg_status_printf (STATUS_FAILURE, "- %u", GPG_ERR_GENERAL);
else
gnupg_status_printf (STATUS_SUCCESS, NULL);
return log_get_errorcount (0)? 1:0;
}
/* Return S or the string "[none]" if S is NULL. */
static GPGRT_INLINE const char *
nullnone (const char *s)
{
return s? s: "[none]";
}
/* Read data from file FNAME up to MAX_GET_DATA_FROM_FILE characters.
* On error return an error code and stores NULL at R_BUFFER; on
* success returns 0 and stores the number of bytes read at R_BUFLEN
* and the address of a newly allocated buffer at R_BUFFER. A
* complementary nul byte is always appended to the data but not
* counted; this allows one to pass NULL for R-BUFFER and consider the
* returned data as a string. */
static gpg_error_t
get_data_from_file (const char *fname, char **r_buffer, size_t *r_buflen)
{
gpg_error_t err;
estream_t fp;
char *data;
int n;
*r_buffer = NULL;
if (r_buflen)
*r_buflen = 0;
fp = es_fopen (fname, "rb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't open '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
data = xtrymalloc (MAX_GET_DATA_FROM_FILE);
if (!data)
{
err = gpg_error_from_syserror ();
log_error (_("error allocating enough memory: %s\n"), gpg_strerror (err));
es_fclose (fp);
return err;
}
n = es_fread (data, 1, MAX_GET_DATA_FROM_FILE - 1, fp);
es_fclose (fp);
if (n < 0)
{
err = gpg_error_from_syserror ();
tty_printf (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
xfree (data);
return err;
}
data[n] = 0;
*r_buffer = data;
if (r_buflen)
*r_buflen = n;
return 0;
}
/* Fixup the ENODEV error from scdaemon which we may see after
* removing a card due to scdaemon scanning for readers with cards.
* We also map the CAERD REMOVED error to the more useful CARD_NOT
* PRESENT. */
static gpg_error_t
fixup_scd_errors (gpg_error_t err)
{
if ((gpg_err_code (err) == GPG_ERR_ENODEV
|| gpg_err_code (err) == GPG_ERR_CARD_REMOVED)
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
err = gpg_error (GPG_ERR_CARD_NOT_PRESENT);
return err;
}
/* Set the card removed flag from INFO depending on ERR. This does
* not clear the flag. */
static gpg_error_t
maybe_set_card_removed (card_info_t info, gpg_error_t err)
{
if ((gpg_err_code (err) == GPG_ERR_ENODEV
|| gpg_err_code (err) == GPG_ERR_CARD_REMOVED)
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
info->card_removed = 1;
return err;
}
/* Write LENGTH bytes from BUFFER to file FNAME. Return 0 on
* success. */
static gpg_error_t
put_data_to_file (const char *fname, const void *buffer, size_t length)
{
gpg_error_t err;
estream_t fp;
fp = es_fopen (fname, "wb");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error (_("can't create '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
if (length && es_fwrite (buffer, length, 1, fp) != 1)
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
es_fclose (fp);
return err;
}
if (es_fclose (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error writing '%s': %s\n"), fname, gpg_strerror (err));
return err;
}
return 0;
}
/* Return a malloced string with the number opf the menu PROMPT.
* Control-D is mapped to "Q". */
static char *
get_selection (const char *prompt)
{
char *answer;
tty_printf ("\n");
tty_printf ("%s", prompt);
tty_printf ("\n");
answer = tty_get (_("Your selection? "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
strcpy (answer, "q");
return answer;
}
/* Simply prints TEXT to the output. Returns 0 as a convenience.
* This is a separate function so that it can be extended to run
* less(1) or so. The extra arguments are int values terminated by a
* 0 to indicate card application types supported with this command.
* If none are given (just the final 0), this is a general
* command. */
static gpg_error_t
print_help (const char *text, ...)
{
estream_t fp;
va_list arg_ptr;
int value;
int any = 0;
fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "%s\n", text);
va_start (arg_ptr, text);
while ((value = va_arg (arg_ptr, int)))
{
if (!any)
tty_fprintf (fp, "[Supported by: ");
tty_fprintf (fp, "%s%s", any?", ":"", app_type_string (value));
any = 1;
}
if (any)
tty_fprintf (fp, "]\n");
va_end (arg_ptr);
return 0;
}
/* Print an (OpenPGP) fingerprint. */
static void
print_shax_fpr (estream_t fp, const unsigned char *fpr, unsigned int fprlen)
{
int i;
if (fpr)
{
for (i=0; i < fprlen ; i++, fpr++)
tty_fprintf (fp, "%02X", *fpr);
}
else
tty_fprintf (fp, " [none]");
tty_fprintf (fp, "\n");
}
/* Print the keygrip GRP. */
static void
print_keygrip (estream_t fp, const unsigned char *grp, int with_lf)
{
int i;
for (i=0; i < 20 ; i++, grp++)
tty_fprintf (fp, "%02X", *grp);
if (with_lf)
tty_fprintf (fp, "\n");
}
/* Print a string but avoid printing control characters. */
static void
print_string (estream_t fp, const char *text, const char *name)
{
tty_fprintf (fp, "%s", text);
/* FIXME: tty_printf_utf8_string2 eats everything after and
including an @ - e.g. when printing an url. */
if (name && *name)
{
if (fp)
print_utf8_buffer2 (fp, name, strlen (name), '\n');
else
tty_print_utf8_string2 (NULL, name, strlen (name), 0);
}
else
tty_fprintf (fp, _("[not set]"));
tty_fprintf (fp, "\n");
}
/* Print an ISO formatted name or "[not set]". */
static void
print_isoname (estream_t fp, const char *name)
{
if (name && *name)
{
char *p, *given, *buf;
buf = xstrdup (name);
given = strstr (buf, "<<");
for (p=buf; *p; p++)
if (*p == '<')
*p = ' ';
if (given && given[2])
{
*given = 0;
given += 2;
if (fp)
print_utf8_buffer2 (fp, given, strlen (given), '\n');
else
tty_print_utf8_string2 (NULL, given, strlen (given), 0);
if (*buf)
tty_fprintf (fp, " ");
}
if (fp)
print_utf8_buffer2 (fp, buf, strlen (buf), '\n');
else
tty_print_utf8_string2 (NULL, buf, strlen (buf), 0);
xfree (buf);
}
else
{
tty_fprintf (fp, _("[not set]"));
}
tty_fprintf (fp, "\n");
}
/* Return true if the buffer MEM of length memlen consists only of zeroes. */
static int
mem_is_zero (const char *mem, unsigned int memlen)
{
int i;
for (i=0; i < memlen && !mem[i]; i++)
;
return (i == memlen);
}
/* Helper to list a single keyref. LABEL_KEYREF is a fallback key
* reference if no info is available; it may be NULL. */
static void
list_one_kinfo (card_info_t info, key_info_t kinfo,
const char *label_keyref, estream_t fp, int no_key_lookup,
int create_shadow)
{
gpg_error_t err;
key_info_t firstkinfo = info->kinfo;
keyblock_t keyblock = NULL;
keyblock_t kb;
pubkey_t pubkey;
userid_t uid;
key_info_t ki;
const char *s;
gcry_sexp_t s_pkey;
int any;
if (firstkinfo && kinfo)
{
tty_fprintf (fp, " ");
if (mem_is_zero (kinfo->grip, sizeof kinfo->grip))
{
tty_fprintf (fp, "[none]\n");
tty_fprintf (fp, " keyref .....: %s\n", kinfo->keyref);
if (kinfo->label)
tty_fprintf (fp, " label ......: %s\n", kinfo->label);
tty_fprintf (fp, " algorithm ..: %s\n",
nullnone (kinfo->keyalgo));
goto leave;
}
print_keygrip (fp, kinfo->grip, 1);
tty_fprintf (fp, " keyref .....: %s", kinfo->keyref);
if (kinfo->usage)
{
any = 0;
tty_fprintf (fp, " (");
if ((kinfo->usage & GCRY_PK_USAGE_SIGN))
{ tty_fprintf (fp, "sign"); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_CERT))
{ tty_fprintf (fp, "%scert", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_AUTH))
{ tty_fprintf (fp, "%sauth", any?",":""); any=1; }
if ((kinfo->usage & GCRY_PK_USAGE_ENCR))
{ tty_fprintf (fp, "%sencr", any?",":""); any=1; }
tty_fprintf (fp, ")");
}
tty_fprintf (fp, "\n");
if (kinfo->label)
tty_fprintf (fp, " label ......: %s\n", kinfo->label);
if (!(err = scd_readkey (kinfo->keyref, create_shadow, &s_pkey)))
{
char *tmp = pubkey_algo_string (s_pkey, NULL);
tty_fprintf (fp, " algorithm ..: %s\n", nullnone (tmp));
xfree (tmp);
gcry_sexp_release (s_pkey);
s_pkey = NULL;
}
else
{
maybe_set_card_removed (info, err);
tty_fprintf (fp, " algorithm ..: %s\n",
nullnone (kinfo->keyalgo));
}
if (kinfo->fprlen && kinfo->created)
{
tty_fprintf (fp, " stored fpr .: ");
print_shax_fpr (fp, kinfo->fpr, kinfo->fprlen);
tty_fprintf (fp, " created ....: %s\n",
isotimestamp (kinfo->created));
}
if (no_key_lookup)
err = 0;
else
err = get_matching_keys (kinfo->grip,
(GNUPG_PROTOCOL_OPENPGP | GNUPG_PROTOCOL_CMS),
&keyblock);
if (err)
{
if (gpg_err_code (err) != GPG_ERR_NO_PUBKEY)
tty_fprintf (fp, " used for ...: [%s]\n", gpg_strerror (err));
goto leave;
}
for (kb = keyblock; kb; kb = kb->next)
{
tty_fprintf (fp, " used for ...: %s\n",
kb->protocol == GNUPG_PROTOCOL_OPENPGP? "OpenPGP" :
kb->protocol == GNUPG_PROTOCOL_CMS? "X.509" : "?");
pubkey = kb->keys;
if (kb->protocol == GNUPG_PROTOCOL_OPENPGP)
{
/* If this is not the primary key print the primary
* key's fingerprint or a reference to it. */
tty_fprintf (fp, " main key .: ");
for (ki=firstkinfo; ki; ki = ki->next)
if (pubkey->grip_valid
&& !memcmp (ki->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (ki)
{
/* Fixme: Replace mapping by a table lookup. */
if (!memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
s = "this";
else if (!strcmp (ki->keyref, "OPENPGP.1"))
s = "Signature key";
else if (!strcmp (ki->keyref, "OPENPGP.2"))
s = "Encryption key";
else if (!strcmp (ki->keyref, "OPENPGP.3"))
s = "Authentication key";
else
s = NULL;
if (s)
tty_fprintf (fp, "<%s>\n", s);
else
tty_fprintf (fp, "<Key %s>\n", ki->keyref);
}
else /* Print the primary key as fallback. */
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
}
if (kb->protocol == GNUPG_PROTOCOL_OPENPGP
|| kb->protocol == GNUPG_PROTOCOL_CMS)
{
/* Find the primary or subkey of that key. */
for (; pubkey; pubkey = pubkey->next)
if (pubkey->grip_valid
&& !memcmp (kinfo->grip, pubkey->grip, KEYGRIP_LEN))
break;
if (pubkey)
{
tty_fprintf (fp, " fpr ......: ");
print_shax_fpr (fp, pubkey->fpr, pubkey->fprlen);
tty_fprintf (fp, " created ..: %s\n",
isotimestamp (pubkey->created));
}
}
for (uid = kb->uids; uid; uid = uid->next)
{
print_string (fp, " user id ..: ", uid->value);
}
}
}
else
{
tty_fprintf (fp, " [none]\n");
if (label_keyref)
tty_fprintf (fp, " keyref .....: %s\n", label_keyref);
if (kinfo)
tty_fprintf (fp, " algorithm ..: %s\n",
nullnone (kinfo->keyalgo));
}
leave:
release_keyblock (keyblock);
}
/* Return the retired key number if KEYREF is for a retired key; 0 if
* not. */
static int
piv_keyref_is_retired (const char *keyref)
{
if (!strncmp (keyref, "PIV.8", 5)
&& keyref[5] >= '2' && hexdigitp (keyref + 5))
return xtoi_1 (keyref+5) - 1;
else if (!strncmp (keyref, "PIV.9", 5)
&& keyref[5] >= '0' && keyref[5] <= '5')
return atoi_1 (keyref+5) + 15;
else
return 0;
}
/* List all keyinfo in INFO using the list of LABELS. */
static void
list_all_kinfo (card_info_t info, keyinfolabel_t labels, estream_t fp,
int no_key_lookup, int create_shadow)
{
key_info_t kinfo;
int idx, i, j;
int rn;
/* Print the keyinfo. We first print those we known and then all
* remaining item. */
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
kinfo->xflag = 0;
if (labels)
{
for (idx=0; labels[idx].label; idx++)
{
tty_fprintf (fp, "%s", labels[idx].label);
kinfo = find_kinfo (info, labels[idx].keyref);
list_one_kinfo (info, kinfo, labels[idx].keyref,
fp, no_key_lookup, create_shadow);
if (kinfo)
kinfo->xflag = 1;
}
}
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
{
if (kinfo->xflag)
continue;
if (info->apptype == APP_TYPE_PIV
&& (rn = piv_keyref_is_retired (kinfo->keyref)))
tty_fprintf (fp, "Key retired %2d ...", rn);
else
{
tty_fprintf (fp, "Key %s", kinfo->keyref);
for (i=4+strlen (kinfo->keyref), j=0; i < 18; i++, j=1)
tty_fprintf (fp, j? ".":" ");
}
tty_fprintf (fp, ":");
list_one_kinfo (info, kinfo, NULL, fp, no_key_lookup, create_shadow);
}
}
static void
list_retry_counter (card_info_t info, estream_t fp)
{
const char *s;
int i;
if (info->chvlabels)
tty_fprintf (fp, "PIN labels .......: %s\n", info->chvlabels);
tty_fprintf (fp, "PIN retry counter :");
for (i=0; i < DIM (info->chvinfo) && i < info->nchvinfo; i++)
{
if (info->chvinfo[i] >= 0)
tty_fprintf (fp, " %d", info->chvinfo[i]);
else
{
switch (info->chvinfo[i])
{
case -1: s = "[error]"; break;
case -2: s = "-"; break; /* No such PIN or info not available. */
case -3: s = "[blocked]"; break;
case -4: s = "[nullpin]"; break;
case -5: s = "[verified]"; break;
default: s = "[?]"; break;
}
tty_fprintf (fp, " %s", s);
}
}
tty_fprintf (fp, "\n");
}
/* List OpenPGP card specific data. */
static void
list_openpgp (card_info_t info, estream_t fp,
int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "Signature key ....:", "OPENPGP.1" },
{ "Encryption key....:", "OPENPGP.2" },
{ "Authentication key:", "OPENPGP.3" },
{ NULL, NULL }
};
if (info->apptype != APP_TYPE_OPENPGP)
{
tty_fprintf (fp, "invalid OpenPGP card\n");
return;
}
tty_fprintf (fp, "Name of cardholder: ");
print_isoname (fp, info->disp_name);
print_string (fp, "Language prefs ...: ", info->disp_lang);
tty_fprintf (fp, "Salutation .......: %s\n",
info->disp_sex == 1? _("Mr."):
info->disp_sex == 2? _("Ms.") : "");
print_string (fp, "URL of public key : ", info->pubkey_url);
print_string (fp, "Login data .......: ", info->login_data);
if (info->private_do[0])
print_string (fp, "Private DO 1 .....: ", info->private_do[0]);
if (info->private_do[1])
print_string (fp, "Private DO 2 .....: ", info->private_do[1]);
if (info->private_do[2])
print_string (fp, "Private DO 3 .....: ", info->private_do[2]);
if (info->private_do[3])
print_string (fp, "Private DO 4 .....: ", info->private_do[3]);
if (info->cafpr1len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 1);
print_shax_fpr (fp, info->cafpr1, info->cafpr1len);
}
if (info->cafpr2len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 2);
print_shax_fpr (fp, info->cafpr2, info->cafpr2len);
}
if (info->cafpr3len)
{
tty_fprintf (fp, "CA fingerprint %d .:", 3);
print_shax_fpr (fp, info->cafpr3, info->cafpr3len);
}
tty_fprintf (fp, "Signature PIN ....: %s\n",
info->chv1_cached? _("not forced"): _("forced"));
tty_fprintf (fp, "Max. PIN lengths .: %d %d %d\n",
info->chvmaxlen[0], info->chvmaxlen[1], info->chvmaxlen[2]);
list_retry_counter (info, fp);
tty_fprintf (fp, "Signature counter : %lu\n", info->sig_counter);
tty_fprintf (fp, "Capabilities .....:");
if (info->extcap.ki)
tty_fprintf (fp, " key-import");
if (info->extcap.aac)
tty_fprintf (fp, " algo-change");
if (info->extcap.bt)
tty_fprintf (fp, " button");
if (info->extcap.sm)
tty_fprintf (fp, " sm(%s)", gcry_cipher_algo_name (info->extcap.smalgo));
if (info->extcap.private_dos)
tty_fprintf (fp, " priv-data");
tty_fprintf (fp, "\n");
if (info->extcap.kdf)
{
tty_fprintf (fp, "KDF setting ......: %s\n",
info->kdf_do_enabled ? "on" : "off");
}
if (info->extcap.bt)
{
tty_fprintf (fp, "UIF setting ......: Sign=%s Decrypt=%s Auth=%s\n",
info->uif[0] ? (info->uif[0]==2? "permanent": "on") : "off",
info->uif[1] ? (info->uif[0]==2? "permanent": "on") : "off",
info->uif[2] ? (info->uif[0]==2? "permanent": "on") : "off");
}
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
/* List PIV card specific data. */
static void
list_piv (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ "PIV authentication:", "PIV.9A" },
{ "Card authenticat. :", "PIV.9E" },
{ "Digital signature :", "PIV.9C" },
{ "Key management ...:", "PIV.9D" },
{ NULL, NULL }
};
if (info->chvusage[0] || info->chvusage[1])
{
tty_fprintf (fp, "PIN usage policy .:");
if ((info->chvusage[0] & 0x40))
tty_fprintf (fp, " app-pin");
if ((info->chvusage[0] & 0x20))
tty_fprintf (fp, " global-pin");
if ((info->chvusage[0] & 0x10))
tty_fprintf (fp, " occ");
if ((info->chvusage[0] & 0x08))
tty_fprintf (fp, " vci");
if ((info->chvusage[0] & 0x08) && !(info->chvusage[0] & 0x04))
tty_fprintf (fp, " pairing");
if (info->chvusage[1] == 0x10)
tty_fprintf (fp, " primary:card");
else if (info->chvusage[1] == 0x20)
tty_fprintf (fp, " primary:global");
tty_fprintf (fp, "\n");
}
list_retry_counter (info, fp);
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
/* List Netkey card specific data. */
static void
list_nks (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ NULL, NULL }
};
list_retry_counter (info, fp);
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
/* List PKCS#15 card specific data. */
static void
list_p15 (card_info_t info, estream_t fp, int no_key_lookup, int create_shadow)
{
static struct keyinfolabel_s keyinfolabels[] = {
{ NULL, NULL }
};
list_retry_counter (info, fp);
list_all_kinfo (info, keyinfolabels, fp, no_key_lookup, create_shadow);
}
static void
print_a_version (estream_t fp, const char *prefix, unsigned int value)
{
unsigned int a, b, c, d;
a = ((value >> 24) & 0xff);
b = ((value >> 16) & 0xff);
c = ((value >> 8) & 0xff);
d = ((value ) & 0xff);
if (a)
tty_fprintf (fp, "%s %u.%u.%u.%u\n", prefix, a, b, c, d);
else if (b)
tty_fprintf (fp, "%s %u.%u.%u\n", prefix, b, c, d);
else if (c)
tty_fprintf (fp, "%s %u.%u\n", prefix, c, d);
else
tty_fprintf (fp, "%s %u\n", prefix, d);
}
/* Print all available information about the current card. With
* NO_KEY_LOOKUP the sometimes expensive listing of all matching
* OpenPGP and X.509 keys is not done */
static void
list_card (card_info_t info, int no_key_lookup, int create_shadow)
{
estream_t fp = opt.interactive? NULL : es_stdout;
tty_fprintf (fp, "Reader ...........: %s\n", nullnone (info->reader));
if (info->cardtype)
tty_fprintf (fp, "Card type ........: %s\n", info->cardtype);
if (info->cardversion)
print_a_version (fp, "Card firmware ....:", info->cardversion);
tty_fprintf (fp, "Serial number ....: %s\n", nullnone (info->serialno));
tty_fprintf (fp, "Application type .: %s%s%s%s\n",
app_type_string (info->apptype),
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? "(":"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr
? info->apptypestr:"",
info->apptype == APP_TYPE_UNKNOWN && info->apptypestr? ")":"");
if (info->appversion)
print_a_version (fp, "Version ..........:", info->appversion);
if (info->serialno && info->dispserialno
&& strcmp (info->serialno, info->dispserialno))
tty_fprintf (fp, "Displayed s/n ....: %s\n", info->dispserialno);
if (info->manufacturer_name && info->manufacturer_id)
tty_fprintf (fp, "Manufacturer .....: %s (%x)\n",
info->manufacturer_name, info->manufacturer_id);
else if (info->manufacturer_name && !info->manufacturer_id)
tty_fprintf (fp, "Manufacturer .....: %s\n", info->manufacturer_name);
else if (info->manufacturer_id)
tty_fprintf (fp, "Manufacturer .....: (%x)\n", info->manufacturer_id);
switch (info->apptype)
{
case APP_TYPE_OPENPGP:
list_openpgp (info, fp, no_key_lookup, create_shadow);
break;
case APP_TYPE_PIV:
list_piv (info, fp, no_key_lookup, create_shadow);
break;
case APP_TYPE_NKS:
list_nks (info, fp, no_key_lookup, create_shadow);
break;
case APP_TYPE_P15:
list_p15 (info, fp, no_key_lookup, create_shadow);
break;
default: break;
}
}
/* Helper for cmd_list. */
static void
print_card_list (estream_t fp, card_info_t info, strlist_t cards,
int only_current)
{
int count;
strlist_t sl;
size_t snlen;
int star;
const char *s;
for (count = 0, sl = cards; sl; sl = sl->next, count++)
{
if (info && info->serialno)
{
s = strchr (sl->d, ' ');
if (s)
snlen = s - sl->d;
else
snlen = strlen (sl->d);
star = (strlen (info->serialno) == snlen
&& !memcmp (info->serialno, sl->d, snlen));
}
else
star = 0;
if (!only_current || star)
tty_fprintf (fp, "%d%c %s\n", count, star? '*':' ', sl->d);
}
}
/* The LIST command. This also updates INFO if needed. */
static gpg_error_t
cmd_list (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_cards, opt_apps, opt_info, opt_reread, opt_no_key_lookup;
int opt_shadow;
strlist_t cards = NULL;
strlist_t sl;
estream_t fp = opt.interactive? NULL : es_stdout;
const char *cardsn = NULL;
char *appstr = NULL;
int count;
int need_learn = 0;
if (!info)
return print_help
("LIST [--cards] [--apps] [--info] [--reread] [--shadow]"
" [--no-key-lookup] [N] [APP]\n\n"
"Show the content of the current card.\n"
"With N given select and list the N-th card;\n"
"with APP also given select that application.\n"
"To select an APP on the current card use '-' for N.\n"
"The S/N of the card may be used instead of N.\n"
" --cards list available cards\n"
" --apps list additional card applications\n"
" --info select a card and prints its s/n\n"
" --reread read infos from PCKS#15 cards again\n"
" --shadow create shadow keys for all card keys\n"
" --no-key-lookup do not list matching OpenPGP or X.509 keys\n"
, 0);
opt_cards = has_leading_option (argstr, "--cards");
opt_apps = has_leading_option (argstr, "--apps");
opt_info = has_leading_option (argstr, "--info");
opt_reread = has_leading_option (argstr, "--reread");
opt_shadow = has_leading_option (argstr, "--shadow");
opt_no_key_lookup = has_leading_option (argstr, "--no-key-lookup");
argstr = skip_options (argstr);
if (opt_shadow)
opt_no_key_lookup = 1;
if (opt.no_key_lookup)
opt_no_key_lookup = 1;
if (hexdigitp (argstr) || (*argstr == '-' && spacep (argstr+1)))
{
if (*argstr == '-' && (argstr[1] || spacep (argstr+1)))
argstr++; /* Keep current card. */
else
{
cardsn = argstr;
while (hexdigitp (argstr))
argstr++;
if (*argstr && !spacep (argstr))
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (*argstr)
*argstr++ = 0;
}
while (spacep (argstr))
argstr++;
if (*argstr)
{
appstr = argstr;
while (*argstr && !spacep (argstr))
argstr++;
while (spacep (argstr))
argstr++;
if (*argstr)
{
/* Extra arguments found. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
}
}
else if (*argstr)
{
/* First argument needs to be a digit. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (!info->serialno || info->need_sn_cmd)
{
/* This is probably the first call or was explicitly requested.
* We need to send a SERIALNO command to scdaemon so that our
* session knows all cards. */
err = scd_serialno (NULL, NULL);
if (err)
goto leave;
info->need_sn_cmd = 0;
need_learn = 1;
}
if (opt_cards || opt_apps)
{
/* Note that with option --apps CARDS is here the list of all
* apps. Format is "SERIALNO APPNAME {APPNAME}". We print the
* card number in the first column. */
if (opt_apps)
err = scd_applist (&cards, opt_cards);
else
err = scd_cardlist (&cards);
if (err)
goto leave;
print_card_list (fp, info, cards, 0);
}
else
{
if (cardsn)
{
int i, cardno;
err = scd_cardlist (&cards);
if (err)
goto leave;
/* Switch to the requested card. */
for (i=0; digitp (cardsn+i); i++)
;
if (i && i < 4 && !cardsn[i])
{ /* Looks like an index into the card list. */
cardno = atoi (cardsn);
for (count = 0, sl = cards; sl; sl = sl->next, count++)
if (count == cardno)
break;
if (!sl)
{
err = gpg_error (GPG_ERR_INV_INDEX);
goto leave;
}
}
else /* S/N of card specified. */
{
for (sl = cards; sl; sl = sl->next)
if (!ascii_strcasecmp (sl->d, cardsn))
break;
if (!sl)
{
err = gpg_error (GPG_ERR_INV_INDEX);
goto leave;
}
}
err = scd_switchcard (sl->d);
need_learn = 1;
}
else /* show app list. */
{
err = scd_applist (&cards, 1);
if (err)
goto leave;
}
if (appstr && *appstr)
{
/* Switch to the requested app. */
err = scd_switchapp (appstr);
if (err)
goto leave;
need_learn = 1;
}
if (need_learn)
err = scd_learn (info, opt_reread);
else
err = 0;
if (err)
;
else if (opt_info)
print_card_list (fp, info, cards, 1);
else
{
size_t snlen;
const char *s;
/* First get the list of active cards and check whether the
* current card is still in the list. If not the card has
* been removed. Note that during the listing the card
* remove state might also be detected but only if an access
* to the scdaemon is required; it is anyway better to test
* that before starting a listing. */
free_strlist (cards);
err = scd_cardlist (&cards);
if (err)
goto leave;
for (sl = cards; sl; sl = sl->next)
{
if (info && info->serialno)
{
s = strchr (sl->d, ' ');
if (s)
snlen = s - sl->d;
else
snlen = strlen (sl->d);
if (strlen (info->serialno) == snlen
&& !memcmp (info->serialno, sl->d, snlen))
break;
}
}
if (!sl)
{
info->need_sn_cmd = 1;
err = gpg_error (GPG_ERR_CARD_REMOVED);
goto leave;
}
list_card (info, opt_no_key_lookup, opt_shadow);
}
}
leave:
free_strlist (cards);
return err;
}
/* The CHECKKEYS command. */
static gpg_error_t
cmd_checkkeys (card_info_t callerinfo, char *argstr)
{
gpg_error_t err;
estream_t fp = opt.interactive? NULL : es_stdout;
strlist_t cards = NULL;
strlist_t sl;
int opt_ondisk;
int opt_delete_clear;
int opt_delete_protected;
int delete_count = 0;
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
key_info_t kinfo;
if (!callerinfo)
return print_help
("CHECKKEYS [--ondisk] [--delete-clear-copy] [--delete-protected-copy]"
"\n\n"
"Print a list of keys on all inserted cards. With --ondisk only\n"
"keys are listed which also have a copy on disk. Missing shadow\n"
"keys are created. With --delete-clear-copy, copies of keys also\n"
"stored on disk without any protection will be deleted.\n"
, 0);
opt_ondisk = has_leading_option (argstr, "--ondisk");
opt_delete_clear = has_leading_option (argstr, "--delete-clear-copy");
opt_delete_protected = has_leading_option (argstr, "--delete-protected-copy");
argstr = skip_options (argstr);
if (*argstr)
{
/* No args expected */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (!callerinfo->serialno)
{
/* This is probably the first call We need to send a SERIALNO
* command to scdaemon so that our session knows all cards. */
err = scd_serialno (NULL, NULL);
if (err)
goto leave;
}
/* Get the list of all cards. */
err = scd_cardlist (&cards);
if (err)
goto leave;
/* Loop over all cards. We use our own info buffer here. */
for (sl = cards; sl; sl = sl->next)
{
err = scd_switchcard (sl->d);
if (err)
{
log_error ("Error switching to card %s: %s\n",
sl->d, gpg_strerror (err));
continue;
}
release_card_info (info);
err = scd_learn (info, 0);
if (err)
{
log_error ("Error getting infos from card %s: %s\n",
sl->d, gpg_strerror (err));
continue;
}
for (kinfo = info->kinfo; kinfo; kinfo = kinfo->next)
{
char *infostr;
err = scd_havekey_info (kinfo->grip, &infostr);
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
{
/* Create a shadow key and try again. */
scd_readkey (kinfo->keyref, 1, NULL);
err = scd_havekey_info (kinfo->grip, &infostr);
}
if (err && gpg_err_code (err) != GPG_ERR_NOT_FOUND)
log_error ("Error getting infos for a key: %s\n",
gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_NOT_FOUND)
; /* does not make sense to show this. */
else if (opt_ondisk && infostr && !strcmp (infostr, "shadowed"))
; /* Don't print this one. */
else
{
tty_fprintf (fp, "%s %s ",
nullnone (info->serialno),
app_type_string (info->apptype));
print_keygrip (fp, kinfo->grip, 0);
tty_fprintf (fp, " %s %s\n",
kinfo->keyref, infostr? infostr: "error");
}
if (infostr
&& ((opt_delete_clear && !strcmp (infostr, "clear"))
|| (opt_delete_protected && !strcmp (infostr, "protected"))))
{
err = scd_delete_key (kinfo->grip, 0);
if (err)
log_error ("Error deleting a key copy: %s\n",
gpg_strerror (err));
else
delete_count++;
}
xfree (infostr);
}
}
es_fflush (es_stdout);
if (delete_count)
log_info ("Number of deleted key copies: %d\n", delete_count);
err = 0;
leave:
release_card_info (info);
free_strlist (cards);
/* Better reset to the original card. */
scd_learn (callerinfo, 0);
return err;
}
/* The VERIFY command. */
static gpg_error_t
cmd_verify (card_info_t info, char *argstr)
{
gpg_error_t err, err2;
const char *pinref;
if (!info)
return print_help ("verify [chvid]", 0);
if (*argstr)
pinref = argstr;
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = info->serialno;
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
return gpg_error (GPG_ERR_MISSING_VALUE);
err = scd_checkpin (pinref);
if (err)
log_error ("verify failed: %s <%s>\n",
gpg_strerror (err), gpg_strsource (err));
/* In any case update the CHV status, so that the next "list" shows
* the correct retry counter values. */
err2 = scd_getattr ("CHV-STATUS", info);
return err ? err : err2;
}
static gpg_error_t
cmd_authenticate (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_setkey;
int opt_raw;
char *string = NULL;
char *key = NULL;
size_t keylen;
if (!info)
return print_help
("AUTHENTICATE [--setkey] [--raw] [< FILE]|KEY\n\n"
"Perform a mutual authentication either by reading the key\n"
"from FILE or by taking it from the command line. Without\n"
"the option --raw the key is expected to be hex encoded.\n"
"To install a new administration key --setkey is used; this\n"
"requires a prior authentication with the old key.",
APP_TYPE_PIV, 0);
if (info->apptype != APP_TYPE_PIV)
{
log_info ("Note: This is a PIV only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
opt_setkey = has_leading_option (argstr, "--setkey");
opt_raw = has_leading_option (argstr, "--raw");
argstr = skip_options (argstr);
if (*argstr == '<') /* Read key from a file. */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &string, NULL);
if (err)
goto leave;
}
if (opt_raw)
{
key = string? string : xstrdup (argstr);
string = NULL;
keylen = strlen (key);
}
else
{
key = hex_to_buffer (string? string: argstr, &keylen);
if (!key)
{
err = gpg_error_from_syserror ();
goto leave;
}
}
err = scd_setattr (opt_setkey? "SET-ADM-KEY":"AUTH-ADM-KEY", key, keylen);
leave:
if (key)
{
wipememory (key, keylen);
xfree (key);
}
xfree (string);
return err;
}
/* Helper for cmd_name to qyery a part of name. */
static char *
ask_one_name (const char *prompt)
{
char *name;
int i;
for (;;)
{
name = tty_get (prompt);
trim_spaces (name);
tty_kill_prompt ();
if (!*name || *name == CONTROL_D)
{
if (*name == CONTROL_D)
tty_fprintf (NULL, "\n");
xfree (name);
return NULL;
}
for (i=0; name[i] && name[i] >= ' ' && name[i] <= 126; i++)
;
/* The name must be in Latin-1 and not UTF-8 - lacking the code
* to ensure this we restrict it to ASCII. */
if (name[i])
tty_printf (_("Error: Only plain ASCII is currently allowed.\n"));
else if (strchr (name, '<'))
tty_printf (_("Error: The \"<\" character may not be used.\n"));
else if (strstr (name, " "))
tty_printf (_("Error: Double spaces are not allowed.\n"));
else
return name;
xfree (name);
}
}
/* The NAME command. */
static gpg_error_t
cmd_name (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *surname, *givenname;
char *isoname, *p;
if (!info)
return print_help
("name [--clear]\n\n"
"Set the name field of an OpenPGP card. With --clear the stored\n"
"name is cleared off the card.", APP_TYPE_OPENPGP, APP_TYPE_NKS, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
again:
if (!strcmp (argstr, "--clear"))
isoname = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
surname = ask_one_name (_("Cardholder's surname: "));
givenname = ask_one_name (_("Cardholder's given name: "));
if (!surname || !givenname || (!*surname && !*givenname))
{
xfree (surname);
xfree (givenname);
return gpg_error (GPG_ERR_CANCELED);
}
isoname = xstrconcat (surname, "<<", givenname, NULL);
xfree (surname);
xfree (givenname);
for (p=isoname; *p; p++)
if (*p == ' ')
*p = '<';
if (strlen (isoname) > 39 )
{
log_info (_("Error: Combined name too long "
"(limit is %d characters).\n"), 39);
xfree (isoname);
goto again;
}
}
err = scd_setattr ("DISP-NAME", isoname, strlen (isoname));
xfree (isoname);
return err;
}
static gpg_error_t
cmd_url (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *url;
if (!info)
return print_help
("URL [--clear]\n\n"
"Set the URL data object. That data object can be used by\n"
"the FETCH command to retrieve the full public key. The\n"
"option --clear deletes the content of that data object.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!strcmp (argstr, "--clear"))
url = xstrdup (" "); /* No real way to clear; set to space instead. */
else
{
url = tty_get (_("URL to retrieve public key: "));
trim_spaces (url);
tty_kill_prompt ();
if (!*url || *url == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr ("PUBKEY-URL", url, strlen (url));
leave:
xfree (url);
return err;
}
/* Fetch the key from the URL given on the card or try to get it from
* the default keyserver. */
static gpg_error_t
cmd_fetch (card_info_t info)
{
gpg_error_t err;
key_info_t kinfo;
if (!info)
return print_help
("FETCH\n\n"
"Retrieve a key using the URL data object or if that is missing\n"
"using the fingerprint.", APP_TYPE_OPENPGP, 0);
if (info->pubkey_url && *info->pubkey_url)
{
/* strlist_t sl = NULL; */
/* add_to_strlist (&sl, info.pubkey_url); */
/* err = keyserver_fetch (ctrl, sl, KEYORG_URL); */
/* free_strlist (sl); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else if ((kinfo = find_kinfo (info, "OPENPGP.1")) && kinfo->fprlen)
{
/* rc = keyserver_import_fprint (ctrl, info.fpr1, info.fpr1len, */
/* opt.keyserver, 0); */
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED); /* FIXME */
}
else
err = gpg_error (GPG_ERR_NO_DATA);
return err;
}
static gpg_error_t
cmd_login (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data;
size_t datalen;
int use_default_pin;
if (!info)
return print_help
("LOGIN [--clear|--use-default-pin] [< FILE]\n\n"
"Set the login data object. If FILE is given the data is\n"
"is read from that file. This allows for binary data.\n"
"The option --clear deletes the login data. --use-default-pin\n"
"tells the card to always use the default PIN (\"123456\").",
APP_TYPE_OPENPGP, 0);
use_default_pin = has_leading_option (argstr, "--use-default-pin");
argstr = skip_options (argstr);
if (!strcmp (argstr, "--clear"))
{
data = xstrdup (" "); /* kludge. */
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else
{
data = tty_get (_("Login data (account name): "));
trim_spaces (data);
tty_kill_prompt ();
if ((!*data && !use_default_pin) || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
datalen = strlen (data);
}
if (use_default_pin)
{
char *tmpdata = xmalloc (datalen + 5);
memcpy (tmpdata, data, datalen);
memcpy (tmpdata+datalen, "\n\x14" "F=3", 5);
xfree (data);
data = tmpdata;
datalen += 5;
}
err = scd_setattr ("LOGIN-DATA", data, datalen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_lang (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data, *p;
if (!info)
return print_help
("LANG [--clear]\n\n"
"Change the language info for the card. This info can be used\n"
"by applications for a personalized greeting. Up to 4 two-digit\n"
"language identifiers can be entered as a preference. The option\n"
"--clear removes all identifiers. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
if (!strcmp (argstr, "--clear"))
data = xstrdup (" "); /* Note that we need two spaces here. */
else
{
again:
data = tty_get (_("Language preferences: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (strlen (data) > 8 || (strlen (data) & 1))
{
log_info (_("Error: invalid length of preference string.\n"));
xfree (data);
goto again;
}
for (p=data; *p && *p >= 'a' && *p <= 'z'; p++)
;
if (*p)
{
log_info (_("Error: invalid characters in preference string.\n"));
xfree (data);
goto again;
}
}
err = scd_setattr ("DISP-LANG", data, strlen (data));
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_salut (card_info_t info, const char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *str;
if (!info)
return print_help
("SALUT [--clear]\n\n"
"Change the salutation info for the card. This info can be used\n"
"by applications for a personalized greeting. The option --clear\n"
"removes this data object. GnuPG does not use this info.",
APP_TYPE_OPENPGP, 0);
again:
if (!strcmp (argstr, "--clear"))
str = "9";
else
{
data = tty_get (_("Salutation (M = Mr., F = Ms., or space): "));
trim_spaces (data);
tty_kill_prompt ();
if (*data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (!*data)
str = "9";
else if ((*data == 'M' || *data == 'm') && !data[1])
str = "1";
else if ((*data == 'F' || *data == 'f') && !data[1])
str = "2";
else
{
tty_printf (_("Error: invalid response.\n"));
xfree (data);
data = NULL;
goto again;
}
}
err = scd_setattr ("DISP-SEX", str, 1);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_cafpr (card_info_t info, char *argstr)
{
gpg_error_t err;
char *data = NULL;
const char *s;
int i, c;
unsigned char fpr[32];
int fprlen;
int fprno;
int opt_clear = 0;
if (!info)
return print_help
("CAFPR [--clear] N\n\n"
"Change the CA fingerprint number N. N must be in the\n"
"range 1 to 3. The option --clear clears the specified\n"
"CA fingerprint N or all of them if N is 0 or not given.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
fprno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
fprno = 0;
if (opt_clear && !fprno)
; /* Okay: clear all fprs. */
else if (fprno < 1 || fprno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
again:
if (opt_clear)
{
memset (fpr, 0, 20);
fprlen = 20;
}
else
{
xfree (data);
data = tty_get (_("CA fingerprint: "));
trim_spaces (data);
tty_kill_prompt ();
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
for (i=0, s=data; i < sizeof fpr && *s; )
{
while (spacep(s))
s++;
if (*s == ':')
s++;
while (spacep(s))
s++;
c = hextobyte (s);
if (c == -1)
break;
fpr[i++] = c;
s += 2;
}
fprlen = i;
if ((fprlen != 20 && fprlen != 32) || *s)
{
log_error (_("Error: invalid formatted fingerprint.\n"));
goto again;
}
}
if (!fprno)
{
log_assert (opt_clear);
err = scd_setattr ("CA-FPR-1", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-2", fpr, fprlen);
if (!err)
err = scd_setattr ("CA-FPR-3", fpr, fprlen);
}
else
err = scd_setattr (fprno==1?"CA-FPR-1":
fprno==2?"CA-FPR-2":
fprno==3?"CA-FPR-3":"x", fpr, fprlen);
leave:
xfree (data);
return err;
}
static gpg_error_t
cmd_privatedo (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
char *do_name = NULL;
char *data = NULL;
size_t datalen;
int do_no;
if (!info)
return print_help
("PRIVATEDO [--clear] N [< FILE]\n\n"
"Change the private data object N. N must be in the\n"
"range 1 to 4. If FILE is given the data is is read\n"
"from that file. The option --clear clears the data.",
APP_TYPE_OPENPGP, 0);
opt_clear = has_leading_option (argstr, "--clear");
argstr = skip_options (argstr);
if (digitp (argstr))
{
do_no = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
do_no = 0;
if (do_no < 1 || do_no > 4)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
do_name = xasprintf ("PRIVATE-DO-%d", do_no);
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
}
else if (*argstr)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
else
{
data = tty_get (_("Private DO data: "));
trim_spaces (data);
tty_kill_prompt ();
datalen = strlen (data);
if (!*data || *data == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
err = scd_setattr (do_name, data, datalen);
leave:
xfree (do_name);
xfree (data);
return err;
}
static gpg_error_t
cmd_writecert (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_clear;
int opt_openpgp;
char *certref_buffer = NULL;
char *certref;
char *data = NULL;
size_t datalen;
estream_t key = NULL;
if (!info)
return print_help
("WRITECERT CERTREF '<' FILE\n"
"WRITECERT --openpgp CERTREF ['<' FILE|FPR]\n"
"WRITECERT --clear CERTREF\n\n"
"Write a certificate to the card under the id CERTREF.\n"
"The option --clear removes the certificate from the card.\n"
"The option --openpgp expects an OpenPGP keyblock and stores\n"
"it encapsulated in a CMS container; the keyblock is taken\n"
"from FILE or directly from the OpenPGP key with FPR",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_clear = has_leading_option (argstr, "--clear");
opt_openpgp = has_leading_option (argstr, "--openpgp");
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3"))
certref_buffer = xstrdup ("OPENPGP.3");
else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2"))
certref_buffer = xstrdup ("OPENPGP.2");
else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1"))
certref_buffer = xstrdup ("OPENPGP.1");
else
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be OPENPGP.N or just N"
" with N being 1..3\"");
goto leave;
}
certref = certref_buffer;
}
else /* Upcase the certref; prepend cardtype if needed. */
{
if (!strchr (certref, '.'))
certref_buffer = xstrconcat (app_type_string (info->apptype), ".",
certref, NULL);
else
certref_buffer = xstrdup (certref);
ascii_strupr (certref_buffer);
certref = certref_buffer;
}
if (opt_clear)
{
data = xstrdup (" ");
datalen = 1;
}
else if (*argstr == '<') /* Read it from a file */
{
for (argstr++; spacep (argstr); argstr++)
;
err = get_data_from_file (argstr, &data, &datalen);
if (err)
goto leave;
if (ascii_memistr (data, datalen, "-----BEGIN CERTIFICATE-----")
&& ascii_memistr (data, datalen, "-----END CERTIFICATE-----")
&& !memchr (data, 0, datalen) && !memchr (data, 1, datalen))
{
gpgrt_b64state_t b64;
b64 = gpgrt_b64dec_start ("");
if (!b64)
err = gpg_error_from_syserror ();
else
err = gpgrt_b64dec_proc (b64, data, datalen, &datalen);
if (!err)
err = gpgrt_b64dec_finish (b64);
if (err)
goto leave;
}
}
else if (opt_openpgp && *argstr)
{
err = get_minimal_openpgp_key (&key, argstr);
if (err)
goto leave;
if (es_fclose_snatch (key, (void*)&data, &datalen))
{
err = gpg_error_from_syserror ();
goto leave;
}
key = NULL;
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (opt_openpgp && !opt_clear)
{
tlv_builder_t tb;
void *tmpder;
size_t tmpderlen;
tb = tlv_builder_new (0);
if (!tb)
{
err = gpg_error_from_syserror ();
goto leave;
}
tlv_builder_add_tag (tb, 0, TAG_SEQUENCE);
tlv_builder_add_ptr (tb, 0, TAG_OBJECT_ID,
"\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", 10);
tlv_builder_add_tag (tb, CLASS_CONTEXT, 0);
tlv_builder_add_ptr (tb, 0, TAG_OCTET_STRING, data, datalen);
tlv_builder_add_end (tb);
tlv_builder_add_end (tb);
err = tlv_builder_finalize (tb, &tmpder, &tmpderlen);
if (err)
goto leave;
xfree (data);
data = tmpder;
datalen = tmpderlen;
}
err = scd_writecert (certref, data, datalen);
leave:
es_fclose (key);
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_readcert (card_info_t info, char *argstr)
{
gpg_error_t err;
char *certref_buffer = NULL;
char *certref;
void *data = NULL;
size_t datalen, dataoff;
const char *fname;
int opt_openpgp;
if (!info)
return print_help
("READCERT [--openpgp] CERTREF > FILE\n\n"
"Read the certificate for key CERTREF and store it in FILE.\n"
"With option \"--openpgp\" an OpenPGP keyblock is expected\n"
"and stored in FILE.\n",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_openpgp = has_leading_option (argstr, "--openpgp");
argstr = skip_options (argstr);
certref = argstr;
if ((argstr = strchr (certref, ' ')))
{
*argstr++ = 0;
trim_spaces (certref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = certref + strlen (certref);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!ascii_strcasecmp (certref, "OPENPGP.3") || !strcmp (certref, "3"))
certref_buffer = xstrdup ("OPENPGP.3");
else if (!ascii_strcasecmp (certref, "OPENPGP.2")||!strcmp (certref,"2"))
certref_buffer = xstrdup ("OPENPGP.2");
else if (!ascii_strcasecmp (certref, "OPENPGP.1")||!strcmp (certref,"1"))
certref_buffer = xstrdup ("OPENPGP.1");
else
{
err = gpg_error (GPG_ERR_INV_ID);
log_error ("Error: CERTREF must be OPENPGP.N or just N"
" with N being 1..3\"");
goto leave;
}
certref = certref_buffer;
}
if (*argstr == '>') /* Write it to a file */
{
for (argstr++; spacep (argstr); argstr++)
;
fname = argstr;
}
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
dataoff = 0;
err = scd_readcert (certref, &data, &datalen);
if (err)
goto leave;
if (opt_openpgp)
{
/* Check whether DATA contains an OpenPGP keyblock and put only
* this into FILE. If the data is something different, return
* an error. */
const unsigned char *p;
size_t n, objlen, hdrlen;
int class, tag, cons, ndef;
p = data;
n = datalen;
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_UNIVERSAL && tag == TAG_SEQUENCE && cons))
goto not_openpgp; /* Does not start with a sequence. */
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_UNIVERSAL && tag == TAG_OBJECT_ID && !cons))
goto not_openpgp; /* No Object ID. */
if (objlen > n)
goto not_openpgp; /* Inconsistent lengths. */
if (objlen != 10
|| memcmp (p, "\x2B\x06\x01\x04\x01\xDA\x47\x02\x03\x01", objlen))
goto not_openpgp; /* Wrong Object ID. */
p += objlen;
n -= objlen;
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_CONTEXT && tag == 0 && cons))
goto not_openpgp; /* Not a [0] context tag. */
if (parse_ber_header (&p,&n,&class,&tag,&cons,&ndef,&objlen,&hdrlen))
goto not_openpgp;
if (!(class == CLASS_UNIVERSAL && tag == TAG_OCTET_STRING && !cons))
goto not_openpgp; /* Not an octet string. */
if (objlen > n)
goto not_openpgp; /* Inconsistent lengths. */
dataoff = p - (const unsigned char*)data;
datalen = objlen;
}
err = put_data_to_file (fname, (unsigned char*)data+dataoff, datalen);
goto leave;
not_openpgp:
err = gpg_error (GPG_ERR_WRONG_BLOB_TYPE);
leave:
xfree (data);
xfree (certref_buffer);
return err;
}
static gpg_error_t
cmd_writekey (card_info_t info, char *argstr)
{
gpg_error_t err;
int opt_force;
const char *argv[2];
int argc;
char *keyref_buffer = NULL;
const char *keyref;
const char *keygrip;
if (!info)
return print_help
("WRITEKEY [--force] KEYREF KEYGRIP\n\n"
"Write a private key object identified by KEYGRIP to slot KEYREF.\n"
"Use --force to overwrite an existing key.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
opt_force = has_leading_option (argstr, "--force");
argstr = skip_options (argstr);
argc = split_fields (argstr, argv, DIM (argv));
if (argc < 2)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
/* Upcase the keyref; prepend cardtype if needed. */
keyref = argv[0];
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
/* Get the keygrip. */
keygrip = argv[1];
if (strlen (keygrip) != 40
&& !(keygrip[0] == '&' && strlen (keygrip+1) == 40))
{
log_error (_("Not a valid keygrip (expecting 40 hex digits)\n"));
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
err = scd_writekey (keyref, opt_force, keygrip);
leave:
xfree (keyref_buffer);
return err;
}
static gpg_error_t
cmd_forcesig (card_info_t info)
{
gpg_error_t err;
int newstate;
if (!info)
return print_help
("FORCESIG\n\n"
"Toggle the forcesig flag of an OpenPGP card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
newstate = !info->chv1_cached;
err = scd_setattr ("CHV-STATUS-1", newstate? "\x01":"", 1);
if (err)
goto leave;
/* Read it back to be sure we have the right toggle state the next
* time. */
err = scd_getattr ("CHV-STATUS", info);
leave:
return err;
}
/* Helper for cmd_generate_openpgp. Note that either 0 or 1 is stored at
* FORCED_CHV1. */
static gpg_error_t
check_pin_for_key_operation (card_info_t info, int *forced_chv1)
{
gpg_error_t err = 0;
*forced_chv1 = !info->chv1_cached;
if (*forced_chv1)
{ /* Switch off the forced mode so that during key generation we
* don't get bothered with PIN queries for each self-signature. */
err = scd_setattr ("CHV-STATUS-1", "\x01", 1);
if (err)
{
log_error ("error clearing forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = -1; /* Not changed. */
goto leave;
}
}
/* Check the PIN now, so that we won't get asked later for each
* binding signature. */
err = scd_checkpin (info->serialno);
if (err)
log_error ("error checking the PIN: %s\n", gpg_strerror (err));
leave:
return err;
}
/* Helper for cmd_generate_openpgp. */
static void
restore_forced_chv1 (int *forced_chv1)
{
gpg_error_t err;
/* Note the possible values stored at FORCED_CHV1:
* 0 - forcesig was not enabled.
* 1 - forcesig was enabled - enable it again.
* -1 - We have not changed anything. */
if (*forced_chv1 == 1)
{ /* Switch back to forced state. */
err = scd_setattr ("CHV-STATUS-1", "", 1);
if (err)
log_error ("error setting forced signature PIN flag: %s\n",
gpg_strerror (err));
*forced_chv1 = 0;
}
}
/* Ask whether existing keys shall be overwritten. With NULL used for
* KINFO it will ask for all keys, other wise for the given key. */
static gpg_error_t
ask_replace_keys (key_info_t kinfo)
{
gpg_error_t err;
char *answer;
tty_printf ("\n");
if (kinfo)
log_info (_("Note: key %s is already stored on the card!\n"),
kinfo->keyref);
else
log_info (_("Note: Keys are already stored on the card!\n"));
tty_printf ("\n");
if (kinfo)
answer = tty_getf (_("Replace existing key %s ? (y/N) "), kinfo->keyref);
else
answer = tty_get (_("Replace existing keys? (y/N) "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
err = gpg_error (GPG_ERR_CANCELED);
else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
err = gpg_error (GPG_ERR_CANCELED);
else
err = 0;
xfree (answer);
return err;
}
/* Implementation of cmd_generate for OpenPGP cards to generate all
* standard keys at once. */
static gpg_error_t
generate_all_openpgp_card_keys (card_info_t info, char **algos)
{
gpg_error_t err;
int forced_chv1 = -1;
int want_backup;
char *answer = NULL;
key_info_t kinfo1, kinfo2, kinfo3;
if (info->extcap.ki)
{
xfree (answer);
answer = tty_get (_("Make off-card backup of encryption key? (Y/n) "));
want_backup = answer_is_yes_no_default (answer, 1/*(default to Yes)*/);
tty_kill_prompt ();
if (*answer == CONTROL_D)
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
}
else
want_backup = 0;
kinfo1 = find_kinfo (info, "OPENPGP.1");
kinfo2 = find_kinfo (info, "OPENPGP.2");
kinfo3 = find_kinfo (info, "OPENPGP.3");
if ((kinfo1 && kinfo1->fprlen && !mem_is_zero (kinfo1->fpr,kinfo1->fprlen))
|| (kinfo2 && kinfo2->fprlen && !mem_is_zero (kinfo2->fpr,kinfo2->fprlen))
|| (kinfo3 && kinfo3->fprlen && !mem_is_zero (kinfo3->fpr,kinfo3->fprlen))
)
{
err = ask_replace_keys (NULL);
if (err)
goto leave;
}
/* If no displayed name has been set, we assume that this is a fresh
* card and print a hint about the default PINs. */
if (!info->disp_name || !*info->disp_name)
{
tty_printf ("\n");
tty_printf (_("Please note that the factory settings of the PINs are\n"
" PIN = '%s' Admin PIN = '%s'\n"
"You should change them using the command --change-pin\n"),
OPENPGP_USER_PIN_DEFAULT, OPENPGP_ADMIN_PIN_DEFAULT);
tty_printf ("\n");
}
err = check_pin_for_key_operation (info, &forced_chv1);
if (err)
goto leave;
(void)algos; /* FIXME: If we have ALGOS, we need to change the key attr. */
/* FIXME: We need to divert to a function which spawns gpg which
* will then create the key. This also requires new features in
* gpg. We might also first create the keys on the card and then
* tell gpg to use them to create the OpenPGP keyblock. */
/* generate_keypair (ctrl, 1, NULL, info.serialno, want_backup); */
(void)want_backup;
err = scd_genkey ("OPENPGP.1", 1, NULL, NULL);
leave:
restore_forced_chv1 (&forced_chv1);
xfree (answer);
return err;
}
/* Create a single key. This is a helper for cmd_generate. */
static gpg_error_t
generate_key (card_info_t info, const char *keyref, int force,
const char *algo)
{
gpg_error_t err;
key_info_t kinfo;
if (info->apptype == APP_TYPE_OPENPGP)
{
kinfo = find_kinfo (info, keyref);
if (!kinfo)
{
err = gpg_error (GPG_ERR_INV_ID);
goto leave;
}
if (!force
&& kinfo->fprlen && !mem_is_zero (kinfo->fpr, kinfo->fprlen))
{
err = ask_replace_keys (NULL);
if (err)
goto leave;
force = 1;
}
}
err = scd_genkey (keyref, force, algo, NULL);
leave:
return err;
}
static gpg_error_t
cmd_generate (card_info_t info, char *argstr)
{
static char * const valid_algos[] =
{ "rsa2048", "rsa3072", "rsa4096", "",
"nistp256", "nistp384", "nistp521", "",
"brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "",
"ed25519", "cv25519",
NULL
};
gpg_error_t err;
int opt_force;
char *p;
char **opt_algo = NULL; /* Malloced. */
char *keyref_buffer = NULL; /* Malloced. */
char *keyref; /* Points into argstr or keyref_buffer. */
int i, j;
if (!info)
return print_help
("GENERATE [--force] [--algo=ALGO{+ALGO2}] KEYREF\n\n"
"Create a new key on a card.\n"
"Use --force to overwrite an existing key.\n"
"Use \"help\" for ALGO to get a list of known algorithms.\n"
"For OpenPGP cards several algos may be given.\n"
"Note that the OpenPGP key generation is done interactively\n"
"unless a single ALGO or KEYREF are given.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
opt_force = has_leading_option (argstr, "--force");
err = get_option_value (argstr, "--algo", &p);
if (err)
goto leave;
if (p)
{
opt_algo = strtokenize (p, "+");
if (!opt_algo)
{
err = gpg_error_from_syserror ();
xfree (p);
goto leave;
}
xfree (p);
}
argstr = skip_options (argstr);
keyref = argstr;
if ((argstr = strchr (keyref, ' ')))
{
*argstr++ = 0;
trim_spaces (keyref);
trim_spaces (argstr);
}
else /* Let argstr point to an empty string. */
argstr = keyref + strlen (keyref);
if (!*keyref)
keyref = NULL;
if (*argstr)
{
/* Extra arguments found. */
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if (opt_algo)
{
/* opt_algo is an array of algos. */
for (i=0; opt_algo[i]; i++)
{
for (j=0; valid_algos[j]; j++)
if (*valid_algos[j] && !strcmp (valid_algos[j], opt_algo[i]))
break;
if (!valid_algos[j])
{
int lf = 1;
if (!ascii_strcasecmp (opt_algo[i], "help"))
log_info ("Known algorithms:\n");
else
{
log_info ("Invalid algorithm '%s' given. Use one of:\n",
opt_algo[i]);
err = gpg_error (GPG_ERR_PUBKEY_ALGO);
}
for (i=0; valid_algos[i]; i++)
{
if (!*valid_algos[i])
lf = 1;
else if (lf)
{
lf = 0;
log_info (" %s%s",
valid_algos[i], valid_algos[i+1]?",":".");
}
else
log_printf (" %s%s",
valid_algos[i], valid_algos[i+1]?",":".");
}
log_printf ("\n");
show_keysize_warning ();
goto leave;
}
}
}
/* Upcase the keyref; if it misses the cardtype, prepend it. */
if (keyref)
{
if (!strchr (keyref, '.'))
keyref_buffer = xstrconcat (app_type_string (info->apptype), ".",
keyref, NULL);
else
keyref_buffer = xstrdup (keyref);
ascii_strupr (keyref_buffer);
keyref = keyref_buffer;
}
/* Special checks. */
if ((info->cardtype && !strcmp (info->cardtype, "yubikey"))
&& info->cardversion >= 0x040200 && info->cardversion < 0x040305)
{
log_error ("On-chip key generation on this YubiKey has been blocked.\n");
log_info ("Please see <https://yubi.co/ysa201701> for details\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
/* Divert to dedicated functions. */
if (info->apptype == APP_TYPE_OPENPGP
&& !keyref
&& (!opt_algo || (opt_algo[0] && opt_algo[1])))
{
/* With no algo requested or more than one algo requested and no
* keyref given we create all keys. */
if (opt_force || keyref)
log_info ("Note: OpenPGP key generation is interactive.\n");
err = generate_all_openpgp_card_keys (info, opt_algo);
}
else if (!keyref)
err = gpg_error (GPG_ERR_INV_ID);
else if (opt_algo && opt_algo[0] && opt_algo[1])
{
log_error ("only one algorithm expected as value for --algo.\n");
err = gpg_error (GPG_ERR_INV_ARG);
}
else
err = generate_key (info, keyref, opt_force, opt_algo? opt_algo[0]:NULL);
if (!err)
{
err = scd_learn (info, 0);
if (err)
log_error ("Error re-reading card: %s\n", gpg_strerror (err));
}
leave:
xfree (opt_algo);
xfree (keyref_buffer);
return err;
}
/* Change a PIN. */
static gpg_error_t
cmd_passwd (card_info_t info, char *argstr)
{
gpg_error_t err = 0;
char *answer = NULL;
const char *pinref = NULL;
int reset_mode = 0;
int nullpin = 0;
int menu_used = 0;
if (!info)
return print_help
("PASSWD [--reset|--nullpin] [PINREF]\n\n"
"Change or unblock the PINs. Note that in interactive mode\n"
"and without a PINREF a menu is presented for certain cards;\n"
"in non-interactive and without a PINREF a default value is\n"
"used for these cards. The option --reset is used with TCOS\n"
"cards to reset the PIN using the PUK or vice versa; --nullpin\n"
"is used for these cards to set the initial PIN.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (has_option (argstr, "--reset"))
reset_mode = 1;
else if (has_option (argstr, "--nullpin"))
nullpin = 1;
argstr = skip_options (argstr);
/* If --reset or --nullpin has been given we force non-interactive mode. */
if (*argstr || reset_mode || nullpin)
{
pinref = argstr;
if (!*pinref)
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
}
else if (opt.interactive && info->apptype == APP_TYPE_OPENPGP)
{
menu_used = 1;
while (!pinref)
{
xfree (answer);
answer = get_selection ("1 - change the PIN\n"
"2 - unblock and set new a PIN\n"
"3 - change the Admin PIN\n"
"4 - set the Reset Code\n"
"Q - quit\n");
if (strlen (answer) != 1)
continue;
else if (*answer == 'q' || *answer == 'Q')
goto leave;
else if (*answer == '1')
pinref = "OPENPGP.1";
else if (*answer == '2')
{ pinref = "OPENPGP.1"; reset_mode = 1; }
else if (*answer == '3')
pinref = "OPENPGP.3";
else if (*answer == '4')
{ pinref = "OPENPGP.2"; reset_mode = 1; }
}
}
else if (info->apptype == APP_TYPE_OPENPGP)
pinref = "OPENPGP.1";
else if (opt.interactive && info->apptype == APP_TYPE_PIV)
{
menu_used = 1;
while (!pinref)
{
xfree (answer);
answer = get_selection ("1 - change the PIN\n"
"2 - change the PUK\n"
"3 - change the Global PIN\n"
"Q - quit\n");
if (strlen (answer) != 1)
;
else if (*answer == 'q' || *answer == 'Q')
goto leave;
else if (*answer == '1')
pinref = "PIV.80";
else if (*answer == '2')
pinref = "PIV.81";
else if (*answer == '3')
pinref = "PIV.00";
}
}
else if (opt.interactive && info->apptype == APP_TYPE_NKS)
{
int for_qualified = 0;
menu_used = 1;
log_assert (DIM (info->chvinfo) >= 4);
/* If there is a qualified signature use a menu to select
* between standard PIN and QES PINs. */
if (info->chvinfo[2] != -2 || info->chvinfo[3] != -2)
{
for (;;)
{
xfree (answer);
answer = get_selection (" 1 - Standard PIN/PUK\n"
" 2 - PIN/PUK for qualified signature\n"
" Q - quit\n");
if (!ascii_strcasecmp (answer, "q"))
goto leave;
else if (!strcmp (answer, "1"))
break;
else if (!strcmp (answer, "2"))
{
for_qualified = 1;
break;
}
}
}
if (info->chvinfo[for_qualified? 2 : 0] == -4)
{
while (!pinref)
{
xfree (answer);
answer = get_selection
("The NullPIN is still active on this card.\n"
"You need to choose and set a PIN first.\n"
"\n"
" 1 - Set your PIN\n"
" Q - quit\n");
if (!ascii_strcasecmp (answer, "q"))
goto leave;
else if (!strcmp (answer, "1"))
{
pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH";
nullpin = 1;
}
}
}
else
{
while (!pinref)
{
xfree (answer);
answer = get_selection (" 1 - change PIN\n"
" 2 - reset PIN\n"
" 3 - change PUK\n"
" 4 - reset PUK\n"
" Q - quit\n");
if (!ascii_strcasecmp (answer, "q"))
goto leave;
else if (!strcmp (answer, "1"))
{
pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH";
}
else if (!strcmp (answer, "2"))
{
pinref = for_qualified? "PW1.CH.SIG" : "PW1.CH";
reset_mode = 1;
}
else if (!strcmp (answer, "3"))
{
pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH";
}
else if (!strcmp (answer, "4"))
{
pinref = for_qualified? "PW2.CH.SIG" : "PW2.CH";
reset_mode = 1;
}
}
}
}
else if (info->apptype == APP_TYPE_PIV)
pinref = "PIV.80";
else
{
err = gpg_error (GPG_ERR_MISSING_VALUE);
goto leave;
}
err = scd_change_pin (pinref, reset_mode, nullpin);
if (err)
{
if (!opt.interactive && !menu_used && !opt.verbose)
;
else if (gpg_err_code (err) == GPG_ERR_CANCELED
&& gpg_err_source (err) == GPG_ERR_SOURCE_PINENTRY)
log_info ("%s\n", gpg_strerror (err));
else if (!ascii_strcasecmp (pinref, "PIV.81"))
log_error ("Error changing the PUK.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
log_error ("Error unblocking the PIN.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
log_error ("Error setting the Reset Code.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
log_error ("Error changing the Admin PIN.\n");
else if (reset_mode)
log_error ("Error resetting the PIN.\n");
else
log_error ("Error changing the PIN.\n");
}
else
{
if (!opt.interactive && !opt.verbose)
;
else if (!ascii_strcasecmp (pinref, "PIV.81"))
log_info ("PUK changed.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.1") && reset_mode)
log_info ("PIN unblocked and new PIN set.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.2") && reset_mode)
log_info ("Reset Code set.\n");
else if (!ascii_strcasecmp (pinref, "OPENPGP.3"))
log_info ("Admin PIN changed.\n");
else if (reset_mode)
log_info ("PIN reset.\n");
else
log_info ("PIN changed.\n");
/* Update the CHV status. */
err = scd_getattr ("CHV-STATUS", info);
}
leave:
xfree (answer);
return err;
}
static gpg_error_t
cmd_unblock (card_info_t info)
{
gpg_error_t err = 0;
if (!info)
return print_help
("UNBLOCK\n\n"
"Unblock a PIN using a PUK or Reset Code. Note that OpenPGP\n"
"cards prior to version 2 can't use this; instead the PASSWD\n"
"command can be used to set a new PIN.",
0);
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (info->apptype == APP_TYPE_OPENPGP)
{
if (!info->is_v2)
{
log_error (_("This command is only available for version 2 cards\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
}
else if (!info->chvinfo[1])
{
log_error (_("Reset Code not or not anymore available\n"));
err = gpg_error (GPG_ERR_NO_RESET_CODE);
}
else
{
err = scd_change_pin ("OPENPGP.2", 0, 0);
if (!err)
log_info ("PIN changed.\n");
}
}
else if (info->apptype == APP_TYPE_PIV)
{
/* Unblock the Application PIN. */
err = scd_change_pin ("PIV.80", 1, 0);
if (!err)
log_info ("PIN unblocked and changed.\n");
}
else
{
log_info ("Unblocking not supported for '%s'.\n",
app_type_string (info->apptype));
err = gpg_error (GPG_ERR_NOT_IMPLEMENTED);
}
return err;
}
/* Note: On successful execution a redisplay should be scheduled. If
* this function fails the card may be in an unknown state. */
static gpg_error_t
cmd_factoryreset (card_info_t info)
{
gpg_error_t err;
char *answer = NULL;
int termstate = 0;
int any_apdu = 0;
int is_yubikey = 0;
int locked = 0;
int i;
if (!info)
return print_help
("FACTORY-RESET\n\n"
"Do a complete reset of some OpenPGP and PIV cards. This\n"
"deletes all data and keys and resets the PINs to their default.\n"
"This is mainly used by developers with scratch cards. Don't\n"
"worry, you need to confirm before the command proceeds.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
/* We support the factory reset for most OpenPGP cards and Yubikeys
* with the PIV application. */
if (info->apptype == APP_TYPE_OPENPGP)
;
else if (info->apptype == APP_TYPE_PIV
&& info->cardtype && !strcmp (info->cardtype, "yubikey"))
is_yubikey = 1;
else
return gpg_error (GPG_ERR_NOT_SUPPORTED);
/* For an OpenPGP card the code below basically does the same what
* this gpg-connect-agent script does:
*
* scd reset
* scd serialno undefined
* scd apdu 00 A4 04 00 06 D2 76 00 01 24 01
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 81 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 20 00 83 08 40 40 40 40 40 40 40 40
* scd apdu 00 e6 00 00
* scd apdu 00 44 00 00
* scd reset
* /echo Card has been reset to factory defaults
*
* For a PIV application on a Yubikey it merely issues the Yubikey
- * specific resset command.
+ * specific reset command.
*/
err = scd_learn (info, 0);
if (gpg_err_code (err) == GPG_ERR_OBJ_TERM_STATE
&& gpg_err_source (err) == GPG_ERR_SOURCE_SCD)
termstate = 1;
else if (err)
{
log_error (_("OpenPGP card not available: %s\n"), gpg_strerror (err));
goto leave;
}
if (opt.interactive || opt.verbose)
log_info (_("%s card no. %s detected\n"),
app_type_string (info->apptype),
info->dispserialno? info->dispserialno : info->serialno);
if (!termstate || is_yubikey)
{
if (!is_yubikey)
{
if (!(info->status_indicator == 3 || info->status_indicator == 5))
{
/* Note: We won't see status-indicator 3 here because it
* is not possible to select a card application in
* termination state. */
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
}
tty_printf ("\n");
log_info
(_("Note: This command destroys all keys stored on the card!\n"));
tty_printf ("\n");
xfree (answer);
answer = tty_get (_("Continue? (y/N) "));
tty_kill_prompt ();
trim_spaces (answer);
if (*answer == CONTROL_D
|| !answer_is_yes_no_default (answer, 0/*(default to no)*/))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
xfree (answer);
answer = tty_get (_("Really do a factory reset? (enter \"yes\") "));
tty_kill_prompt ();
trim_spaces (answer);
if (strcmp (answer, "yes") && strcmp (answer,_("yes")))
{
err = gpg_error (GPG_ERR_CANCELED);
goto leave;
}
if (is_yubikey)
{
/* If the PIV application is already selected, we only need to
* send the special reset APDU after having blocked PIN and
* PUK. Note that blocking the PUK is done using the
* unblock PIN command. */
any_apdu = 1;
for (i=0; i < 5; i++)
send_apdu ("0020008008FFFFFFFFFFFFFFFF", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 5; i++)
send_apdu ("002C008010FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF",
"RESET RETRY COUNTER", 0xffff, NULL, NULL);
err = send_apdu ("00FB000001FF", "YUBIKEY RESET", 0, NULL, NULL);
if (err)
goto leave;
}
else /* OpenPGP card. */
{
any_apdu = 1;
/* We need to select a card application before we can send
* APDUs to the card without scdaemon doing anything on its
* own. We then lock the connection so that other tools
* (e.g. Kleopatra) don't try a new select. */
err = send_apdu ("lock", "locking connection ", 0, NULL, NULL);
if (err)
goto leave;
locked = 1;
err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL);
if (err)
goto leave;
err = send_apdu ("undefined", "dummy select ", 0, NULL, NULL);
if (err)
goto leave;
/* Select the OpenPGP application. */
err = send_apdu ("00A4040006D27600012401", "SELECT AID", 0,
NULL, NULL);
if (err)
goto leave;
/* Do some dummy verifies with wrong PINs to set the retry
* counter to zero. We can't easily use the card version 2.1
* feature of presenting the admin PIN to allow the terminate
* command because there is no machinery in scdaemon to catch
* the verify command and ask for the PIN when the "APDU"
* command is used.
* Here, the length of dummy wrong PIN is 32-byte, also
* supporting authentication with KDF DO. */
for (i=0; i < 4; i++)
send_apdu ("0020008120"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
for (i=0; i < 4; i++)
send_apdu ("0020008320"
"40404040404040404040404040404040"
"40404040404040404040404040404040", "VERIFY", 0xffff,
NULL, NULL);
/* Send terminate datafile command. */
err = send_apdu ("00e60000", "TERMINATE DF", 0x6985, NULL, NULL);
if (err)
goto leave;
}
}
if (!is_yubikey)
{
any_apdu = 1;
/* Send activate datafile command. This is used without
* confirmation if the card is already in termination state. */
err = send_apdu ("00440000", "ACTIVATE DF", 0, NULL, NULL);
if (err)
goto leave;
}
/* Finally we reset the card reader once more. */
if (locked)
err = send_apdu ("reset-keep-lock", "reset", 0, NULL, NULL);
else
err = send_apdu (NULL, "RESET", 0, NULL, NULL);
if (err)
goto leave;
/* Then, connect the card again. */
err = scd_serialno (NULL, NULL);
if (!err)
info->need_sn_cmd = 0;
leave:
if (err && any_apdu && !is_yubikey)
{
log_info ("Due to an error the card might be in an inconsistent state\n"
"You should run the LIST command to check this.\n");
/* FIXME: We need a better solution in the case that the card is
* in a termination state, i.e. the card was removed before the
* activate was sent. The best solution I found with v2.1
* Zeitcontrol card was to kill scdaemon and the issue this
* sequence with gpg-connect-agent:
* scd reset
* scd serialno undefined
* scd apdu 00A4040006D27600012401 (returns error)
* scd apdu 00440000
* Then kill scdaemon again and issue:
* scd reset
* scd serialno openpgp
*/
}
if (locked)
send_apdu ("unlock", "unlocking connection ", 0, NULL, NULL);
xfree (answer);
return err;
}
/* Generate KDF data. This is a helper for cmd_kdfsetup. */
static gpg_error_t
gen_kdf_data (unsigned char *data, int single_salt)
{
gpg_error_t err;
const unsigned char h0[] = { 0x81, 0x01, 0x03,
0x82, 0x01, 0x08,
0x83, 0x04 };
const unsigned char h1[] = { 0x84, 0x08 };
const unsigned char h2[] = { 0x85, 0x08 };
const unsigned char h3[] = { 0x86, 0x08 };
const unsigned char h4[] = { 0x87, 0x20 };
const unsigned char h5[] = { 0x88, 0x20 };
unsigned char *p, *salt_user, *salt_admin;
unsigned char s2k_char;
unsigned int iterations;
unsigned char count_4byte[4];
p = data;
s2k_char = encode_s2k_iterations (agent_get_s2k_count ());
iterations = S2K_DECODE_COUNT (s2k_char);
count_4byte[0] = (iterations >> 24) & 0xff;
count_4byte[1] = (iterations >> 16) & 0xff;
count_4byte[2] = (iterations >> 8) & 0xff;
count_4byte[3] = (iterations & 0xff);
memcpy (p, h0, sizeof h0);
p += sizeof h0;
memcpy (p, count_4byte, sizeof count_4byte);
p += sizeof count_4byte;
memcpy (p, h1, sizeof h1);
salt_user = (p += sizeof h1);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
if (single_salt)
salt_admin = salt_user;
else
{
memcpy (p, h2, sizeof h2);
p += sizeof h2;
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
memcpy (p, h3, sizeof h3);
salt_admin = (p += sizeof h3);
gcry_randomize (p, 8, GCRY_STRONG_RANDOM);
p += 8;
}
memcpy (p, h4, sizeof h4);
p += sizeof h4;
err = gcry_kdf_derive (OPENPGP_USER_PIN_DEFAULT,
strlen (OPENPGP_USER_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_user, 8, iterations, 32, p);
p += 32;
if (!err)
{
memcpy (p, h5, sizeof h5);
p += sizeof h5;
err = gcry_kdf_derive (OPENPGP_ADMIN_PIN_DEFAULT,
strlen (OPENPGP_ADMIN_PIN_DEFAULT),
GCRY_KDF_ITERSALTED_S2K, GCRY_MD_SHA256,
salt_admin, 8, iterations, 32, p);
}
return err;
}
static gpg_error_t
cmd_kdfsetup (card_info_t info, char *argstr)
{
gpg_error_t err;
unsigned char kdf_data[OPENPGP_KDF_DATA_LENGTH_MAX];
int single = (*argstr != 0);
if (!info)
return print_help
("KDF-SETUP\n\n"
"Prepare the OpenPGP card KDF feature for this card.",
APP_TYPE_OPENPGP, 0);
if (info->apptype != APP_TYPE_OPENPGP)
{
log_info ("Note: This is an OpenPGP only command.\n");
return gpg_error (GPG_ERR_NOT_SUPPORTED);
}
if (!info->extcap.kdf)
{
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
err = gen_kdf_data (kdf_data, single);
if (err)
goto leave;
err = scd_setattr ("KDF", kdf_data,
single ? OPENPGP_KDF_DATA_LENGTH_MIN
/* */ : OPENPGP_KDF_DATA_LENGTH_MAX);
if (err)
goto leave;
err = scd_getattr ("KDF", info);
leave:
return err;
}
static void
show_keysize_warning (void)
{
static int shown;
if (shown)
return;
shown = 1;
tty_printf
(_("Note: There is no guarantee that the card supports the requested\n"
" key type or size. If the key generation does not succeed,\n"
" please check the documentation of your card to see which\n"
" key types and sizes are supported.\n")
);
}
static gpg_error_t
cmd_uif (card_info_t info, char *argstr)
{
gpg_error_t err;
int keyno;
char name[50];
unsigned char data[2];
char *answer = NULL;
int opt_yes;
if (!info)
return print_help
("UIF N [on|off|permanent]\n\n"
"Change the User Interaction Flag. N must in the range 1 to 3.",
APP_TYPE_OPENPGP, APP_TYPE_PIV, 0);
if (!info->extcap.bt)
{
log_error (_("This command is not supported by this card\n"));
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
opt_yes = has_leading_option (argstr, "--yes");
argstr = skip_options (argstr);
if (digitp (argstr))
{
keyno = atoi (argstr);
while (digitp (argstr))
argstr++;
while (spacep (argstr))
argstr++;
}
else
keyno = 0;
if (keyno < 1 || keyno > 3)
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
if ( !strcmp (argstr, "off") )
data[0] = 0x00;
else if ( !strcmp (argstr, "on") )
data[0] = 0x01;
else if ( !strcmp (argstr, "permanent") )
data[0] = 0x02;
else
{
err = gpg_error (GPG_ERR_INV_ARG);
goto leave;
}
data[1] = 0x20;
log_assert (keyno - 1 < DIM(info->uif));
if (info->uif[keyno-1] == 2)
{
log_info (_("User Interaction Flag is set to \"%s\" - can't change\n"),
"permanent");
err = gpg_error (GPG_ERR_INV_STATE);
goto leave;
}
if (data[0] == 0x02)
{
if (opt.interactive)
{
tty_printf (_("Warning: Setting the User Interaction Flag to \"%s\"\n"
" can only be reverted using a factory reset!\n"
), "permanent");
answer = tty_get (_("Continue? (y/N) "));
tty_kill_prompt ();
if (*answer == CONTROL_D)
err = gpg_error (GPG_ERR_CANCELED);
else if (!answer_is_yes_no_default (answer, 0/*(default to No)*/))
err = gpg_error (GPG_ERR_CANCELED);
else
err = 0;
}
else if (!opt_yes)
{
log_info (_("Warning: Setting the User Interaction Flag to \"%s\"\n"
" can only be reverted using a factory reset!\n"
), "permanent");
log_info (_("Please use \"uif --yes %d %s\"\n"),
keyno, "permanent");
err = gpg_error (GPG_ERR_CANCELED);
}
else
err = 0;
if (err)
goto leave;
}
snprintf (name, sizeof name, "UIF-%d", keyno);
err = scd_setattr (name, data, 2);
if (!err) /* Read all UIF attributes again. */
err = scd_getattr ("UIF", info);
leave:
xfree (answer);
return err;
}
static gpg_error_t
cmd_yubikey (card_info_t info, char *argstr)
{
gpg_error_t err, err2;
estream_t fp = opt.interactive? NULL : es_stdout;
const char *words[20];
int nwords;
if (!info)
return print_help
("YUBIKEY <cmd> args\n\n"
"Various commands pertaining to Yubikey tokens with <cmd> being:\n"
"\n"
" LIST \n"
"\n"
"List supported and enabled applications.\n"
"\n"
" ENABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
" DISABLE usb|nfc|all [otp|u2f|opgp|piv|oath|fido2|all]\n"
"\n"
"Enable or disable the specified or all applications on the\n"
"given interface.",
0);
argstr = skip_options (argstr);
if (!info->cardtype || strcmp (info->cardtype, "yubikey"))
{
log_info ("This command can only be used with Yubikeys.\n");
err = gpg_error (GPG_ERR_NOT_SUPPORTED);
goto leave;
}
nwords = split_fields (argstr, words, DIM (words));
if (nwords < 1)
{
err = gpg_error (GPG_ERR_SYNTAX);
goto leave;
}
/* Note that we always do a learn to get a chance to the card back
* into a usable state. */
err = yubikey_commands (info, fp, nwords, words);
err2 = scd_learn (info, 0);
if (err2)
log_error ("Error re-reading card: %s\n", gpg_strerror (err2));
leave:
return err;
}
static gpg_error_t
cmd_apdu (card_info_t info, char *argstr)
{
gpg_error_t err;
estream_t fp = opt.interactive? NULL : es_stdout;
int with_atr;
int handle_more;
const char *s;
const char *exlenstr;
int exlenstrlen;
char *options = NULL;
unsigned int sw;
unsigned char *result = NULL;
size_t i, j, resultlen;
if (!info)
return print_help
("APDU [--more] [--exlen[=N]] <hexstring>\n"
"\n"
"Send an APDU to the current card. This command bypasses the high\n"
"level functions and sends the data directly to the card. HEXSTRING\n"
"is expected to be a proper APDU.\n"
"\n"
"Using the option \"--more\" handles the card status word MORE_DATA\n"
"(61xx) and concatenates all responses to one block.\n"
"\n"
"Using the option \"--exlen\" the returned APDU may use extended\n"
"length up to N bytes. If N is not given a default value is used.\n",
0);
if (has_option (argstr, "--dump-atr"))
with_atr = 2;
else
with_atr = has_option (argstr, "--atr");
handle_more = has_option (argstr, "--more");
exlenstr = has_option_name (argstr, "--exlen");
exlenstrlen = 0;
if (exlenstr)
{
for (s=exlenstr; *s && !spacep (s); s++)
exlenstrlen++;
}
argstr = skip_options (argstr);
if (with_atr || handle_more || exlenstr)
options = xasprintf ("%s%s%s%.*s",
with_atr == 2? " --dump-atr":
with_atr? " --data-atr":"",
handle_more?" --more":"",
exlenstr?" --exlen=":"",
exlenstrlen, exlenstr?exlenstr:"");
err = scd_apdu (argstr, options, &sw, &result, &resultlen);
if (err)
goto leave;
if (!with_atr)
{
if (opt.interactive || opt.verbose)
{
char *p = scd_apdu_strerror (sw);
log_info ("Statusword: 0x%04x (%s)\n", sw, p? p: "?");
xfree (p);
}
else
log_info ("Statusword: 0x%04x\n", sw);
}
for (i=0; i < resultlen; )
{
size_t save_i = i;
tty_fprintf (fp, "D[%04X] ", (unsigned int)i);
for (j=0; j < 16 ; j++, i++)
{
if (j == 8)
tty_fprintf (fp, " ");
if (i < resultlen)
tty_fprintf (fp, " %02X", result[i]);
else
tty_fprintf (fp, " ");
}
tty_fprintf (fp, " ");
i = save_i;
for (j=0; j < 16; j++, i++)
{
unsigned int c = result[i];
if ( i >= resultlen )
tty_fprintf (fp, " ");
else if (isascii (c) && isprint (c) && !iscntrl (c))
tty_fprintf (fp, "%c", c);
else
tty_fprintf (fp, ".");
}
tty_fprintf (fp, "\n");
}
leave:
xfree (result);
xfree (options);
return err;
}
static gpg_error_t
cmd_gpg (card_info_t info, char *argstr, int use_gpgsm)
{
gpg_error_t err;
char **argarray;
ccparray_t ccp;
const char **argv = NULL;
gnupg_process_t proc;
int i;
if (!info)
return print_help
("GPG[SM] <commands_and_options>\n"
"\n"
"Run gpg/gpgsm directly from this shell.\n",
0);
/* Fixme: We need to write and use a version of strtokenize which
* takes care of shell-style quoting. */
argarray = strtokenize (argstr, " \t\n\v");
if (!argarray)
{
err = gpg_error_from_syserror ();
goto leave;
}
ccparray_init (&ccp, 0);
for (i=0; argarray[i]; i++)
ccparray_put (&ccp, argarray[i]);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_process_spawn (use_gpgsm? opt.gpgsm_program:opt.gpg_program,
argv,
(GNUPG_PROCESS_STDOUT_KEEP
| GNUPG_PROCESS_STDERR_KEEP),
NULL, &proc);
if (!err)
{
err = gnupg_process_wait (proc, 1);
gnupg_process_release (proc);
}
leave:
xfree (argv);
xfree (argarray);
return err;
}
static gpg_error_t
cmd_history (card_info_t info, char *argstr)
{
int opt_list, opt_clear;
opt_list = has_option (argstr, "--list");
opt_clear = has_option (argstr, "--clear");
if (!info || !(opt_list || opt_clear))
return print_help
("HISTORY --list\n"
" List the command history\n"
"HISTORY --clear\n"
" Clear the command history",
0);
if (opt_list)
tty_printf ("Sorry, history listing not yet possible\n");
if (opt_clear)
tty_read_history (NULL, 0);
return 0;
}
/* Data used by the command parser. This needs to be outside of the
* function scope to allow readline based command completion. */
enum cmdids
{
cmdNOP = 0,
cmdQUIT, cmdHELP, cmdLIST, cmdRESET, cmdVERIFY,
cmdNAME, cmdURL, cmdFETCH, cmdLOGIN, cmdLANG, cmdSALUT, cmdCAFPR,
cmdFORCESIG, cmdGENERATE, cmdPASSWD, cmdPRIVATEDO, cmdWRITECERT,
cmdREADCERT, cmdWRITEKEY, cmdUNBLOCK, cmdFACTRST, cmdKDFSETUP,
cmdUIF, cmdAUTH, cmdYUBIKEY, cmdAPDU, cmdGPG, cmdGPGSM, cmdHISTORY,
cmdCHECKKEYS,
cmdINVCMD
};
static struct
{
const char *name;
enum cmdids id;
const char *desc;
} cmds[] = {
{ "quit" , cmdQUIT, N_("quit this menu")},
{ "q" , cmdQUIT, NULL },
{ "bye" , cmdQUIT, NULL },
{ "help" , cmdHELP, N_("show this help")},
{ "?" , cmdHELP, NULL },
{ "list" , cmdLIST, N_("list all available data")},
{ "l" , cmdLIST, NULL },
{ "name" , cmdNAME, N_("change card holder's name")},
{ "url" , cmdURL, N_("change URL to retrieve key")},
{ "fetch" , cmdFETCH, N_("fetch the key specified in the card URL")},
{ "login" , cmdLOGIN, N_("change the login name")},
{ "lang" , cmdLANG, N_("change the language preferences")},
{ "salutation",cmdSALUT, N_("change card holder's salutation")},
{ "salut" , cmdSALUT, NULL },
{ "cafpr" , cmdCAFPR , N_("change a CA fingerprint")},
{ "forcesig", cmdFORCESIG, N_("toggle the signature force PIN flag")},
{ "generate", cmdGENERATE, N_("generate new keys")},
{ "passwd" , cmdPASSWD, N_("menu to change or unblock the PIN")},
{ "verify" , cmdVERIFY, N_("verify the PIN and list all data")},
{ "unblock" , cmdUNBLOCK, N_("unblock the PIN using a Reset Code")},
{ "authenticate",cmdAUTH, N_("authenticate to the card")},
{ "auth" , cmdAUTH, NULL },
{ "reset" , cmdRESET, N_("send a reset to the card daemon")},
{ "factory-reset",cmdFACTRST, N_("destroy all keys and data")},
{ "kdf-setup", cmdKDFSETUP, N_("setup KDF for PIN authentication")},
{ "uif", cmdUIF, N_("change the User Interaction Flag")},
{ "privatedo", cmdPRIVATEDO, N_("change a private data object")},
{ "readcert", cmdREADCERT, N_("read a certificate from a data object")},
{ "writecert", cmdWRITECERT, N_("store a certificate to a data object")},
{ "writekey", cmdWRITEKEY, N_("store a private key to a data object")},
{ "checkkeys", cmdCHECKKEYS, N_("run various checks on the keys")},
{ "yubikey", cmdYUBIKEY, N_("Yubikey management commands")},
{ "gpg", cmdGPG, NULL},
{ "gpgsm", cmdGPGSM, NULL},
{ "apdu", cmdAPDU, NULL},
{ "history", cmdHISTORY, N_("manage the command history")},
{ NULL, cmdINVCMD, NULL }
};
/* The command line command dispatcher. */
static gpg_error_t
dispatch_command (card_info_t info, const char *orig_command)
{
gpg_error_t err = 0;
enum cmdids cmd; /* The command. */
char *command; /* A malloced copy of ORIG_COMMAND. */
char *argstr; /* The argument as a string. */
int i;
int ignore_error;
if ((ignore_error = *orig_command == '-'))
orig_command++;
command = xstrdup (orig_command);
argstr = NULL;
if ((argstr = strchr (command, ' ')))
{
*argstr++ = 0;
trim_spaces (command);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (command, cmds[i].name ))
break;
cmd = cmds[i].id; /* (If not found this will be cmdINVCMD). */
/* Make sure we have valid strings for the args. They are allowed
* to be modified and must thus point to a buffer. */
if (!argstr)
argstr = command + strlen (command);
/* For most commands we need to make sure that we have a card. */
if (!info)
; /* Help mode */
else if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdINVCMD)
&& !info->initialized)
{
err = scd_learn (info, 0);
if (err)
{
err = fixup_scd_errors (err);
log_error ("Error reading card: %s\n", gpg_strerror (err));
goto leave;
}
}
if (info)
info->card_removed = 0;
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Stop processing.", 0);
else
{
err = gpg_error (GPG_ERR_EOF);
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
dispatch_command (NULL, argstr);
else
{
es_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
es_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
es_printf ("Prefix a command with a dash to ignore its error.\n");
}
break;
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL, NULL);
if (!err)
info->need_sn_cmd = 1;
}
break;
case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY: err = cmd_verify (info, argstr); break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST: err = cmd_factoryreset (info); break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdAPDU: err = cmd_apdu (info, argstr); break;
case cmdGPG: err = cmd_gpg (info, argstr, 0); break;
case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break;
case cmdHISTORY: err = 0; break; /* Only used in interactive mode. */
case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break;
case cmdINVCMD:
default:
log_error (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
leave:
/* Return GPG_ERR_EOF only if its origin was "quit". */
es_fflush (es_stdout);
if (gpg_err_code (err) == GPG_ERR_EOF && cmd != cmdQUIT)
err = gpg_error (GPG_ERR_GENERAL);
if (!err && info && info->card_removed)
{
info->card_removed = 0;
info->need_sn_cmd = 1;
err = gpg_error (GPG_ERR_CARD_REMOVED);
}
if (err && gpg_err_code (err) != GPG_ERR_EOF)
{
err = fixup_scd_errors (err);
if (ignore_error)
{
log_info ("Command '%s' failed: %s\n", command, gpg_strerror (err));
err = 0;
}
else
{
log_error ("Command '%s' failed: %s\n", command, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
info->need_sn_cmd = 1;
}
}
xfree (command);
return err;
}
/* The interactive main loop. */
static void
interactive_loop (void)
{
gpg_error_t err;
char *answer = NULL; /* The input line. */
enum cmdids cmd = cmdNOP; /* The command. */
char *argstr; /* The argument as a string. */
int redisplay = 1; /* Whether to redisplay the main info. */
char *help_arg = NULL; /* Argument of the HELP command. */
struct card_info_s info_buffer = { 0 };
card_info_t info = &info_buffer;
char *p;
int i;
char *historyname = NULL;
/* In the interactive mode we do not want to print the program prefix. */
log_set_prefix (NULL, 0);
if (!opt.no_history)
{
historyname = make_filename (gnupg_homedir (), HISTORYNAME, NULL);
if (tty_read_history (historyname, 500))
log_info ("error reading '%s': %s\n",
historyname, gpg_strerror (gpg_error_from_syserror ()));
}
for (;;)
{
if (help_arg)
{
/* Clear info to indicate helpmode */
info = NULL;
}
else if (!info)
{
/* Get out of help. */
info = &info_buffer;
help_arg = NULL;
redisplay = 0;
}
else if (redisplay)
{
err = cmd_list (info, "");
if (err)
{
err = fixup_scd_errors (err);
log_error ("Error reading card: %s\n", gpg_strerror (err));
}
else
{
tty_printf("\n");
redisplay = 0;
}
}
if (!info)
{
/* Copy the pending help arg into our answer. Note that
* help_arg points into answer. */
p = xstrdup (help_arg);
help_arg = NULL;
xfree (answer);
answer = p;
}
else
{
do
{
xfree (answer);
tty_enable_completion (command_completion);
answer = tty_get (_("gpg/card> "));
tty_kill_prompt();
tty_disable_completion ();
trim_spaces(answer);
}
while ( *answer == '#' );
}
argstr = NULL;
if (!*answer)
cmd = cmdLIST; /* We default to the list command */
else if (*answer == CONTROL_D)
cmd = cmdQUIT;
else
{
if ((argstr = strchr (answer,' ')))
{
*argstr++ = 0;
trim_spaces (answer);
trim_spaces (argstr);
}
for (i=0; cmds[i].name; i++ )
if (!ascii_strcasecmp (answer, cmds[i].name ))
break;
cmd = cmds[i].id;
}
/* Make sure we have valid strings for the args. They are
* allowed to be modified and must thus point to a buffer. */
if (!argstr)
argstr = answer + strlen (answer);
if (!(cmd == cmdNOP || cmd == cmdQUIT || cmd == cmdHELP
|| cmd == cmdHISTORY || cmd == cmdINVCMD))
{
/* If redisplay is set we know that there was an error reading
* the card. In this case we force a LIST command to retry. */
if (!info)
; /* In help mode. */
else if (redisplay)
{
cmd = cmdLIST;
}
else if (!info->serialno)
{
/* Without a serial number most commands won't work.
* Catch it here. */
if (cmd == cmdRESET || cmd == cmdLIST)
info->need_sn_cmd = 1;
else
{
tty_printf ("\n");
tty_printf ("Serial number missing\n");
continue;
}
}
}
if (info)
info->card_removed = 0;
err = 0;
switch (cmd)
{
case cmdNOP:
if (!info)
print_help ("NOP\n\n"
"Dummy command.", 0);
break;
case cmdQUIT:
if (!info)
print_help ("QUIT\n\n"
"Leave this tool.", 0);
else
{
tty_printf ("\n");
goto leave;
}
break;
case cmdHELP:
if (!info)
print_help ("HELP [command]\n\n"
"Show all commands. With an argument show help\n"
"for that command.", 0);
else if (*argstr)
help_arg = argstr; /* Trigger help for a command. */
else
{
tty_printf
("List of commands (\"help <command>\" for details):\n");
for (i=0; cmds[i].name; i++ )
if(cmds[i].desc)
tty_printf("%-14s %s\n", cmds[i].name, _(cmds[i].desc) );
}
break;
case cmdRESET:
if (!info)
print_help ("RESET\n\n"
"Send a RESET to the card daemon.", 0);
else
{
flush_keyblock_cache ();
err = scd_apdu (NULL, NULL, NULL, NULL, NULL);
if (!err)
info->need_sn_cmd = 1;
}
break;
case cmdLIST: err = cmd_list (info, argstr); break;
case cmdVERIFY:
err = cmd_verify (info, argstr);
if (!err)
redisplay = 1;
break;
case cmdAUTH: err = cmd_authenticate (info, argstr); break;
case cmdNAME: err = cmd_name (info, argstr); break;
case cmdURL: err = cmd_url (info, argstr); break;
case cmdFETCH: err = cmd_fetch (info); break;
case cmdLOGIN: err = cmd_login (info, argstr); break;
case cmdLANG: err = cmd_lang (info, argstr); break;
case cmdSALUT: err = cmd_salut (info, argstr); break;
case cmdCAFPR: err = cmd_cafpr (info, argstr); break;
case cmdPRIVATEDO: err = cmd_privatedo (info, argstr); break;
case cmdWRITECERT: err = cmd_writecert (info, argstr); break;
case cmdREADCERT: err = cmd_readcert (info, argstr); break;
case cmdWRITEKEY: err = cmd_writekey (info, argstr); break;
case cmdFORCESIG: err = cmd_forcesig (info); break;
case cmdGENERATE: err = cmd_generate (info, argstr); break;
case cmdPASSWD: err = cmd_passwd (info, argstr); break;
case cmdUNBLOCK: err = cmd_unblock (info); break;
case cmdFACTRST:
err = cmd_factoryreset (info);
if (!err)
redisplay = 1;
break;
case cmdKDFSETUP: err = cmd_kdfsetup (info, argstr); break;
case cmdUIF: err = cmd_uif (info, argstr); break;
case cmdYUBIKEY: err = cmd_yubikey (info, argstr); break;
case cmdAPDU: err = cmd_apdu (info, argstr); break;
case cmdGPG: err = cmd_gpg (info, argstr, 0); break;
case cmdGPGSM: err = cmd_gpg (info, argstr, 1); break;
case cmdHISTORY: err = cmd_history (info, argstr); break;
case cmdCHECKKEYS: err = cmd_checkkeys (info, argstr); break;
case cmdINVCMD:
default:
tty_printf ("\n");
tty_printf (_("Invalid command (try \"help\")\n"));
break;
} /* End command switch. */
if (!err && info && info->card_removed)
{
info->card_removed = 0;
info->need_sn_cmd = 1;
err = gpg_error (GPG_ERR_CARD_REMOVED);
}
if (gpg_err_code (err) == GPG_ERR_CANCELED)
tty_fprintf (NULL, "\n");
else if (err)
{
const char *s = "?";
for (i=0; cmds[i].name; i++ )
if (cmd == cmds[i].id)
{
s = cmds[i].name;
break;
}
err = fixup_scd_errors (err);
log_error ("Command '%s' failed: %s\n", s, gpg_strerror (err));
if (gpg_err_code (err) == GPG_ERR_CARD_NOT_PRESENT)
info->need_sn_cmd = 1;
}
} /* End of main menu loop. */
leave:
if (historyname && tty_write_history (historyname))
log_info ("error writing '%s': %s\n",
historyname, gpg_strerror (gpg_error_from_syserror ()));
release_card_info (info);
xfree (historyname);
xfree (answer);
}
#ifdef HAVE_LIBREADLINE
/* Helper function for readline's command completion. */
static char *
command_generator (const char *text, int state)
{
static int list_index, len;
const char *name;
/* If this is a new word to complete, initialize now. This includes
* saving the length of TEXT for efficiency, and initializing the
index variable to 0. */
if (!state)
{
list_index = 0;
len = strlen(text);
}
/* Return the next partial match */
while ((name = cmds[list_index].name))
{
/* Only complete commands that have help text. */
if (cmds[list_index++].desc && !strncmp (name, text, len))
return strdup(name);
}
return NULL;
}
/* Second helper function for readline's command completion. */
static char **
command_completion (const char *text, int start, int end)
{
(void)end;
/* If we are at the start of a line, we try and command-complete.
* If not, just do nothing for now. The support for help completion
* needs to be more smarter. */
if (!start)
return rl_completion_matches (text, command_generator);
else if (start == 5 && !ascii_strncasecmp (rl_line_buffer, "help ", 5))
return rl_completion_matches (text, command_generator);
rl_attempted_completion_over = 1;
return NULL;
}
#endif /*HAVE_LIBREADLINE*/
diff --git a/tools/gpg-check-pattern.c b/tools/gpg-check-pattern.c
index 2698431c9..9859074e3 100644
--- a/tools/gpg-check-pattern.c
+++ b/tools/gpg-check-pattern.c
@@ -1,632 +1,632 @@
/* gpg-check-pattern.c - A tool to check passphrases against pattern.
* Copyright (C) 2021 g10 Code GmbH
* Copyright (C) 2007 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#ifdef HAVE_LOCALE_H
# include <locale.h>
#endif
#ifdef HAVE_LANGINFO_CODESET
# include <langinfo.h>
#endif
#ifdef HAVE_DOSISH_SYSTEM
# include <fcntl.h> /* for setmode() */
#endif
#include <sys/stat.h>
#include <sys/types.h>
#include <ctype.h>
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
#include "../regexp/jimregexp.h"
enum cmd_and_opt_values
{ aNull = 0,
oVerbose = 'v',
oNull = '0',
oNoVerbose = 500,
oCheck,
oHomedir
};
/* The list of commands and options. */
static gpgrt_opt_t opts[] = {
{ 301, NULL, 0, N_("@Options:\n ") },
{ oVerbose, "verbose", 0, "verbose" },
{ oHomedir, "homedir", 2, "@" },
{ oCheck, "check", 0, "run only a syntax check on the patternfile" },
{ oNull, "null", 0, "input is expected to be null delimited" },
ARGPARSE_end ()
};
/* Global options are accessed through the usual OPT structure. */
static struct
{
int verbose;
const char *homedir;
int checkonly;
int null;
} opt;
enum {
PAT_NULL, /* Indicates end of the array. */
PAT_STRING, /* The pattern is a simple string. */
PAT_REGEX /* The pattern is an extended regular expression. */
};
/* An object to decibe an item of our pattern table. */
struct pattern_s
{
int type;
unsigned int lineno; /* Line number of the pattern file. */
unsigned int newblock; /* First pattern in a new block. */
unsigned int icase:1; /* Case insensitive match. */
unsigned int accept:1; /* In accept mode. */
unsigned int reverse:1; /* Reverse the outcome of a regexp match. */
union {
struct {
const char *string; /* Pointer to the actual string (nul termnated). */
size_t length; /* The length of this string (strlen). */
} s; /*PAT_STRING*/
struct {
/* We allocate the regex_t because this type is larger than what
we need for PAT_STRING and we expect only a few regex in a
patternfile. It would be a waste of core to have so many
unused stuff in the table. */
regex_t *regex;
} r; /*PAT_REGEX*/
} u;
};
typedef struct pattern_s pattern_t;
/*** Local prototypes ***/
static char *read_file (const char *fname, size_t *r_length);
static pattern_t *parse_pattern_file (char *data, size_t datalen);
static void process (FILE *fp, pattern_t *patarray);
/* Info function for usage(). */
static const char *
my_strusage (int level)
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "gpg-check-pattern (@GnuPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = _("Usage: gpg-check-pattern [options] patternfile (-h for help)\n");
break;
case 41:
p = _("Syntax: gpg-check-pattern [options] patternfile\n"
"Check a passphrase given on stdin against the patternfile\n");
break;
default: p = NULL;
}
return p;
}
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
char *raw_pattern;
size_t raw_pattern_length;
pattern_t *patternarray;
early_system_init ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
log_set_prefix ("gpg-check-pattern", GPGRT_LOG_WITH_PREFIX);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_INIT_SECMEM, 4096, 0);
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= ARGPARSE_FLAG_KEEP;
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oVerbose: opt.verbose++; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oCheck: opt.checkonly = 1; break;
case oNull: opt.null = 1; break;
default : pargs.err = 2; break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (log_get_errorcount(0))
exit (2);
if (argc != 1)
gpgrt_usage (1);
/* We read the entire pattern file into our memory and parse it
using a separate function. This allows us to eventually do the
reading while running setuid so that the pattern file can be
hidden from regular users. I am not sure whether this makes
sense, but lets be prepared for it. */
raw_pattern = read_file (*argv, &raw_pattern_length);
if (!raw_pattern)
exit (2);
patternarray = parse_pattern_file (raw_pattern, raw_pattern_length);
if (!patternarray)
exit (1);
if (opt.checkonly)
return 0;
#ifdef HAVE_DOSISH_SYSTEM
setmode (fileno (stdin) , O_BINARY );
#endif
process (stdin, patternarray);
return 4; /*NOTREACHED*/
}
/* Read a file FNAME into a buffer and return that malloced buffer.
Caller must free the buffer. On error NULL is returned, on success
the valid length of the buffer is stored at R_LENGTH. The returned
buffer is guaranteed to be nul terminated. */
static char *
read_file (const char *fname, size_t *r_length)
{
estream_t fp;
char *buf;
size_t buflen;
if (!strcmp (fname, "-"))
{
size_t nread, bufsize = 0;
fp = es_stdin;
es_set_binary (fp);
buf = NULL;
buflen = 0;
#define NCHUNK 8192
do
{
bufsize += NCHUNK;
if (!buf)
buf = xmalloc (bufsize+1);
else
buf = xrealloc (buf, bufsize+1);
nread = es_fread (buf+buflen, 1, NCHUNK, fp);
if (nread < NCHUNK && es_ferror (fp))
{
log_error ("error reading '[stdin]': %s\n", strerror (errno));
xfree (buf);
return NULL;
}
buflen += nread;
}
while (nread == NCHUNK);
#undef NCHUNK
}
else
{
struct stat st;
fp = es_fopen (fname, "rb");
if (!fp)
{
log_error ("can't open '%s': %s\n", fname, strerror (errno));
return NULL;
}
if (fstat (es_fileno (fp), &st))
{
log_error ("can't stat '%s': %s\n", fname, strerror (errno));
es_fclose (fp);
return NULL;
}
buflen = st.st_size;
buf = xmalloc (buflen+1);
if (buflen && es_fread (buf, buflen, 1, fp) != 1)
{
log_error ("error reading '%s': %s\n", fname, strerror (errno));
es_fclose (fp);
xfree (buf);
return NULL;
}
es_fclose (fp);
}
buf[buflen] = 0;
*r_length = buflen;
return buf;
}
static char *
get_regerror (int errcode, regex_t *compiled)
{
size_t length = regerror (errcode, compiled, NULL, 0);
char *buffer = xmalloc (length);
regerror (errcode, compiled, buffer, length);
return buffer;
}
/* Parse the pattern given in the memory aread DATA/DATALEN and return
a new pattern array. The end of the array is indicated by a NULL
entry. On error an error message is printed and the function
returns NULL. Note that the function modifies DATA and assumes
that data is nul terminated (even if this is one byte past
DATALEN). */
static pattern_t *
parse_pattern_file (char *data, size_t datalen)
{
char *p, *p2;
size_t n;
pattern_t *array;
size_t arraysize, arrayidx;
unsigned int lineno = 0;
unsigned int icase_mode = 1;
unsigned int accept_mode = 0;
- unsigned int newblock = 1; /* The first implict block. */
+ unsigned int newblock = 1; /* The first implicit block. */
/* Estimate the number of entries by counting the non-comment lines. */
arraysize = 0;
p = data;
for (n = datalen; n && (p2 = memchr (p, '\n', n)); p2++, n -= p2 - p, p = p2)
if (*p != '#')
arraysize++;
arraysize += 2; /* For the terminating NULL and a last line w/o a LF. */
array = xcalloc (arraysize, sizeof *array);
arrayidx = 0;
/* Loop over all lines. */
while (datalen && data)
{
lineno++;
p = data;
p2 = data = memchr (p, '\n', datalen);
if (p2)
{
*data++ = 0;
datalen -= data - p;
}
else
p2 = p + datalen;
log_assert (!*p2);
p2--;
while (isascii (*p) && isspace (*p))
p++;
if (*p == '#')
continue;
while (p2 > p && isascii (*p2) && isspace (*p2))
*p2-- = 0;
if (!*p)
continue;
if (!strcmp (p, "[case]"))
{
icase_mode = 0;
continue;
}
if (!strcmp (p, "[icase]"))
{
icase_mode = 1;
continue;
}
if (!strcmp (p, "[accept]"))
{
accept_mode = 1;
newblock = 1;
continue;
}
if (!strcmp (p, "[reject]"))
{
accept_mode = 0;
newblock = 1;
continue;
}
log_assert (arrayidx < arraysize);
array[arrayidx].lineno = lineno;
array[arrayidx].icase = icase_mode;
array[arrayidx].accept = accept_mode;
array[arrayidx].reverse = 0;
array[arrayidx].newblock = newblock;
newblock = 0;
if (*p == '/' || (*p == '!' && p[1] == '/'))
{
int rerr;
int reverse;
reverse = (*p == '!');
p++;
if (reverse)
p++;
array[arrayidx].type = PAT_REGEX;
if (*p && p[strlen(p)-1] == '/')
p[strlen(p)-1] = 0; /* Remove optional delimiter. */
array[arrayidx].u.r.regex = xcalloc (1, sizeof (regex_t));
array[arrayidx].reverse = reverse;
rerr = regcomp (array[arrayidx].u.r.regex, p,
(array[arrayidx].icase? REG_ICASE:0)|REG_EXTENDED);
if (rerr)
{
char *rerrbuf = get_regerror (rerr, array[arrayidx].u.r.regex);
log_error ("invalid regexp at line %u: %s\n", lineno, rerrbuf);
xfree (rerrbuf);
if (!opt.checkonly)
exit (1);
}
}
else
{
if (*p == '[')
{
static int shown;
if (!shown)
{
log_info ("future warning: do no start a string with '['"
" but use a regexp (line %u)\n", lineno);
shown = 1;
}
}
array[arrayidx].type = PAT_STRING;
array[arrayidx].u.s.string = p;
array[arrayidx].u.s.length = strlen (p);
}
arrayidx++;
}
log_assert (arrayidx < arraysize);
array[arrayidx].type = PAT_NULL;
if (lineno && newblock)
log_info ("warning: pattern list ends with a singleton"
" accept or reject tag\n");
return array;
}
/* Check whether string matches any of the pattern in PATARRAY and
returns the matching pattern item or NULL. */
static pattern_t *
match_p (const char *string, pattern_t *patarray)
{
pattern_t *pat;
int match;
int accept_match; /* Tracks matchinf state in an accept block. */
int accept_skip; /* Skip remaining patterns in an accept block. */
if (!*string)
{
if (opt.verbose)
log_info ("zero length input line - ignored\n");
return NULL;
}
accept_match = 0;
accept_skip = 0;
for (pat = patarray; pat->type != PAT_NULL; pat++)
{
match = 0;
if (pat->newblock)
accept_match = accept_skip = 0;
if (pat->type == PAT_STRING)
{
if (pat->icase)
{
if (!strcasecmp (pat->u.s.string, string))
match = 1;
}
else
{
if (!strcmp (pat->u.s.string, string))
match = 1;
}
}
else if (pat->type == PAT_REGEX)
{
int rerr;
rerr = regexec (pat->u.r.regex, string, 0, NULL, 0);
if (pat->reverse)
{
if (!rerr)
rerr = REG_NOMATCH;
else if (rerr == REG_NOMATCH)
rerr = 0;
}
if (!rerr)
match = 1;
else if (rerr != REG_NOMATCH)
{
char *rerrbuf = get_regerror (rerr, pat->u.r.regex);
log_error ("matching regexp failed: %s\n", rerrbuf);
xfree (rerrbuf);
if (pat->accept)
match = 0; /* Better indicate no match on error. */
else
match = 1; /* Better indicate a match on error. */
}
}
else
BUG ();
if (pat->accept)
{
/* Accept mode: all patterns in the accept block must match.
* Thus we need to check whether the next pattern has a
* transition and act only then. */
if (match && !accept_skip)
accept_match = 1;
else
{
accept_match = 0;
accept_skip = 1;
}
if (pat[1].type == PAT_NULL || pat[1].newblock)
{
/* Transition detected. Note that this also handles the
* end of pattern loop case. */
if (accept_match)
return pat;
/* The next is not really but we do it for clarity. */
accept_match = accept_skip = 0;
}
}
else /* Reject mode: Return true on the first match. */
{
if (match)
return pat;
}
}
return NULL;
}
/* Actual processing of the input. This function does not return an
error code but exits as soon as a match has been found. */
static void
process (FILE *fp, pattern_t *patarray)
{
char buffer[2048];
size_t idx;
int c;
unsigned long lineno = 0;
pattern_t *pat;
int last_is_accept;
idx = 0;
c = 0;
while (idx < sizeof buffer -1 && c != EOF )
{
if ((c = getc (fp)) != EOF)
buffer[idx] = c;
if ((c == '\n' && !opt.null) || (!c && opt.null) || c == EOF)
{
lineno++;
if (!opt.null)
{
while (idx && isascii (buffer[idx-1]) && isspace (buffer[idx-1]))
idx--;
}
buffer[idx] = 0;
pat = match_p (buffer, patarray);
if (pat)
{
/* Note that the accept mode works correctly only with
* one input line. */
if (opt.verbose)
log_info ("input line %lu matches pattern at line %u"
" - %s\n",
lineno, pat->lineno,
pat->accept? "accepted":"rejected");
}
idx = 0;
wipememory (buffer, sizeof buffer);
if (pat)
{
if (pat->accept)
exit (0);
else
exit (1);
}
}
else
idx++;
}
wipememory (buffer, sizeof buffer);
if (c != EOF)
{
log_error ("input line %lu too long - rejected\n", lineno+1);
exit (1);
}
if (ferror (fp))
{
log_error ("input read error at line %lu: %s - rejected\n",
lineno+1, strerror (errno));
exit (1);
}
/* Check last pattern to see whether we are in accept mode. */
last_is_accept = 0;
for (pat = patarray; pat->type != PAT_NULL; pat++)
last_is_accept = pat->accept;
if (opt.verbose)
log_info ("no input line matches the pattern - %s\n",
last_is_accept? "rejected":"accepted");
if (log_get_errorcount(0))
exit (2); /* Ooops - reject. */
else if (last_is_accept)
exit (1); /* Reject */
else
exit (0); /* Accept */
}
diff --git a/tools/gpgconf-comp.c b/tools/gpgconf-comp.c
index b54b52c69..adfadf7d7 100644
--- a/tools/gpgconf-comp.c
+++ b/tools/gpgconf-comp.c
@@ -1,3625 +1,3625 @@
/* gpgconf-comp.c - Configuration utility for GnuPG.
* Copyright (C) 2004, 2007-2011 Free Software Foundation, Inc.
* Copyright (C) 2016 Werner Koch
* Copyright (C) 2020, 2021 g10 Code GmbH
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful, but
* WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with GnuPG; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#if HAVE_CONFIG_H
#include <config.h>
#endif
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <errno.h>
#include <time.h>
#include <stdarg.h>
#ifdef HAVE_SIGNAL_H
# include <signal.h>
#endif
#include <ctype.h>
#ifdef HAVE_W32_SYSTEM
# define WIN32_LEAN_AND_MEAN 1
# include <windows.h>
#else
# include <pwd.h>
# include <grp.h>
#endif
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/status.h"
#include "../common/gc-opt-flags.h"
#include "gpgconf.h"
#if (__GNUC__ > 2 || (__GNUC__ == 2 && __GNUC_MINOR__ >= 5 ))
void gc_error (int status, int errnum, const char *fmt, ...) \
__attribute__ ((format (printf, 3, 4)));
#endif
/* Output a diagnostic message. If ERRNUM is not 0, then the output
is followed by a colon, a white space, and the error string for the
error number ERRNUM. In any case the output is finished by a
newline. The message is prepended by the program name, a colon,
and a whitespace. The output may be further formatted or
redirected by the jnlib logging facility. */
void
gc_error (int status, int errnum, const char *fmt, ...)
{
va_list arg_ptr;
va_start (arg_ptr, fmt);
log_logv (GPGRT_LOGLVL_ERROR, fmt, arg_ptr);
va_end (arg_ptr);
if (errnum)
log_printf (": %s\n", strerror (errnum));
else
log_printf ("\n");
if (status)
{
log_printf (NULL);
log_printf ("fatal error (exit status %i)\n", status);
gpgconf_failure (gpg_error_from_errno (errnum));
}
}
/* Forward declaration. */
static void gpg_agent_runtime_change (int killflag);
static void scdaemon_runtime_change (int killflag);
#ifdef BUILD_WITH_TPM2D
static void tpm2daemon_runtime_change (int killflag);
#endif
static void dirmngr_runtime_change (int killflag);
static void keyboxd_runtime_change (int killflag);
/* STRING_ARRAY is a malloced array with malloced strings. It is used
* a space to store strings so that other objects may point to these
- * strings. It shall never be shrinked or any items changes.
+ * strings. It shall never be shrunk or any items changed.
* STRING_ARRAY itself may be reallocated to increase the size of the
* table. STRING_ARRAY_USED is the number of items currently used,
* STRING_ARRAY_SIZE is the number of calloced slots. */
static char **string_array;
static size_t string_array_used;
static size_t string_array_size;
/* Option configuration. */
/* An option might take an argument, or not. Argument types can be
basic or complex. Basic types are generic and easy to validate.
Complex types provide more specific information about the intended
use, but can be difficult to validate. If you add to this enum,
don't forget to update GC_ARG_TYPE below. YOU MUST NOT CHANGE THE
NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE EXTERNAL
INTERFACE. */
typedef enum
{
/* Basic argument types. */
/* No argument. */
GC_ARG_TYPE_NONE = 0,
/* A String argument. */
GC_ARG_TYPE_STRING = 1,
/* A signed integer argument. */
GC_ARG_TYPE_INT32 = 2,
/* An unsigned integer argument. */
GC_ARG_TYPE_UINT32 = 3,
/* ADD NEW BASIC TYPE ENTRIES HERE. */
/* Complex argument types. */
/* A complete filename. */
GC_ARG_TYPE_FILENAME = 32,
/* An LDAP server in the format
HOSTNAME:PORT:USERNAME:PASSWORD:BASE_DN. */
GC_ARG_TYPE_LDAP_SERVER = 33,
/* A 40 character fingerprint. */
GC_ARG_TYPE_KEY_FPR = 34,
/* A user ID or key ID or fingerprint for a certificate. */
GC_ARG_TYPE_PUB_KEY = 35,
/* A user ID or key ID or fingerprint for a certificate with a key. */
GC_ARG_TYPE_SEC_KEY = 36,
/* A alias list made up of a key, an equal sign and a space
separated list of values. */
GC_ARG_TYPE_ALIAS_LIST = 37,
/* ADD NEW COMPLEX TYPE ENTRIES HERE. */
/* The number of the above entries. */
GC_ARG_TYPE_NR
} gc_arg_type_t;
/* For every argument, we record some information about it in the
following struct. */
static const struct
{
/* For every argument type exists a basic argument type that can be
used as a fallback for input and validation purposes. */
gc_arg_type_t fallback;
/* Human-readable name of the type. */
const char *name;
} gc_arg_type[GC_ARG_TYPE_NR] =
{
/* The basic argument types have their own types as fallback. */
{ GC_ARG_TYPE_NONE, "none" },
{ GC_ARG_TYPE_STRING, "string" },
{ GC_ARG_TYPE_INT32, "int32" },
{ GC_ARG_TYPE_UINT32, "uint32" },
/* Reserved basic type entries for future extension. */
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
{ GC_ARG_TYPE_NR, NULL }, { GC_ARG_TYPE_NR, NULL },
/* The complex argument types have a basic type as fallback. */
{ GC_ARG_TYPE_STRING, "filename" },
{ GC_ARG_TYPE_STRING, "ldap server" },
{ GC_ARG_TYPE_STRING, "key fpr" },
{ GC_ARG_TYPE_STRING, "pub key" },
{ GC_ARG_TYPE_STRING, "sec key" },
{ GC_ARG_TYPE_STRING, "alias list" },
};
/* Every option has an associated expert level, than can be used to
hide advanced and expert options from beginners. If you add to
this list, don't forget to update GC_LEVEL below. YOU MUST NOT
CHANGE THE NUMBERS OF THE EXISTING ENTRIES, AS THEY ARE PART OF THE
EXTERNAL INTERFACE. */
typedef enum
{
/* The basic options should always be displayed. */
GC_LEVEL_BASIC,
/* The advanced options may be hidden from beginners. */
GC_LEVEL_ADVANCED,
/* The expert options should only be displayed to experts. */
GC_LEVEL_EXPERT,
/* The invisible options should normally never be displayed. */
GC_LEVEL_INVISIBLE,
/* The internal options are never exported, they mark options that
are recorded for internal use only. */
GC_LEVEL_INTERNAL,
/* ADD NEW ENTRIES HERE. */
/* The number of the above entries. */
GC_LEVEL_NR
} gc_expert_level_t;
/* A description for each expert level. */
static const struct
{
const char *name;
} gc_level[] =
{
{ "basic" },
{ "advanced" },
{ "expert" },
{ "invisible" },
{ "internal" }
};
/* Option flags. The flags which are used by the components are defined
by gc-opt-flags.h, included above.
YOU MUST NOT CHANGE THE NUMBERS OF THE EXISTING FLAGS, AS THEY ARE
PART OF THE EXTERNAL INTERFACE. */
/* Some entries in the emitted option list are not options, but mark
the beginning of a new group of options. These entries have the
GROUP flag set. Note that this is internally also known as a
header line. */
#define GC_OPT_FLAG_GROUP (1UL << 0)
/* The ARG_OPT flag for an option indicates that the argument is
optional. This is never set for GC_ARG_TYPE_NONE options. */
#define GC_OPT_FLAG_ARG_OPT (1UL << 1)
/* The LIST flag for an option indicates that the option can occur
several times. A comma separated list of arguments is used as the
argument value. */
#define GC_OPT_FLAG_LIST (1UL << 2)
/* The RUNTIME flag for an option indicates that the option can be
changed at runtime. */
#define GC_OPT_FLAG_RUNTIME (1UL << 3)
/* A human-readable description for each flag. */
static const struct
{
const char *name;
} gc_flag[] =
{
{ "group" },
{ "optional arg" },
{ "list" },
{ "runtime" },
{ "default" },
{ "default desc" },
{ "no arg desc" },
{ "no change" }
};
/* Each option we want to support in gpgconf has the needed
- * information in a static list per componenet. This struct describes
+ * information in a static list per component. This struct describes
* the info for a single option. */
struct known_option_s
{
/* If this is NULL, then this is a terminator in an array of unknown
* length. Otherwise it is the name of the option described by this
* entry. The name must not contain a colon. */
const char *name;
/* The option flags. */
unsigned long flags;
/* The expert level. */
gc_expert_level_t level;
/* The complex type of the option argument; the default of 0 is used
* for a standard type as returned by --dump-option-table. */
gc_arg_type_t arg_type;
};
typedef struct known_option_s known_option_t;
/* The known options of the GC_COMPONENT_GPG_AGENT component. */
static known_option_t known_options_gpg_agent[] =
{
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "disable-scdaemon", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "enable-ssh-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "ssh-fingerprint-digest", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "enable-putty-support", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "enable-extended-key-format", GC_OPT_FLAG_RUNTIME, GC_LEVEL_INVISIBLE },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED},
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
/**/ GC_ARG_TYPE_FILENAME },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "default-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "default-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ "max-cache-ttl", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "max-cache-ttl-ssh", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "ignore-cache-for-signing", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "allow-emacs-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ "grab", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "no-allow-external-cache", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "no-allow-mark-trusted", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ "no-allow-loopback-pinentry", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "enforce-passphrase-constraints", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "min-passphrase-len", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ "min-passphrase-nonalpha", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "check-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
/**/ GC_ARG_TYPE_FILENAME },
{ "check-sym-passphrase-pattern", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT,
/**/ GC_ARG_TYPE_FILENAME },
{ "max-passphrase-days", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "enable-passphrase-history", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "pinentry-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ NULL }
};
/* The known options of the GC_COMPONENT_SCDAEMON component. */
static known_option_t known_options_scdaemon[] =
{
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "reader-port", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "ctapi-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ "pcsc-driver", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ "disable-ccid", GC_OPT_FLAG_RUNTIME, GC_LEVEL_EXPERT },
{ "disable-pinpad", GC_OPT_FLAG_NONE|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "enable-pinpad-varlen", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "card-timeout", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "application-priority", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED},
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_FILENAME },
{ "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ NULL }
};
#ifdef BUILD_WITH_TPM2D
/* The known options of the GC_COMPONENT_TPM2DAEMON component. */
static known_option_t known_options_tpm2daemon[] =
{
{ "verbose", GC_OPT_FLAG_LIST|GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "debug-level", GC_OPT_FLAG_ARG_OPT|GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED},
{ "log-file", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_FILENAME },
{ "deny-admin", GC_OPT_FLAG_RUNTIME, GC_LEVEL_BASIC },
{ "parent", GC_OPT_FLAG_RUNTIME, GC_LEVEL_ADVANCED },
{ NULL }
};
#endif
/* The known options of the GC_COMPONENT_GPG component. */
static known_option_t known_options_gpg[] =
{
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "group", GC_OPT_FLAG_LIST, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_ALIAS_LIST},
{ "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
{ "default-new-key-algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "trust-model", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_FILENAME },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "auto-key-locate", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "auto-key-import", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "auto-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
{ "include-key-block", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
{ "max-cert-depth", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "completes-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "marginals-needed", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
/* The next items are pseudo options which we read via --gpgconf-list.
* The meta information is taken from the table below. */
{ "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "compliance_de_vs", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "use_keyboxd", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ NULL }
};
static const char *known_pseudo_options_gpg[] =
{/* v-- ARGPARSE_TYPE_STRING */
"default_pubkey_algo:0:2:@:",
/* A basic compliance check for gpg. We use gpg here but the
* result is valid for all components.
* v-- ARGPARSE_TYPE_INT */
"compliance_de_vs:0:1:@:",
/* True is use_keyboxd is enabled. That option can be set in
- * common.conf but is not direcly supported by gpgconf. Thus we
+ * common.conf but is not directly supported by gpgconf. Thus we
* only allow to read it out.
* v-- ARGPARSE_TYPE_INT */
"use_keyboxd:0:1:@:",
NULL
};
/* The known options of the GC_COMPONENT_GPGSM component. */
static known_option_t known_options_gpgsm[] =
{
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "default-key", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "encrypt-to", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "disable-dirmngr", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
{ "p12-charset", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "keyserver", GC_OPT_FLAG_LIST, GC_LEVEL_INVISIBLE,
GC_ARG_TYPE_LDAP_SERVER },
{ "compliance", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_FILENAME },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "disable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "enable-crl-checks", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "enable-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "include-certs", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
{ "disable-policy-checks", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "auto-issuer-key-retrieve", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "cipher-algo", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "disable-trusted-cert-crl-check", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
/* Pseudo option follows. See also table below. */
{ "default_pubkey_algo", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ NULL }
};
static const char *known_pseudo_options_gpgsm[] =
{/* v-- ARGPARSE_TYPE_STRING */
"default_pubkey_algo:0:2:@:",
NULL
};
/* The known options of the GC_COMPONENT_DIRMNGR component. */
static known_option_t known_options_dirmngr[] =
{
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "no-greeting", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "resolver-timeout", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "nameserver", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "debug-level", GC_OPT_FLAG_ARG_OPT, GC_LEVEL_ADVANCED },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_FILENAME },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ "batch", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "force", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "use-tor", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "keyserver", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "ldapserver", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC,
GC_ARG_TYPE_LDAP_SERVER },
{ "disable-http", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "ignore-http-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "honor-http-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "disable-ldap", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "ignore-ldap-dp", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "only-ldap-proxy", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "add-servers", GC_OPT_FLAG_NONE, GC_LEVEL_EXPERT },
{ "ldaptimeout", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "max-replies", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "allow-ocsp", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "ocsp-responder", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "ocsp-signer", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ "allow-version-check", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "ignore-ocsp-service-url", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED },
{ NULL }
};
/* The known options of the GC_COMPONENT_KEYBOXD component. */
static known_option_t known_options_keyboxd[] =
{
{ "verbose", GC_OPT_FLAG_LIST, GC_LEVEL_BASIC },
{ "quiet", GC_OPT_FLAG_NONE, GC_LEVEL_BASIC },
{ "log-file", GC_OPT_FLAG_NONE, GC_LEVEL_ADVANCED,
GC_ARG_TYPE_FILENAME },
{ "faked-system-time", GC_OPT_FLAG_NONE, GC_LEVEL_INVISIBLE },
{ NULL }
};
/* The known options of the GC_COMPONENT_PINENTRY component. */
static known_option_t known_options_pinentry[] =
{
{ NULL }
};
/* Our main option info object. We copy all required information from the
* gpgrt_opt_t items but convert the flags value to bit flags. */
struct gc_option_s
{
const char *name; /* The same as gpgrt_opt_t.long_opt. */
const char *desc; /* The same as gpgrt_opt_t.description. */
unsigned int is_header:1; /* This is a header item. */
unsigned int is_list:1; /* This is a list style option. */
unsigned int opt_arg:1; /* The option's argument is optional. */
unsigned int runtime:1; /* The option is runtime changeable. */
unsigned int gpgconf_list:1; /* Mentioned by --gpgconf-list. */
unsigned int has_default:1; /* The option has a default value. */
- unsigned int def_in_desc:1; /* The default is in the descrition. */
+ unsigned int def_in_desc:1; /* The default is in the description. */
unsigned int no_arg_desc:1; /* The argument has a default ???. */
unsigned int no_change:1; /* User shall not change the option. */
unsigned int attr_ignore:1; /* The ARGPARSE_ATTR_IGNORE. */
unsigned int attr_force:1; /* The ARGPARSE_ATTR_FORCE. */
/* The expert level - copied from known_options. */
gc_expert_level_t level;
/* The complex type - copied from known_options. */
gc_arg_type_t arg_type;
/* The default value for this option. This is NULL if the option is
not present in the component, the empty string if no default is
available, and otherwise a quoted string. This is currently
malloced.*/
char *default_value;
/* The current value of this option. */
char *value;
/* The new flags for this option. The only defined flag is actually
GC_OPT_FLAG_DEFAULT, and it means that the option should be
deleted. In this case, NEW_VALUE is NULL. */
unsigned long new_flags;
/* The new value of this option. */
char *new_value;
};
typedef struct gc_option_s gc_option_t;
/* The information associated with each component. */
static struct
{
/* The name of the component. Some components don't have an
* associated program, but are implemented directly by GPGConf. In
* this case, PROGRAM is NULL. */
char *program;
/* The displayed name of this component. Must not contain a colon
* (':') character. */
const char *name;
/* The gettext domain for the description DESC. If this is NULL,
then the description is not translated. */
const char *desc_domain;
/* The description of this component. */
const char *desc;
/* The module name (GNUPG_MODULE_NAME_foo) as defined by
* ../common/util.h. This value is used to get the actual installed
* path of the program. 0 is used if no program for the component
* is available. */
char module_name;
/* The name for the configuration filename of this component. */
const char *option_config_filename;
/* The static table of known options for this component. */
known_option_t *known_options;
/* The static table of known pseudo options for this component or NULL. */
const char **known_pseudo_options;
/* The runtime change callback. If KILLFLAG is true the component
is killed and not just reloaded. */
void (*runtime_change) (int killflag);
/* The table of known options as read from the component including
* header lines and such. This is suitable to be passed to
* gpgrt_argparser. Will be filled in by
* retrieve_options_from_program. */
gpgrt_opt_t *opt_table;
/* The full table including data from OPT_TABLE. The end of the
* table is marked by NULL entry for NAME. Will be filled in by
* retrieve_options_from_program. */
gc_option_t *options;
} gc_component[GC_COMPONENT_NR] =
{
/* Note: The order of the items must match the order given in the
* gc_component_id_t enumeration. The order is often used by
* frontends to display the backend options thus do not change the
* order without considering the user experience. */
{ NULL }, /* DUMMY for GC_COMPONENT_ANY */
{ GPG_NAME, GPG_DISP_NAME, "gnupg", N_("OpenPGP"),
GNUPG_MODULE_NAME_GPG, GPG_NAME ".conf",
known_options_gpg, known_pseudo_options_gpg },
{ GPGSM_NAME, GPGSM_DISP_NAME, "gnupg", N_("S/MIME"),
GNUPG_MODULE_NAME_GPGSM, GPGSM_NAME ".conf",
known_options_gpgsm, known_pseudo_options_gpgsm },
{ KEYBOXD_NAME, KEYBOXD_DISP_NAME, "gnupg", N_("Public Keys"),
GNUPG_MODULE_NAME_KEYBOXD, KEYBOXD_NAME ".conf",
known_options_keyboxd, NULL, keyboxd_runtime_change },
{ GPG_AGENT_NAME, GPG_AGENT_DISP_NAME, "gnupg", N_("Private Keys"),
GNUPG_MODULE_NAME_AGENT, GPG_AGENT_NAME ".conf",
known_options_gpg_agent, NULL, gpg_agent_runtime_change },
{ SCDAEMON_NAME, SCDAEMON_DISP_NAME, "gnupg", N_("Smartcards"),
GNUPG_MODULE_NAME_SCDAEMON, SCDAEMON_NAME ".conf",
known_options_scdaemon, NULL, scdaemon_runtime_change},
#ifdef BUILD_WITH_TPM2D
{ TPM2DAEMON_NAME, TPM2DAEMON_DISP_NAME, "gnupg", N_("TPM"),
GNUPG_MODULE_NAME_TPM2DAEMON, TPM2DAEMON_NAME ".conf",
known_options_tpm2daemon, NULL, tpm2daemon_runtime_change},
#else
{ NULL }, /* DUMMY to keep the table in-sync with enums */
#endif
{ DIRMNGR_NAME, DIRMNGR_DISP_NAME, "gnupg", N_("Network"),
GNUPG_MODULE_NAME_DIRMNGR, DIRMNGR_NAME ".conf",
known_options_dirmngr, NULL, dirmngr_runtime_change },
{ "pinentry", "Pinentry", "gnupg", N_("Passphrase Entry"),
GNUPG_MODULE_NAME_PINENTRY, NULL,
known_options_pinentry }
};
/* Structure used to collect error output of the component programs. */
struct error_line_s;
typedef struct error_line_s *error_line_t;
struct error_line_s
{
error_line_t next; /* Link to next item. */
const char *fname; /* Name of the config file (points into BUFFER). */
unsigned int lineno; /* Line number of the config file. */
const char *errtext; /* Text of the error message (points into BUFFER). */
char buffer[1]; /* Helper buffer. */
};
/* Initialization and finalization. */
static void
gc_option_free (gc_option_t *o)
{
if (o == NULL || o->name == NULL)
return;
xfree (o->value);
gc_option_free (o + 1);
}
static void
gc_components_free (void)
{
int i;
for (i = 0; i < DIM (gc_component); i++)
gc_option_free (gc_component[i].options);
}
void
gc_components_init (void)
{
atexit (gc_components_free);
}
/* Engine specific support. */
static void
gpg_agent_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[5];
gnupg_process_t proc = NULL;
int i = 0;
int cmdidx;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
argv[i++] = "--no-autostart";
cmdidx = i;
argv[i++] = killflag? "KILLAGENT" : "RELOADAGENT";
argv[i] = NULL;
log_assert (i < DIM(argv));
if (!err)
err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc);
if (!err)
err = gnupg_process_wait (proc, 1);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[cmdidx], gpg_strerror (err));
gnupg_process_release (proc);
}
static void
scdaemon_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[9];
gnupg_process_t proc = NULL;
int i = 0;
int cmdidx;
(void)killflag; /* For scdaemon kill and reload are synonyms. */
/* We use "GETINFO app_running" to see whether the agent is already
running and kill it only in this case. This avoids an explicit
starting of the agent in case it is not yet running. There is
obviously a race condition but that should not harm too much. */
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
argv[i++] = "-s";
argv[i++] = "--no-autostart";
argv[i++] = "GETINFO scd_running";
argv[i++] = "/if ${! $?}";
cmdidx = i;
argv[i++] = "scd killscd";
argv[i++] = "/end";
argv[i] = NULL;
log_assert (i < DIM(argv));
if (!err)
err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc);
if (!err)
err = gnupg_process_wait (proc, 1);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[cmdidx], gpg_strerror (err));
gnupg_process_release (proc);
}
#ifdef BUILD_WITH_TPM2D
static void
tpm2daemon_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[9];
gnupg_process_t proc = NULL;
int i = 0;
int cmdidx;
(void)killflag; /* For scdaemon kill and reload are synonyms. */
/* We use "GETINFO app_running" to see whether the agent is already
running and kill it only in this case. This avoids an explicit
starting of the agent in case it is not yet running. There is
obviously a race condition but that should not harm too much. */
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
argv[i++] = "-s";
argv[i++] = "--no-autostart";
argv[i++] = "GETINFO tpm2d_running";
argv[i++] = "/if ${! $?}";
cmdidx = i;
argv[i++] = "scd killtpm2cd";
argv[i++] = "/end";
argv[i] = NULL;
log_assert (i < DIM(argv));
if (!err)
err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc);
if (!err)
err = gnupg_process_wait (proc, 1);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[cmdidx], gpg_strerror (err));
gnupg_process_release (proc);
}
#endif
static void
dirmngr_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[6];
gnupg_process_t proc = NULL;
int i = 0;
int cmdidx;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
argv[i++] = "--no-autostart";
argv[i++] = "--dirmngr";
cmdidx = i;
argv[i++] = killflag? "KILLDIRMNGR" : "RELOADDIRMNGR";
argv[i] = NULL;
log_assert (i < DIM(argv));
if (!err)
err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc);
if (!err)
err = gnupg_process_wait (proc, 1);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[cmdidx], gpg_strerror (err));
gnupg_process_release (proc);
}
static void
keyboxd_runtime_change (int killflag)
{
gpg_error_t err = 0;
const char *pgmname;
const char *argv[6];
gnupg_process_t proc = NULL;
int i = 0;
int cmdidx;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
argv[i++] = "--no-autostart";
argv[i++] = "--keyboxd";
cmdidx = i;
argv[i++] = killflag? "KILLKEYBOXD" : "RELOADKEYBOXD";
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
argv[i] = NULL;
log_assert (i < DIM(argv));
if (!err)
err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc);
if (!err)
err = gnupg_process_wait (proc, 1);
if (err)
gc_error (0, 0, "error running '%s %s': %s",
pgmname, argv[cmdidx], gpg_strerror (err));
gnupg_process_release (proc);
}
/* Launch the gpg-agent or the dirmngr if not already running. */
gpg_error_t
gc_component_launch (int component)
{
gpg_error_t err;
const char *pgmname;
const char *argv[6];
int i;
gnupg_process_t proc = NULL;
if (component < 0)
{
err = gc_component_launch (GC_COMPONENT_GPG_AGENT);
if (!err)
err = gc_component_launch (GC_COMPONENT_KEYBOXD);
if (!err)
err = gc_component_launch (GC_COMPONENT_DIRMNGR);
return err;
}
if (!(component == GC_COMPONENT_GPG_AGENT
|| component == GC_COMPONENT_KEYBOXD
|| component == GC_COMPONENT_DIRMNGR))
{
log_error ("%s\n", _("Component not suitable for launching"));
gpgconf_failure (0);
}
if (gc_component_check_options (component, NULL, NULL))
{
log_error (_("Configuration file of component %s is broken\n"),
gc_component[component].name);
if (!opt.quiet)
log_info (_("Note: Use the command \"%s%s\" to get details.\n"),
gc_component[component].program
? gc_component[component].program
: gc_component[component].name,
" --gpgconf-test");
gpgconf_failure (0);
}
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_CONNECT_AGENT);
i = 0;
if (!gnupg_default_homedir_p ())
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
if (component == GC_COMPONENT_DIRMNGR)
argv[i++] = "--dirmngr";
else if (component == GC_COMPONENT_KEYBOXD)
argv[i++] = "--keyboxd";
argv[i++] = "NOP";
argv[i] = NULL;
log_assert (i < DIM(argv));
err = gnupg_process_spawn (pgmname, argv, 0, NULL, &proc);
if (!err)
err = gnupg_process_wait (proc, 1);
if (err)
gc_error (0, 0, "error running '%s%s%s': %s",
pgmname,
component == GC_COMPONENT_DIRMNGR? " --dirmngr"
: component == GC_COMPONENT_KEYBOXD? " --keyboxd":"",
" NOP",
gpg_strerror (err));
gnupg_process_release (proc);
return err;
}
static void
do_runtime_change (int component, int killflag)
{
int runtime[GC_COMPONENT_NR] = { 0 };
if (component < 0)
{
for (component = 0; component < GC_COMPONENT_NR; component++)
runtime [component] = 1;
}
else
{
log_assert (component >= 0 && component < GC_COMPONENT_NR);
runtime [component] = 1;
}
/* Do the restart for the selected components. */
for (component = GC_COMPONENT_NR-1; component >= 0; component--)
{
if (runtime[component] && gc_component[component].runtime_change)
(*gc_component[component].runtime_change) (killflag);
}
}
/* Unconditionally restart COMPONENT. */
void
gc_component_kill (int component)
{
do_runtime_change (component, 1);
}
/* Unconditionally reload COMPONENT or all components if COMPONENT is -1. */
void
gc_component_reload (int component)
{
do_runtime_change (component, 0);
}
/* More or less Robust version of dgettext. It has the side effect of
switching the codeset to utf-8 because this is what we want to
output. In theory it is possible to keep the original code set and
switch back for regular diagnostic output (redefine "_(" for that)
but given the nature of this tool, being something invoked from
other programs, it does not make much sense. */
static const char *
my_dgettext (const char *domain, const char *msgid)
{
if (!msgid || !*msgid)
return msgid; /* Shortcut form "" which has the PO files meta data. */
#ifdef USE_SIMPLE_GETTEXT
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
gettext_use_utf8 (1);
}
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
/* FIXME: we have no dgettext, thus we can't switch. */
text = (char*)gettext (msgid);
return text ? text : msgid;
}
else
return msgid;
#elif defined(ENABLE_NLS)
if (domain)
{
static int switched_codeset;
char *text;
if (!switched_codeset)
{
switched_codeset = 1;
bind_textdomain_codeset (PACKAGE_GT, "utf-8");
bindtextdomain (DIRMNGR_NAME, gnupg_localedir ());
bind_textdomain_codeset (DIRMNGR_NAME, "utf-8");
}
/* Note: This is a hack to actually use the gnupg2 domain as
long we are in a transition phase where gnupg 1.x and 1.9 may
coexist. */
if (!strcmp (domain, "gnupg"))
domain = PACKAGE_GT;
text = dgettext (domain, msgid);
return text ? text : msgid;
}
else
return msgid;
#else
(void)domain;
return msgid;
#endif
}
/* Percent-Escape special characters. The string is valid until the
next invocation of the function. */
char *
gc_percent_escape (const char *src)
{
static char *esc_str;
static int esc_str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (esc_str_len < new_len)
{
char *new_esc_str = xrealloc (esc_str, new_len);
esc_str = new_esc_str;
esc_str_len = new_len;
}
dst = esc_str;
while (*src)
{
if (*src == '%')
{
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = '5';
}
else if (*src == ':')
{
/* The colon is used as field separator. */
*(dst++) = '%';
*(dst++) = '3';
*(dst++) = 'a';
}
else if (*src == ',')
{
/* The comma is used as list separator. */
*(dst++) = '%';
*(dst++) = '2';
*(dst++) = 'c';
}
else if (*src == '\n')
{
/* The newline is problematic in a line-based format. */
*(dst++) = '%';
*(dst++) = '0';
*(dst++) = 'a';
}
else
*(dst++) = *(src);
src++;
}
*dst = '\0';
return esc_str;
}
/* Percent-Deescape special characters. The string is valid until the
next invocation of the function. */
static char *
percent_deescape (const char *src)
{
static char *str;
static int str_len;
int new_len = 3 * strlen (src) + 1;
char *dst;
if (str_len < new_len)
{
char *new_str = xrealloc (str, new_len);
str = new_str;
str_len = new_len;
}
dst = str;
while (*src)
{
if (*src == '%')
{
int val = hextobyte (src + 1);
if (val < 0)
gc_error (1, 0, "malformed end of string %s", src);
*(dst++) = (char) val;
src += 3;
}
else
*(dst++) = *(src++);
}
*dst = '\0';
return str;
}
/* List all components that are available. */
void
gc_component_list_components (estream_t out)
{
gc_component_id_t component;
const char *desc;
const char *pgmname;
for (component = 0; component < GC_COMPONENT_NR; component++)
{
if (!gc_component[component].program)
continue;
if (gc_component[component].module_name)
pgmname = gnupg_module_name (gc_component[component].module_name);
else
pgmname = "";
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].program, gc_percent_escape (desc));
es_fprintf (out, "%s\n", gc_percent_escape (pgmname));
}
}
static int
all_digits_p (const char *p, size_t len)
{
if (!len)
return 0; /* No. */
for (; len; len--, p++)
if (!isascii (*p) || !isdigit (*p))
return 0; /* No. */
return 1; /* Yes. */
}
/* Collect all error lines from stream FP. Only lines prefixed with
TAG are considered. Returns a list of error line items (which may
be empty). There is no error return. */
static error_line_t
collect_error_output (estream_t fp, const char *tag)
{
char buffer[1024];
char *p, *p2, *p3;
int c, cont_line;
unsigned int pos;
error_line_t eitem, errlines, *errlines_tail;
size_t taglen = strlen (tag);
errlines = NULL;
errlines_tail = &errlines;
pos = 0;
cont_line = 0;
while ((c=es_getc (fp)) != EOF)
{
buffer[pos++] = c;
if (pos >= sizeof buffer - 5 || c == '\n')
{
buffer[pos - (c == '\n')] = 0;
if (cont_line)
; /*Ignore continuations of previous line. */
else if (!strncmp (buffer, tag, taglen) && buffer[taglen] == ':')
{
/* "gpgsm: foo:4: bla" */
/* Yep, we are interested in this line. */
p = buffer + taglen + 1;
while (*p == ' ' || *p == '\t')
p++;
trim_trailing_spaces (p); /* Get rid of extra CRs. */
if (!*p)
; /* Empty lines are ignored. */
else if ( (p2 = strchr (p, ':')) && (p3 = strchr (p2+1, ':'))
&& all_digits_p (p2+1, p3 - (p2+1)))
{
/* Line in standard compiler format. */
p3++;
while (*p3 == ' ' || *p3 == '\t')
p3++;
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = eitem->buffer;
eitem->buffer[p2-p] = 0;
eitem->errtext = eitem->buffer + (p3 - p);
/* (we already checked that there are only ascii
digits followed by a colon) */
eitem->lineno = 0;
for (p2++; isdigit (*p2); p2++)
eitem->lineno = eitem->lineno*10 + (*p2 - '0');
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
else
{
/* Other error output. */
eitem = xmalloc (sizeof *eitem + strlen (p));
eitem->next = NULL;
strcpy (eitem->buffer, p);
eitem->fname = NULL;
eitem->errtext = eitem->buffer;
eitem->lineno = 0;
*errlines_tail = eitem;
errlines_tail = &eitem->next;
}
}
pos = 0;
/* If this was not a complete line mark that we are in a
continuation. */
cont_line = (c != '\n');
}
}
/* We ignore error lines not terminated by a LF. */
return errlines;
}
/* Check the options of a single component. If CONF_FILE is NULL the
* standard config file is used. If OUT is not NULL the output is
* written to that stream. Returns 0 if everything is OK. */
int
gc_component_check_options (int component, estream_t out, const char *conf_file)
{
gpg_error_t err;
unsigned int result;
const char *pgmname;
const char *argv[6];
int i;
gnupg_process_t proc;
estream_t errfp;
error_line_t errlines;
log_assert (component >= 0 && component < GC_COMPONENT_NR);
if (!gc_component[component].program)
return 0;
if (!gc_component[component].module_name)
return 0;
pgmname = gnupg_module_name (gc_component[component].module_name);
i = 0;
if (!gnupg_default_homedir_p ()
&& component != GC_COMPONENT_PINENTRY)
{
argv[i++] = "--homedir";
argv[i++] = gnupg_homedir ();
}
if (conf_file)
{
argv[i++] = "--options";
argv[i++] = conf_file;
}
if (component == GC_COMPONENT_PINENTRY)
argv[i++] = "--version";
else
argv[i++] = "--gpgconf-test";
argv[i] = NULL;
log_assert (i < DIM(argv));
result = 0;
errlines = NULL;
err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDERR_PIPE,
NULL, &proc);
if (err)
result |= 1; /* Program could not be run. */
else
{
gnupg_process_get_streams (proc, 0, NULL, NULL, &errfp);
errlines = collect_error_output (errfp,
gc_component[component].name);
if (!gnupg_process_wait (proc, 1))
{
int exitcode;
gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode);
if (exitcode == -1)
result |= 1; /* Program could not be run or it
terminated abnormally. */
else if (exitcode)
result |= 2; /* Program returned an error. */
}
gnupg_process_release (proc);
es_fclose (errfp);
}
/* If the program could not be run, we can't tell whether
the config file is good. */
if (result & 1)
result |= 2;
if (out)
{
const char *desc;
error_line_t errptr;
desc = gc_component[component].desc;
desc = my_dgettext (gc_component[component].desc_domain, desc);
es_fprintf (out, "%s:%s:",
gc_component[component].program, gc_percent_escape (desc));
es_fputs (gc_percent_escape (pgmname), out);
es_fprintf (out, ":%d:%d:", !(result & 1), !(result & 2));
for (errptr = errlines; errptr; errptr = errptr->next)
{
if (errptr != errlines)
es_fputs ("\n:::::", out); /* Continuation line. */
if (errptr->fname)
es_fputs (gc_percent_escape (errptr->fname), out);
es_putc (':', out);
if (errptr->fname)
es_fprintf (out, "%u", errptr->lineno);
es_putc (':', out);
es_fputs (gc_percent_escape (errptr->errtext), out);
es_putc (':', out);
}
es_putc ('\n', out);
}
while (errlines)
{
error_line_t tmp = errlines->next;
xfree (errlines);
errlines = tmp;
}
return result;
}
/* Check all components that are available. */
void
gc_check_programs (estream_t out)
{
gc_component_id_t component;
for (component = 0; component < GC_COMPONENT_NR; component++)
gc_component_check_options (component, out, NULL);
}
/* Find the component with the name NAME. Returns -1 if not
found. */
int
gc_component_find (const char *name)
{
gc_component_id_t idx;
for (idx = 0; idx < GC_COMPONENT_NR; idx++)
{
if (gc_component[idx].program
&& !strcmp (name, gc_component[idx].program))
return idx;
}
return -1;
}
/* List the option OPTION. */
static void
list_one_option (gc_component_id_t component,
const gc_option_t *option, estream_t out)
{
const char *desc = NULL;
char *arg_name = NULL;
unsigned long flags;
const char *desc_domain = gc_component[component].desc_domain;
/* Don't show options with the ignore attribute. */
if (option->attr_ignore && !option->attr_force)
return;
if (option->desc)
{
desc = my_dgettext (desc_domain, option->desc);
if (*desc == '|')
{
const char *arg_tail = strchr (&desc[1], '|');
if (arg_tail)
{
int arg_len = arg_tail - &desc[1];
arg_name = xmalloc (arg_len + 1);
memcpy (arg_name, &desc[1], arg_len);
arg_name[arg_len] = '\0';
desc = arg_tail + 1;
}
}
}
/* YOU MUST NOT REORDER THE FIELDS IN THIS OUTPUT, AS THEIR ORDER IS
PART OF THE EXTERNAL INTERFACE. YOU MUST NOT REMOVE ANY
FIELDS. */
/* The name field. */
es_fprintf (out, "%s", option->name);
/* The flags field. */
flags = 0;
if (option->is_header) flags |= GC_OPT_FLAG_GROUP;
if (option->is_list) flags |= GC_OPT_FLAG_LIST;
if (option->runtime) flags |= GC_OPT_FLAG_RUNTIME;
if (option->has_default) flags |= GC_OPT_FLAG_DEFAULT;
if (option->def_in_desc) flags |= GC_OPT_FLAG_DEF_DESC;
if (option->no_arg_desc) flags |= GC_OPT_FLAG_NO_ARG_DESC;
if (option->no_change) flags |= GC_OPT_FLAG_NO_CHANGE;
if (option->attr_force) flags |= GC_OPT_FLAG_NO_CHANGE;
es_fprintf (out, ":%lu", flags);
if (opt.verbose)
{
es_putc (' ', out);
if (!flags)
es_fprintf (out, "none");
else
{
unsigned long flag = 0;
unsigned long first = 1;
while (flags)
{
if (flags & 1)
{
if (first)
first = 0;
else
es_putc (',', out);
es_fprintf (out, "%s", gc_flag[flag].name);
}
flags >>= 1;
flag++;
}
}
}
/* The level field. */
es_fprintf (out, ":%u", option->level);
if (opt.verbose)
es_fprintf (out, " %s", gc_level[option->level].name);
/* The description field. */
es_fprintf (out, ":%s", desc ? gc_percent_escape (desc) : "");
/* The type field. */
es_fprintf (out, ":%u", option->arg_type);
if (opt.verbose)
es_fprintf (out, " %s", gc_arg_type[option->arg_type].name);
/* The alternate type field. */
es_fprintf (out, ":%u", gc_arg_type[option->arg_type].fallback);
if (opt.verbose)
es_fprintf (out, " %s",
gc_arg_type[gc_arg_type[option->arg_type].fallback].name);
/* The argument name field. */
es_fprintf (out, ":%s", arg_name ? gc_percent_escape (arg_name) : "");
xfree (arg_name);
/* The default value field. */
es_fprintf (out, ":%s", option->default_value ? option->default_value : "");
/* The default argument field. This was never used and is thus empty. */
es_fprintf (out, ":");
/* The value field. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& option->is_list && option->value)
{
/* The special format "1,1,1,1,...,1" is converted to a number
here. */
es_fprintf (out, ":%u", (unsigned int)((strlen (option->value) + 1) / 2));
}
else
es_fprintf (out, ":%s", option->value ? option->value : "");
/* ADD NEW FIELDS HERE. */
es_putc ('\n', out);
}
/* List all options of the component COMPONENT. */
void
gc_component_list_options (int component, estream_t out)
{
const gc_option_t *option = gc_component[component].options;
for ( ; option && option->name; option++)
{
/* Do not output unknown or internal options. */
if (!option->is_header
&& option->level == GC_LEVEL_INTERNAL)
continue;
if (option->is_header)
{
const gc_option_t *group_option = option + 1;
gc_expert_level_t level = GC_LEVEL_NR;
/* The manual states that the group level is always the
minimum of the levels of all contained options. Due to
different active options, and because it is hard to
maintain manually, we calculate it here. The value in
the global static table is ignored. */
for ( ; group_option->name; group_option++)
{
if (group_option->is_header)
break;
if (group_option->level < level)
level = group_option->level;
}
/* Check if group is empty. */
if (level != GC_LEVEL_NR)
{
gc_option_t opt_copy;
/* Fix up the group level. */
opt_copy = *option;
opt_copy.level = level;
list_one_option (component, &opt_copy, out);
}
}
else
list_one_option (component, option, out);
}
}
/* Return true if the option NAME is known and that we want it as
* gpgconf managed option. */
static known_option_t *
is_known_option (gc_component_id_t component, const char *name)
{
known_option_t *option = gc_component[component].known_options;
if (option)
{
for (; option->name; option++)
if (!strcmp (option->name, name))
break;
}
return (option && option->name)? option : NULL;
}
/* Find the option NAME in component COMPONENT. Returns pointer to
* the option descriptor or NULL if not found. */
static gc_option_t *
find_option (gc_component_id_t component, const char *name)
{
gc_option_t *option = gc_component[component].options;
if (option)
{
for (; option->name; option++)
{
if (!option->is_header
&& !strcmp (option->name, name))
return option;
}
}
return NULL;
}
struct read_line_wrapper_parm_s
{
const char *pgmname;
estream_t fp;
char *line;
size_t line_len;
const char **extra_lines;
int extra_lines_idx;
char *extra_line_buffer;
};
/* Helper for retrieve_options_from_program. */
static ssize_t
read_line_wrapper (struct read_line_wrapper_parm_s *parm)
{
ssize_t length;
const char *extra_line;
if (parm->fp)
{
length = es_read_line (parm->fp, &parm->line, &parm->line_len, NULL);
if (length > 0)
return length;
if (length < 0 || es_ferror (parm->fp))
gc_error (1, errno, "error reading from %s", parm->pgmname);
if (es_fclose (parm->fp))
gc_error (1, errno, "error closing %s", parm->pgmname);
/* EOF seen. */
parm->fp = NULL;
}
/* Return the made up lines. */
if (!parm->extra_lines
|| !(extra_line = parm->extra_lines[parm->extra_lines_idx]))
return -1; /* This is really the EOF. */
parm->extra_lines_idx++;
xfree (parm->extra_line_buffer);
parm->extra_line_buffer = xstrdup (extra_line);
return strlen (parm->extra_line_buffer);
}
/* Retrieve the options for the component COMPONENT. With
* ONLY_INSTALLED set components which are not installed are silently
* ignored. */
static void
retrieve_options_from_program (gc_component_id_t component, int only_installed)
{
gpg_error_t err;
const char *pgmname;
const char *argv[2];
estream_t outfp;
gnupg_process_t proc;
known_option_t *known_option;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
const char *config_name;
gpgrt_argparse_t pargs;
int dummy_argc;
char *twopartconfig_name = NULL;
gpgrt_opt_t *opt_table = NULL; /* A malloced option table. */
size_t opt_table_used = 0; /* Its current length. */
size_t opt_table_size = 0; /* Its allocated length. */
gc_option_t *opt_info = NULL; /* A malloced options table. */
size_t opt_info_used = 0; /* Its current length. */
size_t opt_info_size = 0; /* Its allocated length. */
int i;
struct read_line_wrapper_parm_s read_line_parm;
int pseudo_count;
pgmname = (gc_component[component].module_name
? gnupg_module_name (gc_component[component].module_name)
: gc_component[component].program );
if (only_installed && gnupg_access (pgmname, X_OK))
{
return; /* The component is not installed. */
}
/* First we need to read the option table from the program. */
argv[0] = "--dump-option-table";
argv[1] = NULL;
err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDOUT_PIPE,
NULL, &proc);
if (err)
{
gc_error (1, 0, "could not gather option table from '%s': %s",
pgmname, gpg_strerror (err));
}
gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL);
read_line_parm.pgmname = pgmname;
read_line_parm.fp = outfp;
read_line_parm.line = line;
read_line_parm.line_len = line_len = 0;
read_line_parm.extra_line_buffer = NULL;
read_line_parm.extra_lines = gc_component[component].known_pseudo_options;
read_line_parm.extra_lines_idx = 0;
pseudo_count = 0;
while ((length = read_line_wrapper (&read_line_parm)) > 0)
{
const char *fields[4];
const char *optname, *optdesc;
unsigned int optflags;
int short_opt;
gc_arg_type_t arg_type;
int pseudo = 0;
if (read_line_parm.extra_line_buffer)
{
line = read_line_parm.extra_line_buffer;
pseudo = 1;
pseudo_count++;
}
else
line = read_line_parm.line;
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
if (split_fields_colon (line, fields, DIM (fields)) < 4)
{
gc_error (0,0, "WARNING: invalid line in option table of '%s'\n",
pgmname);
continue;
}
optname = fields[0];
short_opt = atoi (fields[1]);
if (short_opt < 1 && !pseudo)
{
gc_error (0,0, "WARNING: bad short option in option table of '%s'\n",
pgmname);
continue;
}
optflags = strtoul (fields[2], NULL, 10);
if ((optflags & ARGPARSE_OPT_HEADER))
known_option = NULL; /* We want all header-only options. */
else if ((known_option = is_known_option (component, optname)))
; /* Yes we want this one. */
else
continue; /* No need to store this option description. */
/* The +1 here is to make sure that we will have a zero item at
* the end of the table. */
if (opt_table_used + 1 >= opt_table_size)
{
/* Note that this also does the initial allocation. */
opt_table_size += 128;
opt_table = xreallocarray (opt_table,
opt_table_used,
opt_table_size,
sizeof *opt_table);
}
/* The +1 here is to make sure that we will have a zero item at
* the end of the table. */
if (opt_info_used + 1 >= opt_info_size)
{
/* Note that this also does the initial allocation. */
opt_info_size += 128;
opt_info = xreallocarray (opt_info,
opt_info_used,
opt_info_size,
sizeof *opt_info);
}
/* The +1 here accounts for the two items we are going to add to
* the global string table. */
if (string_array_used + 1 >= string_array_size)
{
string_array_size += 256;
string_array = xreallocarray (string_array,
string_array_used,
string_array_size,
sizeof *string_array);
}
optname = string_array[string_array_used++] = xstrdup (fields[0]);
optdesc = string_array[string_array_used++] = xstrdup (fields[3]);
/* Create an option table which can then be supplied to
* gpgrt_parser. Unfortunately there is no private pointer in
* the public option table struct so that we can't add extra
* data we need here. Thus we need to build up another table
- * for such info and for ease of use we also copy the tehre the
+ * for such info and for ease of use we also copy there the
* data from the option table. It is not possible to use the
* known_option_s for this because that one does not carry
* header lines and it might also be problematic to use such
* static tables for caching options and default values. */
if (!pseudo)
{
opt_table[opt_table_used].long_opt = optname;
opt_table[opt_table_used].short_opt = short_opt;
opt_table[opt_table_used].description = optdesc;
opt_table[opt_table_used].flags = optflags;
opt_table_used++;
}
/* Note that as per argparser specs the opt_table uses "@" to
- * specifify an empty description. In the DESC script of
+ * specify an empty description. In the DESC script of
* options (opt_info_t) we want to have a real empty string. */
opt_info[opt_info_used].name = optname;
if (*optdesc == '@' && !optdesc[1])
opt_info[opt_info_used].desc = optdesc+1;
else
opt_info[opt_info_used].desc = optdesc;
/* Unfortunately we need to remap the types. */
switch ((optflags & ARGPARSE_TYPE_MASK))
{
case ARGPARSE_TYPE_INT: arg_type = GC_ARG_TYPE_INT32; break;
case ARGPARSE_TYPE_LONG: arg_type = GC_ARG_TYPE_INT32; break;
case ARGPARSE_TYPE_ULONG: arg_type = GC_ARG_TYPE_UINT32; break;
case ARGPARSE_TYPE_STRING: arg_type = GC_ARG_TYPE_STRING; break;
default: arg_type = GC_ARG_TYPE_NONE; break;
}
opt_info[opt_info_used].arg_type = arg_type;
if (pseudo) /* Pseudo options are always no_change. */
opt_info[opt_info_used].no_change = 1;
if ((optflags & ARGPARSE_OPT_HEADER))
opt_info[opt_info_used].is_header = 1;
if (known_option)
{
if ((known_option->flags & GC_OPT_FLAG_LIST))
opt_info[opt_info_used].is_list = 1;
/* FIXME: The next can also be taken from opt_table->flags.
* We need to check the code whether both specifications match. */
if ((known_option->flags & GC_OPT_FLAG_ARG_OPT))
opt_info[opt_info_used].opt_arg = 1;
if ((known_option->flags & GC_OPT_FLAG_RUNTIME))
opt_info[opt_info_used].runtime = 1;
opt_info[opt_info_used].level = known_option->level;
/* Override the received argtype by a complex type. */
if (known_option->arg_type)
opt_info[opt_info_used].arg_type = known_option->arg_type;
}
opt_info_used++;
}
xfree (read_line_parm.extra_line_buffer);
line = read_line_parm.line;
line_len = read_line_parm.line_len;
log_assert (opt_table_used + pseudo_count == opt_info_used);
err = gnupg_process_wait (proc, 1);
if (!err)
{
int exitcode;
gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode);
if (exitcode)
gc_error (1, 0, "running %s failed (exitcode=%d): %s",
pgmname, exitcode, gpg_strerror (err));
}
gnupg_process_release (proc);
/* Make the gpgrt option table and the internal option table available. */
gc_component[component].opt_table = opt_table;
gc_component[component].options = opt_info;
/* Now read the default options. */
argv[0] = "--gpgconf-list";
argv[1] = NULL;
err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDOUT_PIPE,
NULL, &proc);
if (err)
{
gc_error (1, 0, "could not gather active options from '%s': %s",
pgmname, gpg_strerror (err));
}
gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL);
while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0)
{
char *linep;
unsigned long flags = 0;
char *default_value = NULL;
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s from %s",
line, pgmname);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s from %s",
line, pgmname);
linep = end;
}
/* Extract default value, if present. Default to empty if
not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
if ((flags & GC_OPT_FLAG_DEFAULT))
default_value = linep;
linep = end;
}
/* Look up the option in the component and install the
configuration data. */
option = find_option (component, line);
if (option)
{
if (option->gpgconf_list)
gc_error (1, errno,
"option %s returned twice from \"%s --gpgconf-list\"",
line, pgmname);
option->gpgconf_list = 1;
if ((flags & GC_OPT_FLAG_DEFAULT))
option->has_default = 1;
if ((flags & GC_OPT_FLAG_DEF_DESC))
option->def_in_desc = 1;
if ((flags & GC_OPT_FLAG_NO_ARG_DESC))
option->no_arg_desc = 1;
if ((flags & GC_OPT_FLAG_NO_CHANGE))
option->no_change = 1;
if (default_value && *default_value)
option->default_value = xstrdup (default_value);
}
}
if (length < 0 || es_ferror (outfp))
gc_error (1, errno, "error reading from %s", pgmname);
if (es_fclose (outfp))
gc_error (1, errno, "error closing %s", pgmname);
err = gnupg_process_wait (proc, 1);
if (!err)
{
int exitcode;
gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode);
if (exitcode)
gc_error (1, 0, "running %s failed (exitcode=%d): %s",
pgmname, exitcode, gpg_strerror (err));
}
gnupg_process_release (proc);
/* At this point, we can parse the configuration file. */
config_name = gc_component[component].option_config_filename;
if (!config_name)
gc_error (1, 0, "name of config file for %s is not known\n", pgmname);
if (!gnupg_default_homedir_p ())
{
/* This is not the default homedir. We need to take an absolute
* config name for the user config file; gpgrt_argparser
* fortunately supports this. */
char *tmp = make_filename (gnupg_homedir (), config_name, NULL);
twopartconfig_name = xstrconcat (config_name, PATHSEP_S, tmp, NULL);
xfree (tmp);
config_name = twopartconfig_name;
}
memset (&pargs, 0, sizeof pargs);
dummy_argc = 0;
pargs.argc = &dummy_argc;
pargs.flags = (ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER
| ARGPARSE_FLAG_WITHATTR);
if (opt.verbose)
pargs.flags |= ARGPARSE_FLAG_VERBOSE;
while (gpgrt_argparser (&pargs, opt_table, config_name))
{
char *opt_value;
if (pargs.r_opt == ARGPARSE_CONFFILE)
{
/* log_debug ("current conffile='%s'\n", */
/* pargs.r_type? pargs.r.ret_str: "[cmdline]"); */
continue;
}
if ((pargs.r_type & ARGPARSE_OPT_IGNORE))
continue;
/* We only have the short option. Search in the option table
* for the long option name. */
for (i=0; opt_table[i].short_opt; i++)
if (opt_table[i].short_opt == pargs.r_opt)
break;
if (!opt_table[i].short_opt || !opt_table[i].long_opt)
continue; /* No or only a short option - ignore. */
/* Look up the option from the config file in our list of
* supported options. */
option= find_option (component, opt_table[i].long_opt);
if (!option)
continue; /* We don't want to handle this option. */
/* Set the force and ignore attributes. The idea is that there
* is no way to clear them again, thus we set them when first
* encountered. */
if ((pargs.r_type & ARGPARSE_ATTR_FORCE))
option->attr_force = 1;
if ((pargs.r_type & ARGPARSE_ATTR_IGNORE))
option->attr_ignore = 1;
/* If an option has been ignored, there is no need to return
* that option with gpgconf --list-options. */
if (option->attr_ignore)
continue;
switch ((pargs.r_type & ARGPARSE_TYPE_MASK))
{
case ARGPARSE_TYPE_INT:
opt_value = xasprintf ("%d", pargs.r.ret_int);
break;
case ARGPARSE_TYPE_LONG:
opt_value = xasprintf ("%ld", pargs.r.ret_long);
break;
case ARGPARSE_TYPE_ULONG:
opt_value = xasprintf ("%lu", pargs.r.ret_ulong);
break;
case ARGPARSE_TYPE_STRING:
if (!pargs.r.ret_str)
opt_value = xstrdup ("\"(none)"); /* We should not see this. */
else
opt_value = xasprintf ("\"%s", gc_percent_escape (pargs.r.ret_str));
break;
default: /* ARGPARSE_TYPE_NONE or any unknown type. */
opt_value = xstrdup ("1"); /* Make sure we have some value. */
break;
}
/* Now enter the value read from the config file into the table. */
if (!option->is_list)
{
xfree (option->value);
option->value = opt_value;
}
else if (!option->value) /* LIST but first item. */
option->value = opt_value;
else
{
char *old = option->value;
option->value = xstrconcat (old, ",", opt_value, NULL);
xfree (old);
xfree (opt_value);
}
}
xfree (line);
xfree (twopartconfig_name);
}
/* Retrieve the currently active options and their defaults for this
component. Using -1 for component will retrieve all options from
all installed components. */
void
gc_component_retrieve_options (int component)
{
int process_all = 0;
if (component == -1)
{
process_all = 1;
component = 0;
}
do
{
if (component == GC_COMPONENT_PINENTRY)
continue; /* Skip this dummy component. */
if (gc_component[component].program)
retrieve_options_from_program (component, process_all);
}
while (process_all && ++component < GC_COMPONENT_NR);
}
/* Perform a simple validity check based on the type. Return in
* NEW_VALUE_NR the value of the number in NEW_VALUE if OPTION is of
* type GC_ARG_TYPE_NONE. If VERBATIM is set the profile parsing mode
* is used. */
static void
option_check_validity (gc_component_id_t component,
gc_option_t *option, unsigned long flags,
char *new_value, unsigned long *new_value_nr,
int verbatim)
{
char *arg;
(void)component;
if (option->new_flags || option->new_value)
gc_error (1, 0, "option %s already changed", option->name);
if (flags & GC_OPT_FLAG_DEFAULT)
{
if (*new_value)
gc_error (1, 0, "argument %s provided for deleted option %s",
new_value, option->name);
return;
}
/* GC_ARG_TYPE_NONE options have special list treatment. */
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE)
{
char *tail;
gpg_err_set_errno (0);
*new_value_nr = strtoul (new_value, &tail, 0);
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*tail)
gc_error (1, 0, "garbage after argument for option %s",
option->name);
if (!option->is_list)
{
if (*new_value_nr != 1)
gc_error (1, 0, "argument for non-list option %s of type 0 "
"(none) must be 1", option->name);
}
else
{
if (*new_value_nr == 0)
gc_error (1, 0, "argument for option %s of type 0 (none) "
"must be positive", option->name);
}
return;
}
arg = new_value;
do
{
if (*arg == '\0' || (*arg == ',' && !verbatim))
{
if (!option->opt_arg)
gc_error (1, 0, "argument required for option %s", option->name);
if (*arg == ',' && !verbatim && !option->is_list)
gc_error (1, 0, "list found for non-list option %s", option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_STRING)
{
if (*arg != '"' && !verbatim)
gc_error (1, 0, "string argument for option %s must begin "
"with a quote (\") character", option->name);
/* FIXME: We do not allow empty string arguments for now, as
we do not quote arguments in configuration files, and
thus no argument is indistinguishable from the empty
string. */
if (arg[1] == '\0' || (arg[1] == ',' && !verbatim))
gc_error (1, 0, "empty string argument for option %s is "
"currently not allowed. Please report this!",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_INT32)
{
long res;
gpg_err_set_errno (0);
res = strtol (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
else if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_UINT32)
{
unsigned long res;
gpg_err_set_errno (0);
res = strtoul (arg, &arg, 0);
(void) res;
if (errno)
gc_error (1, errno, "invalid argument for option %s",
option->name);
if (*arg != '\0' && (*arg != ',' || verbatim))
gc_error (1, 0, "garbage after argument for option %s",
option->name);
}
arg = verbatim? strchr (arg, ',') : NULL;
if (arg)
arg++;
}
while (arg && *arg);
}
#ifdef HAVE_W32_SYSTEM
int
copy_file (const char *src_name, const char *dst_name)
{
#define BUF_LEN 4096
char buffer[BUF_LEN];
int len;
gpgrt_stream_t src;
gpgrt_stream_t dst;
src = gpgrt_fopen (src_name, "r");
if (src == NULL)
return -1;
dst = gpgrt_fopen (dst_name, "w");
if (dst == NULL)
{
int saved_err = errno;
gpgrt_fclose (src);
gpg_err_set_errno (saved_err);
return -1;
}
do
{
int written;
len = gpgrt_fread (buffer, 1, BUF_LEN, src);
if (len == 0)
break;
written = gpgrt_fwrite (buffer, 1, len, dst);
if (written != len)
break;
}
while (! gpgrt_feof (src) && ! gpgrt_ferror (src) && ! gpgrt_ferror (dst));
if (gpgrt_ferror (src) || gpgrt_ferror (dst) || ! gpgrt_feof (src))
{
int saved_errno = errno;
gpgrt_fclose (src);
gpgrt_fclose (dst);
unlink (dst_name);
gpg_err_set_errno (saved_errno);
return -1;
}
if (gpgrt_fclose (dst))
gc_error (1, errno, "error closing %s", dst_name);
if (gpgrt_fclose (src))
gc_error (1, errno, "error closing %s", src_name);
return 0;
}
#endif /* HAVE_W32_SYSTEM */
/* Create and verify the new configuration file for the specified
* component. Returns 0 on success and -1 on error. If
* VERBATIM is set the profile mode is used. This function may store
* pointers to malloced strings in SRC_FILENAMEP, DEST_FILENAMEP, and
* ORIG_FILENAMEP. Those must be freed by the caller. The strings
* refer to three versions of the configuration file:
*
* SRC_FILENAME: The updated configuration is written to this file.
* DEST_FILENAME: Name of the configuration file read by the
* component.
* ORIG_FILENAME: A backup of the previous configuration file.
*
* To apply the configuration change, rename SRC_FILENAME to
* DEST_FILENAME. To revert to the previous configuration, rename
* ORIG_FILENAME to DEST_FILENAME. */
static int
change_options_program (gc_component_id_t component,
char **src_filenamep, char **dest_filenamep,
char **orig_filenamep,
int verbatim)
{
static const char marker[] = "###+++--- " GPGCONF_DISP_NAME " ---+++###";
/* True if we are within the marker in the config file. */
int in_marker = 0;
gc_option_t *option;
char *line = NULL;
size_t line_len;
ssize_t length;
int res;
int fd;
gpgrt_stream_t src_file = NULL;
gpgrt_stream_t dest_file = NULL;
char *src_filename;
char *dest_filename;
char *orig_filename;
/* Special hack for gpg, see below. */
int utf8strings_seen = 0;
/* FIXME. Throughout the function, do better error reporting. */
if (!gc_component[component].option_config_filename)
gc_error (1, 0, "name of config file for %s is not known\n",
gc_component[component].name);
dest_filename = make_absfilename
(gnupg_homedir (), gc_component[component].option_config_filename, NULL);
src_filename = xasprintf ("%s.%s.%i.new",
dest_filename, GPGCONF_NAME, (int)getpid ());
orig_filename = xasprintf ("%s.%s.%i.bak",
dest_filename, GPGCONF_NAME, (int)getpid ());
#ifdef HAVE_W32_SYSTEM
res = copy_file (dest_filename, orig_filename);
#else
res = link (dest_filename, orig_filename);
#endif
if (res < 0 && errno != ENOENT)
{
xfree (dest_filename);
xfree (src_filename);
xfree (orig_filename);
return -1;
}
if (res < 0)
{
xfree (orig_filename);
orig_filename = NULL;
}
/* We now initialize the return strings, so the caller can do the
cleanup for us. */
*src_filenamep = src_filename;
*dest_filenamep = dest_filename;
*orig_filenamep = orig_filename;
/* Use open() so that we can use O_EXCL.
* FIXME: gpgrt has an x flag for quite some time now - use that. */
fd = gnupg_open (src_filename, O_CREAT | O_EXCL | O_WRONLY, 0644);
if (fd < 0)
return -1;
src_file = gpgrt_fdopen (fd, "w");
res = errno;
if (!src_file)
{
gpg_err_set_errno (res);
return -1;
}
/* Only if ORIG_FILENAME is not NULL did the configuration file
exist already. In this case, we will copy its content into the
new configuration file, changing it to our liking in the
process. */
if (orig_filename)
{
dest_file = gpgrt_fopen (dest_filename, "r");
if (!dest_file)
goto change_one_err;
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
int disable = 0;
char *start;
if (!strncmp (marker, line, sizeof (marker) - 1))
{
if (!in_marker)
in_marker = 1;
else
break;
}
else if (component == GC_COMPONENT_GPG && in_marker
&& ! strcmp ("utf8-strings\n", line))
{
/* Strip duplicated entries. */
if (utf8strings_seen)
disable = 1;
else
utf8strings_seen = 1;
}
start = line;
while (*start == ' ' || *start == '\t')
start++;
if (*start && *start != '\r' && *start != '\n' && *start != '#')
{
char *end;
char saved_end;
end = start;
while (*end && *end != ' ' && *end != '\t'
&& *end != '\r' && *end != '\n' && *end != '#')
end++;
saved_end = *end;
*end = '\0';
option = find_option (component, start);
*end = saved_end;
if (option && ((option->new_flags & GC_OPT_FLAG_DEFAULT)
|| option->new_value))
disable = 1;
}
if (disable)
{
if (!in_marker)
{
gpgrt_fprintf (src_file,
"# %s disabled this option here at %s\n",
GPGCONF_DISP_NAME, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# %s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
else
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
if (!in_marker)
{
/* There was no marker. This is the first time we edit the
file. We add our own marker at the end of the file and
proceed. Note that we first write a newline, this guards us
against files which lack the newline at the end of the last
line, while it doesn't hurt us in all other cases. */
gpgrt_fprintf (src_file, "\n%s\n", marker);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
/* At this point, we have copied everything up to the end marker
into the new file, except for the options we are going to change.
Now, dump the changed options (except for those we are going to
revert to their default), and write the end marker, possibly
followed by the rest of the original file. */
/* We have to turn on UTF8 strings for GnuPG. */
if (component == GC_COMPONENT_GPG && ! utf8strings_seen)
gpgrt_fprintf (src_file, "utf8-strings\n");
option = gc_component[component].options;
for ( ; option->name; option++)
{
if (!option->is_header && option->new_value)
{
char *arg = option->new_value;
do
{
if (*arg == '\0' || *arg == ',')
{
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_NONE)
{
log_assert (*arg == '1');
gpgrt_fprintf (src_file, "%s\n", option->name);
if (gpgrt_ferror (src_file))
goto change_one_err;
arg++;
}
else if (gc_arg_type[option->arg_type].fallback
== GC_ARG_TYPE_STRING)
{
char *end;
if (!verbatim)
{
log_assert (*arg == '"');
arg++;
end = strchr (arg, ',');
if (end)
*end = '\0';
}
else
end = NULL;
gpgrt_fprintf (src_file, "%s %s\n", option->name,
verbatim? arg : percent_deescape (arg));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
else
{
char *end;
end = strchr (arg, ',');
if (end)
*end = '\0';
gpgrt_fprintf (src_file, "%s %s\n", option->name, arg);
if (gpgrt_ferror (src_file))
goto change_one_err;
if (end)
*end = ',';
arg = end;
}
log_assert (arg == NULL || *arg == '\0' || *arg == ',');
if (arg && *arg == ',')
arg++;
}
while (arg && *arg);
}
}
gpgrt_fprintf (src_file, "%s %s\n", marker, asctimestamp (gnupg_get_time ()));
if (gpgrt_ferror (src_file))
goto change_one_err;
if (!in_marker)
{
gpgrt_fprintf (src_file, "# %s edited this configuration file.\n",
GPGCONF_DISP_NAME);
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# It will disable options before this marked "
"block, but it will\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
gpgrt_fprintf (src_file, "# never change anything below these lines.\n");
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (dest_file)
{
while ((length = gpgrt_read_line (dest_file, &line, &line_len, NULL)) > 0)
{
gpgrt_fprintf (src_file, "%s", line);
if (gpgrt_ferror (src_file))
goto change_one_err;
}
if (length < 0 || gpgrt_ferror (dest_file))
goto change_one_err;
}
xfree (line);
line = NULL;
res = gpgrt_fclose (src_file);
if (res)
{
res = errno;
close (fd);
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
close (fd);
if (dest_file)
{
res = gpgrt_fclose (dest_file);
if (res)
return -1;
}
return 0;
change_one_err:
xfree (line);
res = errno;
if (src_file)
{
gpgrt_fclose (src_file);
close (fd);
}
if (dest_file)
gpgrt_fclose (dest_file);
gpg_err_set_errno (res);
return -1;
}
/* Common code for gc_component_change_options and
* gc_process_gpgconf_conf. If VERBATIM is set the profile parsing
* mode is used. */
static void
change_one_value (gc_component_id_t component,
gc_option_t *option, int *r_runtime,
unsigned long flags, char *new_value, int verbatim)
{
unsigned long new_value_nr = 0;
option_check_validity (component, option,
flags, new_value, &new_value_nr, verbatim);
if (option->runtime)
*r_runtime = 1;
option->new_flags = flags;
if (!(flags & GC_OPT_FLAG_DEFAULT))
{
if (gc_arg_type[option->arg_type].fallback == GC_ARG_TYPE_NONE
&& option->is_list)
{
char *str;
/* We convert the number to a list of 1's for convenient
list handling. */
log_assert (new_value_nr > 0);
option->new_value = xmalloc ((2 * (new_value_nr - 1) + 1) + 1);
str = option->new_value;
*(str++) = '1';
while (--new_value_nr > 0)
{
*(str++) = ',';
*(str++) = '1';
}
*(str++) = '\0';
}
else
option->new_value = xstrdup (new_value);
}
}
/* Read the modifications from IN and apply them. If IN is NULL the
modifications are expected to already have been set to the global
table. If VERBATIM is set the profile mode is used. */
void
gc_component_change_options (int component, estream_t in, estream_t out,
int verbatim)
{
int err = 0;
int block = 0;
int runtime = 0;
char *src_filename = NULL;
char *dest_filename = NULL;
char *orig_filename = NULL;
gc_option_t *option;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
if (component == GC_COMPONENT_PINENTRY)
return; /* Dummy component for now. */
if (in)
{
/* Read options from the file IN. */
while ((length = es_read_line (in, &line, &line_len, NULL)) > 0)
{
char *linep;
unsigned long flags = 0;
char *new_value = "";
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
linep = strchr (line, ':');
if (linep)
*(linep++) = '\0';
/* Extract additional flags. Default to none. */
if (linep)
{
char *end;
char *tail;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
gpg_err_set_errno (0);
flags = strtoul (linep, &tail, 0);
if (errno)
gc_error (1, errno, "malformed flags in option %s", line);
if (!(*tail == '\0' || *tail == ':' || *tail == ' '))
gc_error (1, 0, "garbage after flags in option %s", line);
linep = end;
}
/* Don't allow setting of the no change flag. */
flags &= ~GC_OPT_FLAG_NO_CHANGE;
/* Extract default value, if present. Default to empty if not. */
if (linep)
{
char *end;
end = strchr (linep, ':');
if (end)
*(end++) = '\0';
new_value = linep;
linep = end;
}
option = find_option (component, line);
if (!option)
gc_error (1, 0, "unknown option %s", line);
if (option->no_change)
{
gc_error (0, 0, "ignoring new value for option %s",
option->name);
continue;
}
change_one_value (component, option, &runtime, flags, new_value, 0);
}
if (length < 0 || gpgrt_ferror (in))
gc_error (1, errno, "error reading stream 'in'");
}
/* Now that we have collected and locally verified the changes,
write them out to new configuration files, verify them
externally, and then commit them. */
option = gc_component[component].options;
while (option && option->name)
{
/* Go on if there is nothing to do. */
if (src_filename || !(option->new_flags || option->new_value))
{
option++;
continue;
}
if (gc_component[component].program)
{
err = change_options_program (component,
&src_filename,
&dest_filename,
&orig_filename,
verbatim);
if (! err)
{
/* External verification. */
err = gc_component_check_options (component, out, src_filename);
if (err)
{
gc_error (0, 0,
_("External verification of component %s failed"),
gc_component[component].name);
gpg_err_set_errno (EINVAL);
}
}
}
if (err)
break;
option++;
}
/* We are trying to atomically commit all changes. Unfortunately,
we cannot rely on gnupg_rename_file to manage the signals for us,
doing so would require us to pass NULL as BLOCK to any subsequent
call to it. Instead, we just manage the signal handling
manually. */
block = 1;
gnupg_block_all_signals ();
if (!err && !opt.dry_run)
{
if (src_filename)
{
/* FIXME: Make a verification here. */
log_assert (dest_filename);
if (orig_filename)
err = gnupg_rename_file (src_filename, dest_filename, NULL);
else
{
#ifdef HAVE_W32_SYSTEM
/* We skip the unlink if we expect the file not to be
* there. */
err = gnupg_rename_file (src_filename, dest_filename, NULL);
#else /* HAVE_W32_SYSTEM */
/* This is a bit safer than rename() because we expect
* DEST_FILENAME not to be there. If it happens to be
* there, this will fail. */
err = link (src_filename, dest_filename);
if (!err)
err = unlink (src_filename);
#endif /* !HAVE_W32_SYSTEM */
}
if (!err)
{
xfree (src_filename);
src_filename = NULL;
}
}
}
if (err || opt.dry_run)
{
int saved_errno = errno;
/* An error occurred or a dry-run is requested. */
if (src_filename)
{
/* The change was not yet committed. */
unlink (src_filename);
if (orig_filename)
unlink (orig_filename);
}
else
{
/* The changes were already committed. FIXME: This is a tad
dangerous, as we don't know if we don't overwrite a
version of the file that is even newer than the one we
just installed. */
if (orig_filename)
gnupg_rename_file (orig_filename, dest_filename, NULL);
else
unlink (dest_filename);
}
if (err)
gc_error (1, saved_errno, "could not commit changes");
/* Fall-through for dry run. */
goto leave;
}
/* If it all worked, notify the daemons of the changes. */
if (opt.runtime)
do_runtime_change (component, 0);
/* Move the per-process backup file into its place. */
if (orig_filename)
{
char *backup_filename;
log_assert (dest_filename);
backup_filename = xasprintf ("%s.%s.bak",
dest_filename, GPGCONF_NAME);
gnupg_rename_file (orig_filename, backup_filename, NULL);
xfree (backup_filename);
}
leave:
if (block)
gnupg_unblock_all_signals ();
xfree (line);
xfree (src_filename);
xfree (dest_filename);
xfree (orig_filename);
}
/* Check whether USER matches the current user or one of its group.
This function may change USER. Returns true is there is a
match. */
static int
key_matches_user_or_group (char *user)
{
char *group;
if (*user == '*' && user[1] == 0)
return 1; /* A single asterisk matches all users. */
group = strchr (user, ':');
if (group)
*group++ = 0;
#ifdef HAVE_W32_SYSTEM
/* Under Windows we don't support groups. */
if (group && *group)
gc_error (0, 0, _("Note that group specifications are ignored\n"));
if (*user)
{
static char *my_name;
if (!my_name)
{
char tmp[1];
DWORD size = 1;
GetUserNameA (tmp, &size);
my_name = xmalloc (size);
if (!GetUserNameA (my_name, &size))
gc_error (1,0, "error getting current user name: %s",
w32_strerror (-1));
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
#else /*!HAVE_W32_SYSTEM*/
/* First check whether the user matches. */
if (*user)
{
static char *my_name;
if (!my_name)
{
struct passwd *pw = getpwuid ( getuid () );
if (!pw)
gc_error (1, errno, "getpwuid failed for current user");
my_name = xstrdup (pw->pw_name);
}
if (!strcmp (user, my_name))
return 1; /* Found. */
}
/* If that failed, check whether a group matches. */
if (group && *group)
{
static char *my_group;
static char **my_supgroups;
int n;
if (!my_group)
{
struct group *gr = getgrgid ( getgid () );
if (!gr)
gc_error (1, errno, "getgrgid failed for current user");
my_group = xstrdup (gr->gr_name);
}
if (!strcmp (group, my_group))
return 1; /* Found. */
if (!my_supgroups)
{
int ngids;
gid_t *gids;
ngids = getgroups (0, NULL);
gids = xcalloc (ngids+1, sizeof *gids);
ngids = getgroups (ngids, gids);
if (ngids < 0)
gc_error (1, errno, "getgroups failed for current user");
my_supgroups = xcalloc (ngids+1, sizeof *my_supgroups);
for (n=0; n < ngids; n++)
{
struct group *gr = getgrgid ( gids[n] );
if (!gr)
gc_error (1, errno, "getgrgid failed for supplementary group");
my_supgroups[n] = xstrdup (gr->gr_name);
}
xfree (gids);
}
for (n=0; my_supgroups[n]; n++)
if (!strcmp (group, my_supgroups[n]))
return 1; /* Found. */
}
#endif /*!HAVE_W32_SYSTEM*/
return 0; /* No match. */
}
/* Read and process the global configuration file for gpgconf. This
optional file is used to update our internal tables at runtime and
may also be used to set new default values. If FNAME is NULL the
default name will be used. With UPDATE set to true the internal
tables are actually updated; if not set, only a syntax check is
done. If DEFAULTS is true the global options are written to the
configuration files. If LISTFP is set, no changes are done but the
configuration file is printed to LISTFP in a colon separated format.
Returns 0 on success or if the config file is not present; -1 is
returned on error. */
int
gc_process_gpgconf_conf (const char *fname_arg, int update, int defaults,
estream_t listfp)
{
int result = 0;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
gpgrt_stream_t config;
int lineno = 0;
int in_rule = 0;
int got_match = 0;
int runtime[GC_COMPONENT_NR] = { 0 };
int component_id;
char *fname;
if (fname_arg)
fname = xstrdup (fname_arg);
else
fname = make_filename (gnupg_sysconfdir (), GPGCONF_NAME EXTSEP_S "conf",
NULL);
config = gpgrt_fopen (fname, "r");
if (!config)
{
/* Do not print an error if the file is not available, except
when running in syntax check mode. */
if (errno != ENOENT || !update)
{
gc_error (0, errno, "can't open global config file '%s'", fname);
result = -1;
}
xfree (fname);
return result;
}
while ((length = gpgrt_read_line (config, &line, &line_len, NULL)) > 0)
{
char *key, *compname, *option, *flags, *value;
char *empty;
gc_option_t *option_info = NULL;
char *p;
int is_continuation;
lineno++;
key = line;
while (*key == ' ' || *key == '\t')
key++;
if (!*key || *key == '#' || *key == '\r' || *key == '\n')
continue;
is_continuation = (key != line);
/* Parse the key field. */
if (!is_continuation && got_match)
break; /* Finish after the first match. */
else if (!is_continuation)
{
in_rule = 0;
for (p=key+1; *p && !strchr (" \t\r\n", *p); p++)
;
if (!*p)
{
gc_error (0, 0, "missing rule at '%s', line %d", fname, lineno);
result = -1;
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"missing rule",
GPG_ERR_SYNTAX, fname, lineno);
continue;
}
*p++ = 0;
compname = p;
}
else if (!in_rule)
{
gc_error (0, 0, "continuation but no rule at '%s', line %d",
fname, lineno);
result = -1;
continue;
}
else
{
compname = key;
key = NULL;
}
in_rule = 1;
/* Parse the component. */
while (*compname == ' ' || *compname == '\t')
compname++;
for (p=compname; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == compname)
{
gc_error (0, 0, "missing component at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
" missing component",
GPG_ERR_NO_NAME, fname, lineno);
result = -1;
continue;
}
empty = p;
*p++ = 0;
option = p;
component_id = gc_component_find (compname);
if (component_id < 0)
{
gc_error (0, 0, "unknown component at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"unknown component",
GPG_ERR_UNKNOWN_NAME, fname, lineno);
result = -1;
}
/* Parse the option name. */
while (*option == ' ' || *option == '\t')
option++;
for (p=option; *p && !strchr (" \t\r\n", *p); p++)
;
if (p == option)
{
gc_error (0, 0, "missing option at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"missing option",
GPG_ERR_INV_NAME, fname, lineno);
result = -1;
continue;
}
*p++ = 0;
flags = p;
if ( component_id != -1)
{
/* We need to make sure that we got the option list for the
* component. */
if (!gc_component[component_id].options)
gc_component_retrieve_options (component_id);
option_info = find_option (component_id, option);
if (!option_info)
{
gc_error (0, 0, "unknown option '%s' at '%s', line %d",
option, fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"unknown option",
GPG_ERR_UNKNOWN_OPTION, fname, lineno);
result = -1;
}
}
/* Parse the optional flags. */
while (*flags == ' ' || *flags == '\t')
flags++;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
gc_error (0, 0, "syntax error in rule at '%s', line %d",
fname, lineno);
gpgconf_write_status (STATUS_WARNING,
"gpgconf.conf %d file '%s' line %d "
"syntax error in rule",
GPG_ERR_SYNTAX, fname, lineno);
result = -1;
continue;
}
*p++ = 0;
value = p;
}
else /* No flags given. */
{
value = flags;
flags = NULL;
}
/* Parse the optional value. */
while (*value == ' ' || *value == '\t')
value++;
for (p=value; *p && !strchr ("\r\n", *p); p++)
;
if (p == value)
value = empty; /* No value given; let it point to an empty string. */
else
{
/* Strip trailing white space. */
*p = 0;
for (p--; p > value && (*p == ' ' || *p == '\t'); p--)
*p = 0;
}
/* Check flag combinations. */
if (!flags)
;
else if (!strcmp (flags, "default"))
{
if (*value)
{
gc_error (0, 0, "flag \"default\" may not be combined "
"with a value at '%s', line %d",
fname, lineno);
result = -1;
}
}
else if (!strcmp (flags, "change"))
;
else if (!strcmp (flags, "no-change"))
;
else
{
gc_error (0, 0, "unknown flag at '%s', line %d",
fname, lineno);
result = -1;
}
/* In list mode we print out all records. */
if (listfp && !result)
{
/* If this is a new ruleset, print a key record. */
if (!is_continuation)
{
char *group = strchr (key, ':');
if (group)
{
*group++ = 0;
if ((p = strchr (group, ':')))
*p = 0; /* We better strip any extra stuff. */
}
es_fprintf (listfp, "k:%s:", gc_percent_escape (key));
es_fprintf (listfp, "%s\n", group? gc_percent_escape (group):"");
}
/* All other lines are rule records. */
es_fprintf (listfp, "r:::%s:%s:%s:",
gc_component[component_id].name,
option_info->name? option_info->name : "",
flags? flags : "");
if (value != empty)
es_fprintf (listfp, "\"%s", gc_percent_escape (value));
es_putc ('\n', listfp);
}
/* Check whether the key matches but do this only if we are not
running in syntax check mode. */
if ( update
&& !result && !listfp
&& (got_match || (key && key_matches_user_or_group (key))) )
{
int newflags = 0;
got_match = 1;
/* Apply the flags from gpgconf.conf. */
if (!flags)
;
else if (!strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
else if (!strcmp (flags, "no-change"))
option_info->no_change = 1;
else if (!strcmp (flags, "change"))
option_info->no_change = 0;
if (defaults)
{
/* Here we explicitly allow updating the value again. */
if (newflags)
{
option_info->new_flags = 0;
}
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (component_id, option_info,
runtime, newflags, value, 0);
}
}
}
if (length < 0 || gpgrt_ferror (config))
{
gc_error (0, errno, "error reading from '%s'", fname);
result = -1;
}
if (gpgrt_fclose (config))
gc_error (0, errno, "error closing '%s'", fname);
xfree (line);
/* If it all worked, process the options. */
if (!result && update && defaults && !listfp)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 0);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
if (runtime[component_id]
&& gc_component[component_id].runtime_change)
(*gc_component[component_id].runtime_change) (0);
}
}
xfree (fname);
return result;
}
/*
* Apply the profile FNAME to all known configure files.
*/
gpg_error_t
gc_apply_profile (const char *fname)
{
gpg_error_t err;
char *fname_buffer = NULL;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
estream_t fp;
int lineno = 0;
int runtime[GC_COMPONENT_NR] = { 0 };
int component_id = -1;
int skip_section = 0;
int error_count = 0;
int newflags;
if (!fname)
fname = "-";
if (!(!strcmp (fname, "-")
|| strchr (fname, '/')
#ifdef HAVE_W32_SYSTEM
|| strchr (fname, '\\')
#endif
|| strchr (fname, '.')))
{
/* FNAME looks like a standard profile name. Check whether one
* is installed and use that instead of the given file name. */
fname_buffer = xstrconcat (gnupg_datadir (), DIRSEP_S,
fname, ".prf", NULL);
if (!gnupg_access (fname_buffer, F_OK))
fname = fname_buffer;
}
fp = !strcmp (fname, "-")? es_stdin : es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
log_error ("can't open '%s': %s\n", fname, gpg_strerror (err));
return err;
}
if (opt.verbose)
log_info ("applying profile '%s'\n", fname);
err = 0;
while ((length = es_read_line (fp, &line, &line_len, NULL)) > 0)
{
char *name, *flags, *value;
gc_option_t *option_info = NULL;
char *p;
lineno++;
name = line;
while (*name == ' ' || *name == '\t')
name++;
if (!*name || *name == '#' || *name == '\r' || *name == '\n')
continue;
trim_trailing_spaces (name);
/* Check whether this is a new section. */
if (*name == '[')
{
name++;
skip_section = 0;
/* New section: Get the name of the component. */
p = strchr (name, ']');
if (!p)
{
error_count++;
log_info ("%s:%d:%d: error: syntax error in section tag\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
*p++ = 0;
if (*p)
log_info ("%s:%d:%d: warning: garbage after section tag\n",
fname, lineno, (int)(p - line));
trim_spaces (name);
component_id = gc_component_find (name);
if (component_id < 0)
{
log_info ("%s:%d:%d: warning: skipping unknown section '%s'\n",
fname, lineno, (int)(name - line), name );
skip_section = 1;
}
continue;
}
if (skip_section)
continue;
if (component_id < 0)
{
error_count++;
log_info ("%s:%d:%d: error: not in a valid section\n",
fname, lineno, (int)(name - line));
skip_section = 1;
continue;
}
/* Parse the option name. */
for (p = name; *p && !spacep (p); p++)
;
*p++ = 0;
value = p;
option_info = find_option (component_id, name);
if (!option_info)
{
error_count++;
log_info ("%s:%d:%d: error: unknown option '%s' in section '%s'\n",
fname, lineno, (int)(name - line),
name, gc_component[component_id].name);
continue;
}
/* Parse the optional flags. */
trim_spaces (value);
flags = value;
if (*flags == '[')
{
flags++;
p = strchr (flags, ']');
if (!p)
{
log_info ("%s:%d:%d: warning: invalid flag specification\n",
fname, lineno, (int)(p - line));
continue;
}
*p++ = 0;
value = p;
trim_spaces (value);
}
else /* No flags given. */
flags = NULL;
/* Set required defaults. */
if (gc_arg_type[option_info->arg_type].fallback == GC_ARG_TYPE_NONE
&& !*value)
value = "1";
/* Check and save this option. */
newflags = 0;
if (flags && !strcmp (flags, "default"))
newflags |= GC_OPT_FLAG_DEFAULT;
if (newflags)
option_info->new_flags = 0;
if (*value)
{
xfree (option_info->new_value);
option_info->new_value = NULL;
}
change_one_value (component_id, option_info, runtime, newflags, value, 1);
}
if (length < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
error_count++;
log_error (_("%s:%u: read error: %s\n"),
fname, lineno, gpg_strerror (err));
}
if (es_fclose (fp))
log_error (_("error closing '%s'\n"), fname);
if (error_count)
log_error (_("error parsing '%s'\n"), fname);
xfree (line);
/* If it all worked, process the options. */
if (!err)
{
/* We need to switch off the runtime update, so that we can do
it later all at once. */
int save_opt_runtime = opt.runtime;
opt.runtime = 0;
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
{
gc_component_change_options (component_id, NULL, NULL, 1);
}
opt.runtime = save_opt_runtime;
if (opt.runtime)
{
for (component_id = 0; component_id < GC_COMPONENT_NR; component_id++)
if (runtime[component_id]
&& gc_component[component_id].runtime_change)
(*gc_component[component_id].runtime_change) (0);
}
}
xfree (fname_buffer);
return err;
}
diff --git a/tools/gpgconf.c b/tools/gpgconf.c
index 301c1838f..a2113aee0 100644
--- a/tools/gpgconf.c
+++ b/tools/gpgconf.c
@@ -1,1825 +1,1825 @@
/* gpgconf.c - Configuration utility for GnuPG
* Copyright (C) 2003, 2007, 2009, 2011 Free Software Foundation, Inc.
* Copyright (C) 2016 g10 Code GmbH.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "gpgconf.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/init.h"
#include "../common/status.h"
#include "../common/exechelp.h"
#include "../common/dotlock.h"
#ifdef HAVE_W32_SYSTEM
#include <windows.h>
#endif
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
oDryRun = 'n',
oOutput = 'o',
oQuiet = 'q',
oVerbose = 'v',
oRuntime = 'r',
oComponent = 'c',
oNull = '0',
aListDirs = 'L',
aKill = 'K',
aReload = 'R',
aShowVersions = 'V',
aShowConfigs = 'X',
oNoVerbose = 500,
oHomedir,
oBuilddir,
oStatusFD,
oShowSocket,
oChUid,
aListComponents,
aCheckPrograms,
aListOptions,
aChangeOptions,
aCheckOptions,
aApplyDefaults,
aListConfig,
aCheckConfig,
aQuerySWDB,
aLaunch,
aCreateSocketDir,
aRemoveSocketDir,
aApplyProfile,
aShowCodepages,
aDotlockLock,
aDotlockUnlock
};
/* The list of commands and options. */
static gpgrt_opt_t opts[] =
{
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aListComponents, "list-components", N_("list all components")),
ARGPARSE_c (aCheckPrograms, "check-programs", N_("check all programs")),
ARGPARSE_c (aListOptions, "list-options", N_("|COMPONENT|list options")),
ARGPARSE_c (aChangeOptions, "change-options",
N_("|COMPONENT|change options")),
ARGPARSE_c (aCheckOptions, "check-options", N_("|COMPONENT|check options")),
ARGPARSE_c (aApplyDefaults, "apply-defaults",
N_("apply global default values")),
ARGPARSE_c (aApplyProfile, "apply-profile",
N_("|FILE|update configuration files using FILE")),
ARGPARSE_c (aListDirs, "list-dirs",
N_("get the configuration directories for @GPGCONF@")),
ARGPARSE_c (aListConfig, "list-config",
N_("list global configuration file")),
ARGPARSE_c (aCheckConfig, "check-config",
N_("check global configuration file")),
ARGPARSE_c (aQuerySWDB, "query-swdb",
N_("query the software version database")),
ARGPARSE_c (aReload, "reload", N_("reload all or a given component")),
ARGPARSE_c (aLaunch, "launch", N_("launch a given component")),
ARGPARSE_c (aKill, "kill", N_("kill a given component")),
ARGPARSE_c (aCreateSocketDir, "create-socketdir", "@"),
ARGPARSE_c (aRemoveSocketDir, "remove-socketdir", "@"),
ARGPARSE_c (aShowVersions, "show-versions", ""),
ARGPARSE_c (aShowConfigs, "show-configs", ""),
/* hidden commands: for debugging */
ARGPARSE_c (aShowCodepages, "show-codepages", "@"),
ARGPARSE_c (aDotlockLock, "lock", "@"),
ARGPARSE_c (aDotlockUnlock, "unlock", "@"),
ARGPARSE_header (NULL, N_("@\nOptions:\n ")),
ARGPARSE_s_s (oOutput, "output", N_("use as output file")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("quiet")),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_n (oRuntime, "runtime",
N_("activate changes at runtime, if possible")),
ARGPARSE_s_i (oStatusFD, "status-fd",
N_("|FD|write status info to this FD")),
/* hidden options */
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_s (oBuilddir, "build-prefix", "@"),
ARGPARSE_s_n (oNull, "null", "@"),
ARGPARSE_s_n (oNoVerbose, "no-verbose", "@"),
ARGPARSE_s_n (oShowSocket, "show-socket", "@"),
ARGPARSE_s_s (oChUid, "chuid", "@"),
ARGPARSE_end ()
};
#define CUTLINE_FMT \
"--8<---------------cut here---------------%s------------->8---\n"
/* The stream to output the status information. Status Output is disabled if
* this is NULL. */
static estream_t statusfp;
static void show_versions (estream_t fp);
static void show_configs (estream_t fp);
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@GPGCONF@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40: p = _("Usage: @GPGCONF@ [options] (-h for help)");
break;
case 41:
p = _("Syntax: @GPGCONF@ [options]\n"
"Manage configuration options for tools of the @GNUPG@ system\n");
break;
default: p = NULL; break;
}
return p;
}
/* Return the fp for the output. This is usually stdout unless
--output has been used. In the latter case this function opens
that file. */
static estream_t
get_outfp (estream_t *fp)
{
if (!*fp)
{
if (opt.outfile)
{
*fp = es_fopen (opt.outfile, "w");
if (!*fp)
gc_error (1, errno, "can not open '%s'", opt.outfile);
}
else
*fp = es_stdout;
}
return *fp;
}
/* Set the status FD. */
static void
set_status_fd (int fd)
{
static int last_fd = -1;
if (fd != -1 && last_fd == fd)
return;
if (statusfp && statusfp != es_stdout && statusfp != es_stderr)
es_fclose (statusfp);
statusfp = NULL;
if (fd == -1)
return;
if (fd == 1)
statusfp = es_stdout;
else if (fd == 2)
statusfp = es_stderr;
else
statusfp = es_fdopen (fd, "w");
if (!statusfp)
{
log_fatal ("can't open fd %d for status output: %s\n",
fd, gpg_strerror (gpg_error_from_syserror ()));
}
last_fd = fd;
}
/* Write a status line with code NO followed by the output of the
* printf style FORMAT. The caller needs to make sure that LFs and
* CRs are not printed. */
void
gpgconf_write_status (int no, const char *format, ...)
{
va_list arg_ptr;
if (!statusfp)
return; /* Not enabled. */
es_fputs ("[GNUPG:] ", statusfp);
es_fputs (get_status_string (no), statusfp);
if (format)
{
es_putc (' ', statusfp);
va_start (arg_ptr, format);
es_vfprintf (statusfp, format, arg_ptr);
va_end (arg_ptr);
}
es_putc ('\n', statusfp);
}
static void
list_dirs (estream_t fp, char **names, int show_config_mode)
{
static struct {
const char *name; /* If NULL only a file check will be done. */
const char *(*fnc)(void);
const char *extra;
} list[] = {
{ "sysconfdir", gnupg_sysconfdir, NULL },
{ "bindir", gnupg_bindir, NULL },
{ "libexecdir", gnupg_libexecdir, NULL },
{ "libdir", gnupg_libdir, NULL },
{ "datadir", gnupg_datadir, NULL },
{ "localedir", gnupg_localedir, NULL },
{ "socketdir", gnupg_socketdir, NULL },
{ "dirmngr-socket", dirmngr_socket_name, NULL,},
{ "keyboxd-socket", keyboxd_socket_name, NULL,},
{ "agent-ssh-socket", gnupg_socketdir, GPG_AGENT_SSH_SOCK_NAME },
{ "agent-extra-socket", gnupg_socketdir, GPG_AGENT_EXTRA_SOCK_NAME },
{ "agent-browser-socket",gnupg_socketdir, GPG_AGENT_BROWSER_SOCK_NAME },
{ "agent-socket", gnupg_socketdir, GPG_AGENT_SOCK_NAME },
{ NULL, gnupg_socketdir, "S.uiserver" },
{ "homedir", gnupg_homedir, NULL }
};
int idx, j;
char *tmp;
const char *s;
gpg_error_t err;
if (show_config_mode)
es_fprintf (fp, "#+begin_example\n");
for (idx = 0; idx < DIM (list); idx++)
{
s = list[idx].fnc ();
if (list[idx].extra)
{
tmp = make_filename (s, list[idx].extra, NULL);
s = tmp;
}
else
tmp = NULL;
if (!list[idx].name)
;
else if (!names)
es_fprintf (fp, "%s%s:%s\n", show_config_mode? " ":"",
list[idx].name, gc_percent_escape (s));
else
{
for (j=0; names[j]; j++)
if (!strcmp (names[j], list[idx].name))
{
if (show_config_mode)
es_fputs (" ", fp);
es_fputs (s, fp);
es_putc (opt.null? '\0':'\n', fp);
}
}
/* In show config mode check that the socket files are accessible. */
if (list[idx].extra && show_config_mode)
{
estream_t tmpfp;
tmpfp = es_fopen (s, "rb");
if (tmpfp)
es_fclose (tmpfp); /* All fine - we can read that file. */
else if ((err=gpg_error_from_syserror ()) == GPG_ERR_ENOENT
|| err == GPG_ERR_ENXIO)
; /* No such file/ No such device or address - this is okay. */
else
es_fprintf (fp,
"# Warning: error reading existing file '%s': %s\n",
s, gpg_strerror (err));
}
xfree (tmp);
}
if (show_config_mode)
es_fprintf (fp, "#+end_example\n");
#ifdef HAVE_W32_SYSTEM
tmp = read_w32_registry_string (NULL,
gnupg_registry_dir (),
"HomeDir");
if (tmp)
{
int hkcu = 0;
int hklm = 0;
xfree (tmp);
if ((tmp = read_w32_registry_string ("HKEY_CURRENT_USER",
gnupg_registry_dir (),
"HomeDir")))
{
xfree (tmp);
hkcu = 1;
}
if ((tmp = read_w32_registry_string ("HKEY_LOCAL_MACHINE",
gnupg_registry_dir (),
"HomeDir")))
{
xfree (tmp);
hklm = 1;
}
es_fflush (fp);
if (show_config_mode)
es_fprintf (fp, "\n"
"Note: homedir taken from registry key %s%s\\%s:%s\n"
"\n",
hkcu?"HKCU":"", hklm?"HKLM":"",
gnupg_registry_dir (), "HomeDir");
else
log_info ("Warning: homedir taken from registry key (%s:%s) in%s%s\n",
gnupg_registry_dir (), "HomeDir",
hkcu?" HKCU":"",
hklm?" HKLM":"");
}
else if ((tmp = read_w32_registry_string (NULL,
gnupg_registry_dir (),
NULL)))
{
xfree (tmp);
es_fflush (fp);
if (show_config_mode)
es_fprintf (fp, "\n"
"Note: registry %s without value in HKCU or HKLM\n"
"\n", GNUPG_REGISTRY_DIR);
else
log_info ("Warning: registry key (%s) without value in HKCU or HKLM\n",
gnupg_registry_dir ());
}
#else /*!HAVE_W32_SYSTEM*/
(void)show_config_mode;
#endif /*!HAVE_W32_SYSTEM*/
}
/* Check whether NAME is valid argument for query_swdb(). Valid names
* start with a letter and contain only alphanumeric characters or an
* underscore. */
static int
valid_swdb_name_p (const char *name)
{
if (!name || !*name || !alphap (name))
return 0;
for (name++; *name; name++)
if (!alnump (name) && *name != '_')
return 0;
return 1;
}
/* Query the SWDB file. If necessary and possible this functions asks
* the dirmngr to load an updated version of that file. The caller
* needs to provide the NAME to query (e.g. "gnupg", "libgcrypt") and
* optional the currently installed version in CURRENT_VERSION. The
* output written to OUT is a colon delimited line with these fields:
*
* name :: The name of the package
* curvers:: The installed version if given.
* status :: This value tells the status of the software package
* '-' :: No information available
* (error or CURRENT_VERSION not given)
* '?' :: Unknown NAME
* 'u' :: Update available
* 'c' :: The version is Current
* 'n' :: The current version is already Newer than the
* available one.
* urgency :: If the value is greater than zero an urgent update is required.
* error :: 0 on success or an gpg_err_code_t
* Common codes seen:
* GPG_ERR_TOO_OLD :: The SWDB file is to old to be used.
* GPG_ERR_ENOENT :: The SWDB file is not available.
* GPG_ERR_BAD_SIGNATURE :: Corrupted SWDB file.
* filedate:: Date of the swdb file (yyyymmddThhmmss)
* verified:: Date we checked the validity of the file (yyyyymmddThhmmss)
* version :: The version string from the swdb.
* reldate :: Release date of that version (yyyymmddThhmmss)
* size :: Size of the package in bytes.
* hash :: SHA-2 hash of the package.
*
*/
static void
query_swdb (estream_t out, const char *name, const char *current_version)
{
gpg_error_t err;
const char *search_name;
char *fname = NULL;
estream_t fp = NULL;
char *line = NULL;
char *self_version = NULL;
size_t length_of_line = 0;
size_t maxlen;
ssize_t len;
const char *fields[2];
char *p;
gnupg_isotime_t filedate = {0};
gnupg_isotime_t verified = {0};
char *value_ver = NULL;
gnupg_isotime_t value_date = {0};
char *value_size = NULL;
char *value_sha2 = NULL;
unsigned long value_size_ul = 0;
int status, i;
if (!valid_swdb_name_p (name))
{
log_error ("error in package name '%s': %s\n",
name, gpg_strerror (GPG_ERR_INV_NAME));
goto leave;
}
if (!strcmp (name, "gnupg"))
search_name = GNUPG_SWDB_TAG;
else if (!strcmp (name, "gnupg1"))
search_name = "gnupg1";
else
search_name = name;
if (!current_version && !strcmp (name, "gnupg"))
{
/* Use our own version but string a possible beta string. */
self_version = xstrdup (PACKAGE_VERSION);
p = strchr (self_version, '-');
if (p)
*p = 0;
current_version = self_version;
}
if (current_version && (strchr (current_version, ':')
|| compare_version_strings (current_version, NULL)))
{
log_error ("error in version string '%s': %s\n",
current_version, gpg_strerror (GPG_ERR_INV_ARG));
goto leave;
}
fname = make_filename (gnupg_homedir (), "swdb.lst", NULL);
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error (_("error opening '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Note that the parser uses the first occurrence of a matching
* values and ignores possible duplicated values. */
maxlen = 2048; /* Set limit. */
while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0)
{
if (!maxlen)
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
/* Strip newline and carriage return, if present. */
while (len > 0 && (line[len - 1] == '\n' || line[len - 1] == '\r'))
line[--len] = '\0';
if (split_fields (line, fields, DIM (fields)) < DIM(fields))
continue; /* Skip empty lines and names w/o a value. */
if (*fields[0] == '#')
continue; /* Skip comments. */
/* Record the meta data. */
if (!*filedate && !strcmp (fields[0], ".filedate"))
{
string2isotime (filedate, fields[1]);
continue;
}
if (!*verified && !strcmp (fields[0], ".verified"))
{
string2isotime (verified, fields[1]);
continue;
}
/* Tokenize the name. */
p = strrchr (fields[0], '_');
if (!p)
continue; /* Name w/o an underscore. */
*p++ = 0;
/* Wait for the requested name. */
if (!strcmp (fields[0], search_name))
{
if (!strcmp (p, "ver") && !value_ver)
value_ver = xstrdup (fields[1]);
else if (!strcmp (p, "date") && !*value_date)
string2isotime (value_date, fields[1]);
else if (!strcmp (p, "size") && !value_size)
value_size = xstrdup (fields[1]);
else if (!strcmp (p, "sha2") && !value_sha2)
value_sha2 = xstrdup (fields[1]);
}
}
if (len < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error (_("error reading '%s': %s\n"), fname, gpg_strerror (err));
goto leave;
}
if (!*filedate || !*verified)
{
err = gpg_error (GPG_ERR_INV_TIME);
es_fprintf (out, "%s:%s:-::%u:::::::\n",
name,
current_version? current_version : "",
gpg_err_code (err));
goto leave;
}
if (!value_ver)
{
es_fprintf (out, "%s:%s:?:::::::::\n",
name,
current_version? current_version : "");
goto leave;
}
if (value_size)
{
gpg_err_set_errno (0);
value_size_ul = strtoul (value_size, &p, 10);
if (errno)
value_size_ul = 0;
else if (*p == 'k')
value_size_ul *= 1024;
}
err = 0;
status = '-';
if (compare_version_strings (value_ver, NULL))
err = gpg_error (GPG_ERR_INV_VALUE);
else if (!current_version)
;
else if (!(i = compare_version_strings (value_ver, current_version)))
status = 'c';
else if (i > 0)
status = 'u';
else
status = 'n';
es_fprintf (out, "%s:%s:%c::%d:%s:%s:%s:%s:%lu:%s:\n",
name,
current_version? current_version : "",
status,
err,
filedate,
verified,
value_ver,
value_date,
value_size_ul,
value_sha2? value_sha2 : "");
leave:
xfree (value_ver);
xfree (value_size);
xfree (value_sha2);
xfree (line);
es_fclose (fp);
xfree (fname);
xfree (self_version);
}
#if !defined(HAVE_W32_SYSTEM)
/* dotlock tool to handle dotlock by command line
DO_LOCK: 1 for to lock, 0 for unlock
FILENAME: filename for the dotlock */
static void
dotlock_tool (int do_lock, const char *filename)
{
dotlock_t h;
unsigned int flags = DOTLOCK_LOCK_BY_PARENT;
if (!do_lock)
flags |= DOTLOCK_LOCKED;
h = dotlock_create (filename, flags);
if (!h)
{
if (do_lock)
log_error ("error creating the lock file\n");
else
log_error ("no lock file found\n");
return;
}
if (do_lock)
{
if (dotlock_take (h, 0))
log_error ("error taking the lock\n");
}
else
dotlock_release (h);
dotlock_destroy (h);
}
#endif
/* gpgconf main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
gpgrt_argparse_t pargs;
const char *fname;
int no_more_options = 0;
enum cmd_and_opt_values cmd = 0;
estream_t outfp = NULL;
int show_socket = 0;
const char *changeuser = NULL;
early_system_init ();
gnupg_reopen_std (GPGCONF_NAME);
gpgrt_set_strusage (my_strusage);
log_set_prefix (GPGCONF_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
gc_components_init ();
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
while (!no_more_options && gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oOutput: opt.outfile = pargs.r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oDryRun: opt.dry_run = 1; break;
case oRuntime: opt.runtime = 1; break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oBuilddir: gnupg_set_builddir (pargs.r.ret_str); break;
case oNull: opt.null = 1; break;
case oStatusFD:
set_status_fd (translate_sys2libc_fd_int (pargs.r.ret_int, 1));
break;
case oShowSocket: show_socket = 1; break;
case oChUid: changeuser = pargs.r.ret_str; break;
case aListDirs:
case aListComponents:
case aCheckPrograms:
case aListOptions:
case aChangeOptions:
case aCheckOptions:
case aApplyDefaults:
case aApplyProfile:
case aListConfig:
case aCheckConfig:
case aQuerySWDB:
case aReload:
case aLaunch:
case aKill:
case aCreateSocketDir:
case aRemoveSocketDir:
case aShowVersions:
case aShowConfigs:
case aShowCodepages:
case aDotlockLock:
case aDotlockUnlock:
cmd = pargs.r_opt;
break;
default: pargs.err = 2; break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (log_get_errorcount (0))
gpgconf_failure (GPG_ERR_USER_2);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
fname = argc ? *argv : NULL;
/* If requested switch to the requested user or die. */
if (changeuser && (err = gnupg_chuid (changeuser, 0)))
gpgconf_failure (err);
- /* Set the configuraton directories for use by gpgrt_argparser. We
+ /* Set the configuration directories for use by gpgrt_argparser. We
* don't have a configuration file for this program but we have code
* which reads the component's config files. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
switch (cmd)
{
case aListComponents:
default:
/* List all components. */
gc_component_list_components (get_outfp (&outfp));
break;
case aCheckPrograms:
/* Check all programs. */
gc_check_programs (get_outfp (&outfp));
break;
case aListOptions:
case aChangeOptions:
case aCheckOptions:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
else
{
int idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
if (cmd == aCheckOptions)
gc_component_check_options (idx, get_outfp (&outfp), NULL);
else
{
gc_component_retrieve_options (idx);
if (gc_process_gpgconf_conf (NULL, 1, 0, NULL))
gpgconf_failure (0);
if (cmd == aListOptions)
gc_component_list_options (idx, get_outfp (&outfp));
else if (cmd == aChangeOptions)
gc_component_change_options (idx, es_stdin,
get_outfp (&outfp), 0);
}
}
break;
case aLaunch:
case aKill:
if (!fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("Need one component argument"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
else if (!strcmp (fname, "all"))
{
if (cmd == aLaunch)
{
if (gc_component_launch (-1))
gpgconf_failure (0);
}
else
{
gc_component_kill (-1);
}
}
else
{
/* Launch/Kill a given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
else if (cmd == aLaunch)
{
err = gc_component_launch (idx);
if (show_socket)
{
char *names[2];
if (idx == GC_COMPONENT_GPG_AGENT)
names[0] = "agent-socket";
else if (idx == GC_COMPONENT_DIRMNGR)
names[0] = "dirmngr-socket";
else if (idx == GC_COMPONENT_KEYBOXD)
names[0] = "keyboxd-socket";
else
names[0] = NULL;
names[1] = NULL;
get_outfp (&outfp);
list_dirs (outfp, names, 0);
}
if (err)
gpgconf_failure (0);
}
else
{
/* We don't error out if the kill failed because this
command should do nothing if the component is not
running. */
gc_component_kill (idx);
}
}
break;
case aReload:
if (!fname || !strcmp (fname, "all"))
{
/* Reload all. */
gc_component_reload (-1);
}
else
{
/* Reload given component. */
int idx;
idx = gc_component_find (fname);
if (idx < 0)
{
es_fputs (_("Component not found"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (0);
}
else
{
gc_component_reload (idx);
}
}
break;
case aListConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, get_outfp (&outfp)))
gpgconf_failure (0);
break;
case aCheckConfig:
if (gc_process_gpgconf_conf (fname, 0, 0, NULL))
gpgconf_failure (0);
break;
case aApplyDefaults:
if (fname)
{
es_fprintf (es_stderr, _("usage: %s [options] "), GPGCONF_NAME);
es_putc ('\n', es_stderr);
es_fputs (_("No argument allowed"), es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_USER_2);
}
if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK))
gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet);
gc_component_retrieve_options (-1);
if (gc_process_gpgconf_conf (NULL, 1, 1, NULL))
gpgconf_failure (0);
break;
case aApplyProfile:
if (!opt.dry_run && gnupg_access (gnupg_homedir (), F_OK))
gnupg_maybe_make_homedir (gnupg_homedir (), opt.quiet);
gc_component_retrieve_options (-1);
if (gc_apply_profile (fname))
gpgconf_failure (0);
break;
case aListDirs:
/* Show the system configuration directories for gpgconf. */
get_outfp (&outfp);
list_dirs (outfp, argc? argv : NULL, 0);
break;
case aQuerySWDB:
/* Query the software version database. */
if (!fname || argc > 2)
{
es_fprintf (es_stderr, "usage: %s --query-swdb NAME [VERSION]\n",
GPGCONF_NAME);
gpgconf_failure (GPG_ERR_USER_2);
}
get_outfp (&outfp);
query_swdb (outfp, fname, argc > 1? argv[1] : NULL);
break;
case aCreateSocketDir:
{
char *socketdir;
unsigned int flags;
/* Make sure that the top /run/user/UID/gnupg dir has been
* created. */
gnupg_socketdir ();
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 64) && !opt.dry_run)
{
/* No sub dir - create it. */
if (gnupg_mkdir (socketdir, "-rwx"))
gc_error (1, errno, "error creating '%s'", socketdir);
/* Try again. */
xfree (socketdir);
socketdir = _gnupg_socketdir_internal (1, &flags);
}
/* Give some info. */
if ( (flags & ~32) || opt.verbose || opt.dry_run)
{
log_info ("socketdir is '%s'\n", socketdir);
if ((flags & 1)) log_info ("\tgeneral error\n");
if ((flags & 2)) log_info ("\tno /run/user dir\n");
if ((flags & 4)) log_info ("\tbad permissions\n");
if ((flags & 8)) log_info ("\tbad permissions (subdir)\n");
if ((flags & 16)) log_info ("\tmkdir failed\n");
if ((flags & 32)) log_info ("\tnon-default homedir\n");
if ((flags & 64)) log_info ("\tno such subdir\n");
if ((flags & 128)) log_info ("\tusing homedir as fallback\n");
}
if ((flags & ~32) && !opt.dry_run)
gc_error (1, 0, "error creating socket directory");
xfree (socketdir);
}
break;
case aRemoveSocketDir:
{
char *socketdir;
unsigned int flags;
/* Check the /var/run dir. */
socketdir = _gnupg_socketdir_internal (1, &flags);
if ((flags & 128))
log_info ("ignoring request to remove non /run/user socket dir\n");
else if (opt.dry_run)
;
else if (gnupg_rmdir (socketdir))
{
/* If the director is not empty we first try to delete
* socket files. */
err = gpg_error_from_syserror ();
if (gpg_err_code (err) == GPG_ERR_ENOTEMPTY
|| gpg_err_code (err) == GPG_ERR_EEXIST)
{
static const char * const names[] = {
GPG_AGENT_SOCK_NAME,
GPG_AGENT_EXTRA_SOCK_NAME,
GPG_AGENT_BROWSER_SOCK_NAME,
GPG_AGENT_SSH_SOCK_NAME,
SCDAEMON_SOCK_NAME,
KEYBOXD_SOCK_NAME,
DIRMNGR_SOCK_NAME,
TPM2DAEMON_SOCK_NAME
};
int i;
char *p;
for (i=0; i < DIM(names); i++)
{
p = strconcat (socketdir , "/", names[i], NULL);
if (p)
gnupg_remove (p);
xfree (p);
}
if (gnupg_rmdir (socketdir))
{
err = gpg_error_from_syserror ();
gc_error (1, 0, "error removing '%s': %s",
socketdir, gpg_strerror (err));
}
}
else if (gpg_err_code (err) == GPG_ERR_ENOENT)
gc_error (0, 0, "warning: removing '%s' failed: %s",
socketdir, gpg_strerror (err));
else
gc_error (1, 0, "error removing '%s': %s",
socketdir, gpg_strerror (err));
}
xfree (socketdir);
}
break;
case aShowVersions:
{
get_outfp (&outfp);
show_versions (outfp);
}
break;
case aShowConfigs:
{
get_outfp (&outfp);
show_configs (outfp);
}
break;
case aShowCodepages:
#ifdef HAVE_W32_SYSTEM
{
get_outfp (&outfp);
if (GetConsoleCP () != GetConsoleOutputCP ())
es_fprintf (outfp, "Console: CP%u/CP%u\n",
GetConsoleCP (), GetConsoleOutputCP ());
else
es_fprintf (outfp, "Console: CP%u\n", GetConsoleCP ());
es_fprintf (outfp, "ANSI: CP%u\n", GetACP ());
es_fprintf (outfp, "OEM: CP%u\n", GetOEMCP ());
}
#endif
break;
case aDotlockLock:
case aDotlockUnlock:
#if !defined(HAVE_W32_SYSTEM)
if (!fname)
{
es_fprintf (es_stderr, "usage: %s --%slock NAME",
GPGCONF_NAME, cmd==aDotlockUnlock?"un":"");
es_putc ('\n', es_stderr);
es_fputs ("Need name of file protected by the lock", es_stderr);
es_putc ('\n', es_stderr);
gpgconf_failure (GPG_ERR_SYNTAX);
}
else
{
char *filename;
/* Keybox pubring.db lock is under public-keys.d. */
if (!strcmp (fname, "pubring.db"))
fname = "public-keys.d/pubring.db";
filename = make_absfilename (gnupg_homedir (), fname, NULL);
dotlock_tool (cmd == aDotlockLock, filename);
xfree (filename);
}
#endif
break;
}
if (outfp != es_stdout)
if (es_fclose (outfp))
gc_error (1, errno, "error closing '%s'", opt.outfile);
if (log_get_errorcount (0))
gpgconf_failure (0);
else
gpgconf_write_status (STATUS_SUCCESS, NULL);
return 0;
}
void
gpgconf_failure (gpg_error_t err)
{
log_flush ();
if (!err)
err = gpg_error (GPG_ERR_GENERAL);
gpgconf_write_status
(STATUS_FAILURE, "- %u",
gpg_err_code (err) == GPG_ERR_USER_2? GPG_ERR_EINVAL : err);
exit (gpg_err_code (err) == GPG_ERR_USER_2? 2 : 1);
}
/* Parse the revision part from the extended version blurb. */
static const char *
get_revision_from_blurb (const char *blurb, int *r_len)
{
const char *s = blurb? blurb : "";
int n;
for (; *s; s++)
if (*s == '\n' && s[1] == '(')
break;
if (s)
{
s += 2;
for (n=0; s[n] && s[n] != ' '; n++)
;
}
else
{
s = "?";
n = 1;
}
*r_len = n;
return s;
}
static void
show_version_gnupg (estream_t fp, const char *prefix)
{
char *fname, *p, *p0;
size_t n;
estream_t verfp;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
es_fprintf (fp, "%s%sGnuPG %s (%s)\n%s%s\n", prefix, *prefix?"":"* ",
gpgrt_strusage (13), BUILD_REVISION, prefix, gpgrt_strusage (17));
/* Show the GnuPG VS-Desktop version in --show-configs mode */
if (prefix && *prefix)
{
fname = make_filename (gnupg_bindir (), NULL);
n = strlen (fname);
if (n > 10 && (!ascii_strcasecmp (fname + n - 10, "/GnuPG/bin")
|| !ascii_strcasecmp (fname + n - 10, "\\GnuPG\\bin")))
{
/* Append VERSION to the ../../ directory. Note that VERSION
* is only 7 bytes and thus fits. */
strcpy (fname + n - 9, "VERSION");
verfp = es_fopen (fname, "r");
if (!verfp)
es_fprintf (fp, "%s[VERSION file not found]\n", prefix);
else
{
int lnr = 0;
p0 = NULL;
while ((length = es_read_line (verfp, &line, &line_len, NULL))>0)
{
lnr++;
trim_spaces (line);
if (lnr == 1 && *line != '[')
{
/* Old file format where we look only at the
* first line. */
p0 = line;
break;
}
else if (!strncmp (line, "version=", 8))
{
p0 = line + 8;
break;
}
}
if (length < 0 || es_ferror (verfp))
es_fprintf (fp, "%s[VERSION file read error]\n", prefix);
else if (p0)
{
for (p=p0; *p; p++)
if (*p < ' ' || *p > '~' || *p == '[')
*p = '?';
es_fprintf (fp, "%s%s\n", prefix, p0);
}
else
es_fprintf (fp, "%s[VERSION file is empty]\n", prefix);
es_fclose (verfp);
}
}
xfree (fname);
}
xfree (line);
#ifdef HAVE_W32_SYSTEM
{
OSVERSIONINFO osvi = { sizeof (osvi) };
GetVersionEx (&osvi);
es_fprintf (fp, "%sWindows %lu.%lu build %lu%s%s%s\n",
prefix,
(unsigned long)osvi.dwMajorVersion,
(unsigned long)osvi.dwMinorVersion,
(unsigned long)osvi.dwBuildNumber,
*osvi.szCSDVersion? " (":"",
osvi.szCSDVersion,
*osvi.szCSDVersion? ")":""
);
}
#endif /*HAVE_W32_SYSTEM*/
}
static void
show_version_libgcrypt (estream_t fp)
{
const char *s;
int n;
s = get_revision_from_blurb (gcry_check_version ("\x01\x01"), &n);
es_fprintf (fp, "* Libgcrypt %s (%.*s)\n",
gcry_check_version (NULL), n, s);
s = gcry_get_config (0, NULL);
if (s)
es_fputs (s, fp);
}
static void
show_version_gpgrt (estream_t fp)
{
const char *s;
int n;
s = get_revision_from_blurb (gpg_error_check_version ("\x01\x01"), &n);
es_fprintf (fp, "* GpgRT %s (%.*s)\n",
gpg_error_check_version (NULL), n, s);
}
/* Printing version information for other libraries is problematic
* because we don't want to link gpgconf to all these libraries. The
* best solution is delegating this to dirmngr which uses libassuan,
* libksba, libnpth and ntbtls anyway. */
static void
show_versions_via_dirmngr (estream_t fp)
{
gpg_error_t err;
const char *pgmname;
const char *argv[2];
estream_t outfp;
gnupg_process_t proc;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
pgmname = gnupg_module_name (GNUPG_MODULE_NAME_DIRMNGR);
argv[0] = "--gpgconf-versions";
argv[1] = NULL;
err = gnupg_process_spawn (pgmname, argv, GNUPG_PROCESS_STDOUT_PIPE,
NULL, &proc);
if (err)
{
log_error ("error spawning %s: %s", pgmname, gpg_strerror (err));
es_fprintf (fp, "[error: can't get further info]\n");
return;
}
gnupg_process_get_streams (proc, 0, NULL, &outfp, NULL);
while ((length = es_read_line (outfp, &line, &line_len, NULL)) > 0)
{
/* Strip newline and carriage return, if present. */
while (length > 0
&& (line[length - 1] == '\n' || line[length - 1] == '\r'))
line[--length] = '\0';
es_fprintf (fp, "%s\n", line);
}
if (length < 0 || es_ferror (outfp))
{
err = gpg_error_from_syserror ();
log_error ("error reading from %s: %s\n", pgmname, gpg_strerror (err));
}
if (es_fclose (outfp))
{
err = gpg_error_from_syserror ();
log_error ("error closing output stream of %s: %s\n",
pgmname, gpg_strerror (err));
}
err = gnupg_process_wait (proc, 1);
if (!err)
{
int exitcode;
gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode);
log_error ("running %s failed (exitcode=%d): %s\n",
pgmname, exitcode, gpg_strerror (err));
es_fprintf (fp, "[error: can't get further info]\n");
}
gnupg_process_release (proc);
xfree (line);
}
/* Show all kind of version information. */
static void
show_versions (estream_t fp)
{
show_version_gnupg (fp, "");
es_fputc ('\n', fp);
show_version_libgcrypt (fp);
es_fputc ('\n', fp);
show_version_gpgrt (fp);
es_fputc ('\n', fp);
show_versions_via_dirmngr (fp);
}
/* Copy data from file SRC to DST. Returns 0 on success or an error
* code on failure. If LISTP is not NULL, that strlist is updated
* with the variable or registry key names detected. Flag bit 0
* indicates a registry entry. */
static gpg_error_t
my_copy_file (estream_t src, estream_t dst, strlist_t *listp)
{
gpg_error_t err;
char *line = NULL;
size_t line_len = 0;
ssize_t length;
int written;
while ((length = es_read_line (src, &line, &line_len, NULL)) > 0)
{
/* Prefix each line with two spaces but use a comma if the line
* starts with a special org-mode character. */
if (*line == '*' || (*line == '#' && line[1] == '+'))
es_fputc (',', dst);
else
es_fputc (' ', dst);
es_fputc (' ', dst);
written = gpgrt_fwrite (line, 1, length, dst);
if (written != length)
return gpg_error_from_syserror ();
trim_spaces (line);
if (*line == '[' && listp)
{
char **tokens;
char *p;
for (p=line+1; *p; p++)
if (*p != ' ' && *p != '\t')
break;
if (*p && p[strlen (p)-1] == ']')
p[strlen (p)-1] = 0;
tokens = strtokenize (p, " \t");
if (!tokens)
{
err = gpg_error_from_syserror ();
log_error ("strtokenize failed: %s\n", gpg_strerror (err));
return err;
}
/* Check whether we have a getreg or getenv statement and
* store the third token to later retrieval. */
if (tokens[0] && tokens[1] && tokens[2]
&& (!strcmp (tokens[0], "getreg")
|| !strcmp (tokens[0], "getenv")))
{
int isreg = (tokens[0][3] == 'r');
strlist_t sl = *listp;
for (sl = *listp; sl; sl = sl->next)
if (!strcmp (sl->d, tokens[2]) && (sl->flags & 1) == isreg)
break;
if (!sl) /* Not yet in the respective list. */
{
sl = add_to_strlist (listp, tokens[2]);
if (isreg)
sl->flags = 1;
}
}
xfree (tokens);
}
}
if (length < 0 || es_ferror (src))
return gpg_error_from_syserror ();
if (gpgrt_fflush (dst))
return gpg_error_from_syserror ();
return 0;
}
/* Helper for show_configs */
static void
show_configs_one_file (const char *fname, int global, estream_t outfp,
strlist_t *listp)
{
gpg_error_t err;
estream_t fp;
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
es_fprintf (outfp, "** %s config \"%s\": %s\n",
global? "global":"local", fname, gpg_strerror (err));
}
else
{
es_fprintf (outfp, "** %s config \"%s\"\n#+begin_src\n",
global? "global":"local", fname);
err = my_copy_file (fp, outfp, listp);
es_fprintf (outfp, "\n#+end_src\n");
if (err)
log_error ("Error copying file \"%s\": %s\n",
fname, gpg_strerror (err));
es_fclose (fp);
}
}
#ifdef HAVE_W32_SYSTEM
/* Print registry entries relevant to the GnuPG system and related
* software. */
static void
show_other_registry_entries (estream_t outfp)
{
static struct {
int group;
const char *name;
unsigned int prependregkey:1;
} names[] =
{
{ 1, "HKLM\\Software\\Gpg4win:Install Directory" },
{ 1, "HKLM\\Software\\Gpg4win:Desktop-Version" },
{ 1, "HKLM\\Software\\Gpg4win:VS-Desktop-Version" },
{ 1, ":HomeDir", 1 },
{ 1, ":DefaultLogFile", 1 },
{ 2, "\\Software\\Microsoft\\Office\\Outlook\\Addins\\GNU.GpgOL"
":LoadBehavior" },
{ 2, "HKCU\\Software\\Microsoft\\Office\\16.0\\Outlook\\Options\\Mail:"
"ReadAsPlain" },
{ 2, "HKCU\\Software\\Policies\\Microsoft\\Office\\16.0\\Outlook\\"
"Options\\Mail:ReadAsPlain" },
{ 3, "logFile" },
{ 3, "enableDebug" },
{ 3, "searchSmimeServers" },
{ 3, "smimeInsecureReplyAllowed" },
{ 3, "enableSmime" },
{ 3, "preferSmime" },
{ 3, "encryptDefault" },
{ 3, "signDefault" },
{ 3, "inlinePGP" },
{ 3, "replyCrypt" },
{ 3, "autoresolve" },
{ 3, "autoretrieve" },
{ 3, "automation" },
{ 3, "autosecure" },
{ 3, "autotrust" },
{ 3, "autoencryptUntrusted" },
{ 3, "autoimport" },
{ 3, "splitBCCMails" },
{ 3, "combinedOpsEnabled" },
{ 3, "encryptSubject" },
{ 0, NULL }
};
int idx;
int group = 0;
char *namebuf = NULL;
const char *name;
int from_hklm;
for (idx=0; (name = names[idx].name); idx++)
{
char *value;
if (names[idx].group == 3)
{
xfree (namebuf);
namebuf = xstrconcat ("\\Software\\GNU\\GpgOL", ":",
names[idx].name, NULL);
name = namebuf;
}
else if (names[idx].prependregkey)
{
xfree (namebuf);
namebuf = xstrconcat ("\\", gnupg_registry_dir (),
names[idx].name, NULL);
name = namebuf;
}
value = read_w32_reg_string (name, &from_hklm);
if (!value)
continue;
if (names[idx].group != group)
{
group = names[idx].group;
es_fprintf (outfp, "\n%s related:\n",
group == 1 ? "GnuPG Desktop" :
group == 2 ? "Outlook" :
group == 3 ? "\\Software\\GNU\\GpgOL"
: "System" );
}
if (group == 3)
es_fprintf (outfp, " %s=%s%s\n", names[idx].name, value,
from_hklm? " [hklm]":"");
else
es_fprintf (outfp, " %s\n ->%s<-%s\n", name, value,
from_hklm? " [hklm]":"");
xfree (value);
}
xfree (namebuf);
}
/* Print registry entries take from a configuration file. */
static void
show_registry_entries_from_file (estream_t outfp)
{
gpg_error_t err;
char *fname;
estream_t fp;
char *line = NULL;
size_t length_of_line = 0;
size_t maxlen;
ssize_t len;
char *value = NULL;
int from_hklm;
int any = 0;
fname = make_filename (gnupg_datadir (), "gpgconf.rnames", NULL);
fp = es_fopen (fname, "r");
if (!fp)
{
err = gpg_error_from_syserror ();
if (gpg_err_code (err) != GPG_ERR_ENOENT)
log_error ("error opening '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
maxlen = 2048; /* Set limit. */
while ((len = es_read_line (fp, &line, &length_of_line, &maxlen)) > 0)
{
if (!maxlen)
{
err = gpg_error (GPG_ERR_LINE_TOO_LONG);
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
goto leave;
}
trim_spaces (line);
if (*line == '#')
continue;
xfree (value);
value = read_w32_reg_string (line, &from_hklm);
if (!value)
continue;
if (!any)
{
any = 1;
es_fprintf (outfp, "Taken from gpgconf.rnames:\n");
}
es_fprintf (outfp, " %s\n ->%s<-%s\n", line, value,
from_hklm? " [hklm]":"");
}
if (len < 0 || es_ferror (fp))
{
err = gpg_error_from_syserror ();
log_error ("error reading '%s': %s\n", fname, gpg_strerror (err));
}
leave:
xfree (value);
xfree (line);
es_fclose (fp);
xfree (fname);
}
#endif /*HAVE_W32_SYSTEM*/
/* Show all config files. */
static void
show_configs (estream_t outfp)
{
static const char *names[] = { "common.conf", "gpg-agent.conf",
"scdaemon.conf", "dirmngr.conf",
"gpg.conf", "gpgsm.conf" };
static const char *envvars[] = { "PATH",
"http_proxy", "HTTP_PROXY",
"https_proxy", "HTTPS_PROXY",
"LD_LIBRARY_PATH", "LD_PRELOAD",
"LD_AUDIT", "LD_ORIGIN_PATH" };
gpg_error_t err;
int idx;
char *fname;
gnupg_dir_t dir;
gnupg_dirent_t dir_entry;
size_t n;
int any, anywarn;
strlist_t list = NULL;
strlist_t sl;
const char *s;
int got_gpgconfconf = 0;
es_fprintf (outfp, "# gpgconf -X invoked %s%*s-*- org -*-\n\n",
isotimestamp (time (NULL)), 28, "");
es_fprintf (outfp, "* General information\n");
es_fprintf (outfp, "** Versions\n");
show_version_gnupg (outfp, " ");
es_fprintf (outfp, " Libgcrypt %s\n", gcry_check_version (NULL));
es_fprintf (outfp, " GpgRT %s\n", gpg_error_check_version (NULL));
#ifdef HAVE_W32_SYSTEM
es_fprintf (outfp, " Codepages:");
if (GetConsoleCP () != GetConsoleOutputCP ())
es_fprintf (outfp, " %u/%u", GetConsoleCP (), GetConsoleOutputCP ());
else
es_fprintf (outfp, " %u", GetConsoleCP ());
es_fprintf (outfp, " %u", GetACP ());
es_fprintf (outfp, " %u\n", GetOEMCP ());
#endif
es_fprintf (outfp, "\n\n");
es_fprintf (outfp, "** Directories\n");
list_dirs (outfp, NULL, 1);
es_fprintf (outfp, "\n");
es_fprintf (outfp, "** Environment\n#+begin_example\n");
for (idx=0; idx < DIM(envvars); idx++)
if ((s = getenv (envvars[idx])))
es_fprintf (outfp, "%s=%s\n", envvars[idx], s);
es_fprintf (outfp, "#+end_example\n");
es_fprintf (outfp, "* Config files\n");
fname = make_filename (gnupg_sysconfdir (), "gpgconf.conf", NULL);
if (!gnupg_access (fname, F_OK))
{
got_gpgconfconf = 1;
show_configs_one_file (fname, 1, outfp, &list);
es_fprintf (outfp, "\n");
}
xfree (fname);
for (idx = 0; idx < DIM (names); idx++)
{
fname = make_filename (gnupg_sysconfdir (), names[idx], NULL);
show_configs_one_file (fname, 1, outfp, &list);
xfree (fname);
fname = make_filename (gnupg_homedir (), names[idx], NULL);
show_configs_one_file (fname, 0, outfp, &list);
xfree (fname);
es_fprintf (outfp, "\n");
}
/* Print the encountered registry values and envvars. */
es_fprintf (outfp, "* Other info\n");
if (list)
{
any = 0;
for (sl = list; sl; sl = sl->next)
if (!(sl->flags & 1))
{
if (!any)
{
any = 1;
es_fprintf (outfp,
"** List of encountered environment variables\n"
"#+begin_example\n");
}
if ((s = getenv (sl->d)))
es_fprintf (outfp, " %-12s ->%s<-\n", sl->d, s);
else
es_fprintf (outfp, " %-12s [not set]\n", sl->d);
}
if (any)
es_fprintf (outfp, "#+end_example\n");
}
#ifdef HAVE_W32_SYSTEM
es_fprintf (outfp, "** Registry entries\n");
es_fprintf (outfp, "#+begin_example\n");
any = 0;
if (list)
{
for (sl = list; sl; sl = sl->next)
if ((sl->flags & 1))
{
char *p;
int from_hklm;
if (!any)
{
any = 1;
es_fprintf (outfp, "Encountered in config files:\n");
}
if ((p = read_w32_reg_string (sl->d, &from_hklm)))
es_fprintf (outfp, " %s ->%s<-%s\n", sl->d, p,
from_hklm? " [hklm]":"");
else
es_fprintf (outfp, " %s [not set]\n", sl->d);
xfree (p);
}
}
show_other_registry_entries (outfp);
show_registry_entries_from_file (outfp);
es_fprintf (outfp, "#+end_example\n");
#endif /*HAVE_W32_SYSTEM*/
free_strlist (list);
/* Additional warning. */
anywarn = 0;
if (got_gpgconfconf)
{
anywarn = 1;
es_fprintf (outfp, "* Warnings\n");
es_fprintf (outfp,
"- Legacy config file \"gpgconf.conf\" found\n");
}
/* Check for uncommon files in the home directory. */
dir = gnupg_opendir (gnupg_homedir ());
if (!dir)
{
err = gpg_error_from_syserror ();
log_error ("error reading directory \"%s\": %s\n",
gnupg_homedir (), gpg_strerror (err));
return;
}
any = 0;
while ((dir_entry = gnupg_readdir (dir)))
{
for (idx = 0; idx < DIM (names); idx++)
{
n = strlen (names[idx]);
if (!ascii_strncasecmp (dir_entry->d_name, names[idx], n)
&& dir_entry->d_name[n] == '-'
&& ascii_strncasecmp (dir_entry->d_name, "gpg.conf-1", 10))
{
if (!anywarn)
{
anywarn = 1;
es_fprintf (outfp, "* Warnings\n");
}
if (!any)
{
any = 1;
es_fprintf (outfp,
"- Suspicious files in \"%s\":\n",
gnupg_homedir ());
}
es_fprintf (outfp, " - %s\n", dir_entry->d_name);
}
}
}
gnupg_closedir (dir);
es_fprintf (outfp, "# eof #\n");
}
diff --git a/tools/gpgtar-list.c b/tools/gpgtar-list.c
index 55d9246af..74706cddd 100644
--- a/tools/gpgtar-list.c
+++ b/tools/gpgtar-list.c
@@ -1,617 +1,617 @@
/* gpgtar-list.c - List a TAR archive
* Copyright (C) 2016-2017, 2019-2022 g10 Code GmbH
* Copyright (C) 2010, 2012, 2013 Werner Koch
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "../common/i18n.h"
#include <gpg-error.h>
#include "gpgtar.h"
#include "../common/exechelp.h"
#include "../common/sysutils.h"
#include "../common/ccparray.h"
static unsigned long long
parse_xoctal (const void *data, size_t length, const char *filename)
{
const unsigned char *p = data;
unsigned long long value;
if (!length)
value = 0;
else if ( (*p & 0x80))
{
/* Binary format. */
value = (*p++ & 0x7f);
while (--length)
{
value <<= 8;
value |= *p++;
}
}
else
{
/* Octal format */
value = 0;
/* Skip leading spaces and zeroes. */
for (; length && (*p == ' ' || *p == '0'); length--, p++)
;
for (; length && *p; length--, p++)
{
if (*p >= '0' && *p <= '7')
{
value <<= 3;
value += (*p - '0');
}
else
{
log_error ("%s: invalid octal number encountered - assuming 0\n",
filename);
value = 0;
break;
}
}
}
return value;
}
static tar_header_t
parse_header (const void *record, const char *filename, tarinfo_t info)
{
const struct ustar_raw_header *raw = record;
size_t n, namelen, prefixlen;
tar_header_t header;
int use_prefix;
int anyerror = 0;
info->headerblock = info->nblocks - 1;
use_prefix = (!memcmp (raw->magic, "ustar", 5)
&& (raw->magic[5] == ' ' || !raw->magic[5]));
for (namelen=0; namelen < sizeof raw->name && raw->name[namelen]; namelen++)
;
if (namelen == sizeof raw->name)
{
log_info ("%s: warning: name not terminated by a nul\n", filename);
anyerror = 1;
}
for (n=namelen+1; n < sizeof raw->name; n++)
if (raw->name[n])
{
log_info ("%s: warning: garbage after name\n", filename);
anyerror = 1;
break;
}
if (use_prefix && raw->prefix[0])
{
for (prefixlen=0; (prefixlen < sizeof raw->prefix
&& raw->prefix[prefixlen]); prefixlen++)
;
if (prefixlen == sizeof raw->prefix)
log_info ("%s: warning: prefix not terminated by a nul (block %llu)\n",
filename, info->headerblock);
for (n=prefixlen+1; n < sizeof raw->prefix; n++)
if (raw->prefix[n])
{
log_info ("%s: warning: garbage after prefix\n", filename);
anyerror = 1;
break;
}
}
else
prefixlen = 0;
header = xtrycalloc (1, sizeof *header + prefixlen + 1 + namelen);
if (!header)
{
log_error ("%s: error allocating header: %s\n",
filename, gpg_strerror (gpg_error_from_syserror ()));
return NULL;
}
if (prefixlen)
{
n = prefixlen;
memcpy (header->name, raw->prefix, n);
if (raw->prefix[n-1] != '/')
header->name[n++] = '/';
}
else
n = 0;
memcpy (header->name+n, raw->name, namelen);
header->name[n+namelen] = 0;
header->mode = parse_xoctal (raw->mode, sizeof raw->mode, filename);
header->uid = parse_xoctal (raw->uid, sizeof raw->uid, filename);
header->gid = parse_xoctal (raw->gid, sizeof raw->gid, filename);
header->size = parse_xoctal (raw->size, sizeof raw->size, filename);
header->mtime = parse_xoctal (raw->mtime, sizeof raw->mtime, filename);
/* checksum = */
switch (raw->typeflag[0])
{
case '0': header->typeflag = TF_REGULAR; break;
case '1': header->typeflag = TF_HARDLINK; break;
case '2': header->typeflag = TF_SYMLINK; break;
case '3': header->typeflag = TF_CHARDEV; break;
case '4': header->typeflag = TF_BLOCKDEV; break;
case '5': header->typeflag = TF_DIRECTORY; break;
case '6': header->typeflag = TF_FIFO; break;
case '7': header->typeflag = TF_RESERVED; break;
case 'g': header->typeflag = TF_GEXTHDR; break;
case 'x': header->typeflag = TF_EXTHDR; break;
default: header->typeflag = TF_UNKNOWN; break;
}
/* Compute the number of data records following this header. */
if (header->typeflag == TF_REGULAR
|| header->typeflag == TF_EXTHDR
|| header->typeflag == TF_UNKNOWN)
header->nrecords = (header->size + RECORDSIZE-1)/RECORDSIZE;
else
header->nrecords = 0;
if (anyerror)
{
log_info ("%s: header block %llu is corrupt"
" (size=%llu type=%d nrec=%llu)\n",
filename, info->headerblock,
header->size, header->typeflag, header->nrecords);
/* log_printhex (record, RECORDSIZE, " "); */
}
return header;
}
-/* Parse the extended header. This funcion may modify BUFFER. */
+/* Parse the extended header. This function may modify BUFFER. */
static gpg_error_t
parse_extended_header (const char *fname,
char *buffer, size_t buflen, strlist_t *r_exthdr)
{
unsigned int reclen;
unsigned char *p, *record;
strlist_t sl;
while (buflen)
{
record = buffer; /* Remember begin of record. */
reclen = 0;
for (p = buffer; buflen && digitp (p); buflen--, p++)
{
reclen *= 10;
reclen += (*p - '0');
}
if (!buflen || *p != ' ')
{
log_error ("%s: malformed record length in extended header\n", fname);
return gpg_error (GPG_ERR_INV_RECORD);
}
p++; /* Skip space. */
buflen--;
if (buflen + (p-record) < reclen)
{
log_error ("%s: extended header record larger"
" than total extended header data\n", fname);
return gpg_error (GPG_ERR_INV_RECORD);
}
if (reclen < (p-record)+2 || record[reclen-1] != '\n')
{
log_error ("%s: malformed extended header record\n", fname);
return gpg_error (GPG_ERR_INV_RECORD);
}
record[reclen-1] = 0; /* For convenience change LF to a Nul. */
reclen -= (p-record);
/* P points to the begin of the keyword and RECLEN is the
* remaining length of the record excluding the LF. */
if (memchr (p, 0, reclen-1)
&& (!strncmp (p, "path=", 5) || !strncmp (p, "linkpath=", 9)))
{
log_error ("%s: extended header record has an embedded nul"
" - ignoring\n", fname);
}
else if (!strncmp (p, "path=", 5))
{
sl = add_to_strlist_try (r_exthdr, p+5);
if (!sl)
return gpg_error_from_syserror ();
sl->flags = 1; /* Mark as path */
}
else if (!strncmp (p, "linkpath=", 9))
{
sl = add_to_strlist_try (r_exthdr, p+9);
if (!sl)
return gpg_error_from_syserror ();
sl->flags = 2; /* Mark as linkpath */
}
buffer = p + reclen;
buflen -= reclen;
}
return 0;
}
/* Read the next block, assuming it is a tar header. Returns a header
* object on success in R_HEADER, or an error. If the stream is
* consumed (i.e. end-of-archive), R_HEADER is set to NULL. In case
* of an error an error message is printed. If the header is an
* extended header, a string list is allocated and stored at
* R_EXTHEADER; the caller should provide a pointer to NULL. Such an
* extended header is fully processed here and the returned R_HEADER
* has then the next regular header. */
static gpg_error_t
read_header (estream_t stream, tarinfo_t info,
tar_header_t *r_header, strlist_t *r_extheader)
{
gpg_error_t err;
char record[RECORDSIZE];
int i;
tar_header_t hdr;
char *buffer;
size_t buflen, nrec;
err = read_record (stream, record);
if (err)
return err;
info->nblocks++;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
if (i == RECORDSIZE)
{
/* All zero header - check whether it is the first part of an
end of archive mark. */
err = read_record (stream, record);
if (err)
return err;
info->nblocks++;
for (i=0; i < RECORDSIZE && !record[i]; i++)
;
if (i != RECORDSIZE)
log_info ("%s: warning: skipping empty header\n",
es_fname_get (stream));
else
{
/* End of archive - FIXME: we might want to check for garbage. */
*r_header = NULL;
return 0;
}
}
*r_header = parse_header (record, es_fname_get (stream), info);
if (!*r_header)
return gpg_error_from_syserror ();
hdr = *r_header;
if (hdr->typeflag != TF_EXTHDR || !r_extheader)
return 0;
/* Read the extended header. */
if (!hdr->nrecords)
{
- /* More than 64k for an extedned header is surely too large. */
+ /* More than 64k for an extended header is surely too large. */
log_info ("%s: warning: empty extended header\n",
es_fname_get (stream));
return 0;
}
if (hdr->nrecords > 65536 / RECORDSIZE)
{
- /* More than 64k for an extedned header is surely too large. */
+ /* More than 64k for an extended header is surely too large. */
log_error ("%s: extended header too large - skipping\n",
es_fname_get (stream));
return 0;
}
buffer = xtrymalloc (hdr->nrecords * RECORDSIZE);
if (!buffer)
{
err = gpg_error_from_syserror ();
log_error ("%s: error allocating space for extended header: %s\n",
es_fname_get (stream), gpg_strerror (err));
return err;
}
buflen = 0;
for (nrec=0; nrec < hdr->nrecords;)
{
err = read_record (stream, buffer + buflen);
if (err)
{
xfree (buffer);
return err;
}
info->nblocks++;
nrec++;
if (nrec < hdr->nrecords || (hdr->size && !(hdr->size % RECORDSIZE)))
buflen += RECORDSIZE;
else
buflen += (hdr->size % RECORDSIZE);
}
err = parse_extended_header (es_fname_get (stream),
buffer, buflen, r_extheader);
if (err)
{
free_strlist (*r_extheader);
*r_extheader = NULL;
}
xfree (buffer);
- /* Now tha the extedned header has been read, we read the next
+ /* Now that the extended header has been read, we read the next
* header without allowing an extended header. */
return read_header (stream, info, r_header, NULL);
}
/* Skip the data records according to HEADER. Prints an error message
on error and return -1. */
static int
skip_data (estream_t stream, tarinfo_t info, tar_header_t header)
{
char record[RECORDSIZE];
unsigned long long n;
for (n=0; n < header->nrecords; n++)
{
if (read_record (stream, record))
return -1;
info->nblocks++;
}
return 0;
}
static void
print_header (tar_header_t header, strlist_t extheader, estream_t out)
{
unsigned long mask;
char modestr[10+1];
int i;
strlist_t sl;
const char *name, *linkname;
*modestr = '?';
switch (header->typeflag)
{
case TF_REGULAR: *modestr = '-'; break;
case TF_HARDLINK: *modestr = 'h'; break;
case TF_SYMLINK: *modestr = 'l'; break;
case TF_CHARDEV: *modestr = 'c'; break;
case TF_BLOCKDEV: *modestr = 'b'; break;
case TF_DIRECTORY:*modestr = 'd'; break;
case TF_FIFO: *modestr = 'f'; break;
case TF_RESERVED: *modestr = '='; break;
case TF_EXTHDR: break;
case TF_GEXTHDR: break;
case TF_UNKNOWN: break;
case TF_NOTSUP: break;
}
for (mask = 0400, i = 0; i < 9; i++, mask >>= 1)
modestr[1+i] = (header->mode & mask)? "rwxrwxrwx"[i]:'-';
if ((header->typeflag & 04000))
modestr[3] = modestr[3] == 'x'? 's':'S';
if ((header->typeflag & 02000))
modestr[6] = modestr[6] == 'x'? 's':'S';
if ((header->typeflag & 01000))
modestr[9] = modestr[9] == 'x'? 't':'T';
modestr[10] = 0;
/* FIXME: We do not parse the linkname unless its part of an
* extended header. */
name = header->name;
linkname = header->typeflag == TF_SYMLINK? "?" : NULL;
for (sl = extheader; sl; sl = sl->next)
{
if (sl->flags == 1)
name = sl->d;
else if (sl->flags == 2)
linkname = sl->d;
}
es_fprintf (out, "%s %lu %lu/%lu %12llu %s %s%s%s\n",
modestr, header->nlink, header->uid, header->gid, header->size,
isotimestamp (header->mtime),
name,
linkname? " -> " : "",
linkname? linkname : "");
}
/* List the tarball FILENAME or, if FILENAME is NULL, the tarball read
from stdin. */
gpg_error_t
gpgtar_list (const char *filename, int decrypt)
{
gpg_error_t err;
estream_t stream = NULL;
tar_header_t header = NULL;
strlist_t extheader = NULL;
struct tarinfo_s tarinfo_buffer;
tarinfo_t tarinfo = &tarinfo_buffer;
gnupg_process_t proc = NULL;
memset (&tarinfo_buffer, 0, sizeof tarinfo_buffer);
if (decrypt)
{
strlist_t arg;
ccparray_t ccp;
#ifdef HAVE_W32_SYSTEM
HANDLE except[2] = { INVALID_HANDLE_VALUE, INVALID_HANDLE_VALUE };
#else
int except[2] = { -1, -1 };
#endif
const char **argv;
gnupg_spawn_actions_t act = NULL;
ccparray_init (&ccp, 0);
if (opt.batch)
ccparray_put (&ccp, "--batch");
if (opt.require_compliance)
ccparray_put (&ccp, "--require-compliance");
if (opt.status_fd)
{
static char tmpbuf[40];
es_syshd_t hd;
snprintf (tmpbuf, sizeof tmpbuf, "--status-fd=%s", opt.status_fd);
ccparray_put (&ccp, tmpbuf);
es_syshd (opt.status_stream, &hd);
#ifdef HAVE_W32_SYSTEM
except[0] = hd.u.handle;
#else
except[0] = hd.u.fd;
#endif
}
ccparray_put (&ccp, "--output");
ccparray_put (&ccp, "-");
ccparray_put (&ccp, "--decrypt");
for (arg = opt.gpg_arguments; arg; arg = arg->next)
ccparray_put (&ccp, arg->d);
if (filename)
{
ccparray_put (&ccp, "--");
ccparray_put (&ccp, filename);
}
ccparray_put (&ccp, NULL);
argv = ccparray_get (&ccp, NULL);
if (!argv)
{
err = gpg_error_from_syserror ();
goto leave;
}
err = gnupg_spawn_actions_new (&act);
if (err)
{
xfree (argv);
goto leave;
}
#ifdef HAVE_W32_SYSTEM
gnupg_spawn_actions_set_inherit_handles (act, except);
#else
gnupg_spawn_actions_set_inherit_fds (act, except);
#endif
err = gnupg_process_spawn (opt.gpg_program, argv,
((filename ? 0 : GNUPG_PROCESS_STDIN_KEEP)
| GNUPG_PROCESS_STDOUT_PIPE), act, &proc);
gnupg_spawn_actions_release (act);
xfree (argv);
if (err)
goto leave;
gnupg_process_get_streams (proc, 0, NULL, &stream, NULL);
es_set_binary (stream);
}
else if (filename) /* No decryption requested. */
{
if (!strcmp (filename, "-"))
stream = es_stdin;
else
stream = es_fopen (filename, "rb,sysopen");
if (!stream)
{
err = gpg_error_from_syserror ();
log_error ("error opening '%s': %s\n", filename, gpg_strerror (err));
goto leave;
}
if (stream == es_stdin)
es_set_binary (es_stdin);
}
else
{
stream = es_stdin;
es_set_binary (es_stdin);
}
for (;;)
{
err = read_header (stream, tarinfo, &header, &extheader);
if (err || header == NULL)
goto leave;
print_header (header, extheader, es_stdout);
if (skip_data (stream, tarinfo, header))
goto leave;
free_strlist (extheader);
extheader = NULL;
xfree (header);
header = NULL;
}
if (proc)
{
err = es_fclose (stream);
stream = NULL;
if (err)
log_error ("error closing pipe: %s\n", gpg_strerror (err));
err = gnupg_process_wait (proc, 1);
if (!err)
{
int exitcode;
gnupg_process_ctl (proc, GNUPG_PROCESS_GET_EXIT_ID, &exitcode);
log_error ("running %s failed (exitcode=%d): %s",
opt.gpg_program, exitcode, gpg_strerror (err));
}
gnupg_process_release (proc);
proc = NULL;
}
leave:
free_strlist (extheader);
xfree (header);
if (stream != es_stdin)
es_fclose (stream);
return err;
}
gpg_error_t
gpgtar_read_header (estream_t stream, tarinfo_t info,
tar_header_t *r_header, strlist_t *r_extheader)
{
return read_header (stream, info, r_header, r_extheader);
}
void
gpgtar_print_header (tar_header_t header, strlist_t extheader, estream_t out)
{
if (header && out)
print_header (header, extheader, out);
}
diff --git a/tools/gpgtar.c b/tools/gpgtar.c
index f93ba2e65..3e4d135f0 100644
--- a/tools/gpgtar.c
+++ b/tools/gpgtar.c
@@ -1,701 +1,701 @@
/* gpgtar.c - A simple TAR implementation mainly useful for Windows.
* Copyright (C) 2010 Free Software Foundation, Inc.
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
/* GnuPG comes with a shell script gpg-zip which creates archive files
in the same format as PGP Zip, which is actually a USTAR format.
That is fine and works nicely on all Unices but for Windows we
don't have a compatible shell and the supply of tar programs is
limited. Given that we need just a few tar option and it is an
open question how many Unix concepts are to be mapped to Windows,
we might as well write our own little tar customized for use with
gpg. So here we go. */
#include <config.h>
#include <ctype.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#define INCLUDED_BY_MAIN_MODULE 1
#include "../common/util.h"
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/openpgpdefs.h"
#include "../common/init.h"
#include "../common/strlist.h"
#include "../common/comopt.h"
#include "gpgtar.h"
/* Constants to identify the commands and options. */
enum cmd_and_opt_values
{
aNull = 0,
aCreate = 600,
aExtract,
aEncrypt = 'e',
aDecrypt = 'd',
aSign = 's',
aList = 't',
oSymmetric = 'c',
oRecipient = 'r',
oUser = 'u',
oOutput = 'o',
oDirectory = 'C',
oQuiet = 'q',
oVerbose = 'v',
oFilesFrom = 'T',
oNoVerbose = 500,
aSignEncrypt,
oGpgProgram,
oSkipCrypto,
oOpenPGP,
oCMS,
oSetFilename,
oNull,
oUtf8Strings,
oNoCompress,
oBatch,
oAnswerYes,
oAnswerNo,
oStatusFD,
oRequireCompliance,
oWithLog,
/* Compatibility with gpg-zip. */
oGpgArgs,
oTarArgs,
oTarProgram,
/* Debugging. */
oDebug,
oDryRun
};
/* The list of commands and options. */
static gpgrt_opt_t opts[] = {
ARGPARSE_group (300, N_("@Commands:\n ")),
ARGPARSE_c (aCreate, "create", N_("create an archive")),
ARGPARSE_c (aExtract, "extract", N_("extract an archive")),
ARGPARSE_c (aEncrypt, "encrypt", N_("create an encrypted archive")),
ARGPARSE_c (aDecrypt, "decrypt", N_("extract an encrypted archive")),
ARGPARSE_c (aSign, "sign", N_("create a signed archive")),
ARGPARSE_c (aList, "list-archive", N_("list an archive")),
ARGPARSE_group (301, N_("@\nOptions:\n ")),
ARGPARSE_s_n (oSymmetric, "symmetric", N_("use symmetric encryption")),
ARGPARSE_s_s (oRecipient, "recipient", N_("|USER-ID|encrypt for USER-ID")),
ARGPARSE_s_s (oUser, "local-user",
N_("|USER-ID|use USER-ID to sign or decrypt")),
ARGPARSE_s_s (oOutput, "output", N_("|FILE|write output to FILE")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_s (oGpgProgram, "gpg", "@"),
ARGPARSE_s_n (oSkipCrypto, "skip-crypto", N_("skip the crypto processing")),
ARGPARSE_s_n (oDryRun, "dry-run", N_("do not make any changes")),
ARGPARSE_s_s (oSetFilename, "set-filename", "@"),
ARGPARSE_s_n (oOpenPGP, "openpgp", "@"),
ARGPARSE_s_n (oCMS, "cms", "@"),
ARGPARSE_s_n (oNoCompress, "no-compress", "@"),
ARGPARSE_s_n (oBatch, "batch", "@"),
ARGPARSE_s_n (oAnswerYes, "yes", "@"),
ARGPARSE_s_n (oAnswerNo, "no", "@"),
ARGPARSE_s_s (oStatusFD, "status-fd", "@"),
ARGPARSE_s_n (oRequireCompliance, "require-compliance", "@"),
ARGPARSE_s_n (oWithLog, "with-log", "@"),
ARGPARSE_group (302, N_("@\nTar options:\n ")),
ARGPARSE_s_s (oDirectory, "directory",
N_("|DIRECTORY|change to DIRECTORY first")),
ARGPARSE_s_s (oFilesFrom, "files-from",
N_("|FILE|get names to create from FILE")),
ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
#ifdef HAVE_W32_SYSTEM
ARGPARSE_s_n (oUtf8Strings, "utf8-strings",
N_("-T reads UTF-8 encoded names")),
#else
ARGPARSE_s_n (oUtf8Strings, "utf8-strings", "@"),
#endif
ARGPARSE_s_s (oGpgArgs, "gpg-args", "@"),
ARGPARSE_s_s (oTarArgs, "tar-args", "@"),
ARGPARSE_s_s (oTarProgram, "tar", "@"),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_end ()
};
/* The list of commands and options for tar that we understand. */
static gpgrt_opt_t tar_opts[] = {
ARGPARSE_s_s (oDirectory, "directory",
N_("|DIRECTORY|extract files into DIRECTORY")),
ARGPARSE_s_s (oFilesFrom, "files-from",
N_("|FILE|get names to create from FILE")),
ARGPARSE_s_n (oNull, "null", N_("-T reads null-terminated names")),
ARGPARSE_end ()
};
/* Global flags. */
static enum cmd_and_opt_values cmd = 0;
static int skip_crypto = 0;
static const char *files_from = NULL;
static int null_names = 0;
static int any_debug;
/* Print usage information and provide strings for help. */
static const char *
my_strusage( int level )
{
const char *p;
switch (level)
{
case 9: p = "GPL-3.0-or-later"; break;
case 11: p = "@GPGTAR@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 14: p = GNUPG_DEF_COPYRIGHT_LINE; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 1:
case 40:
p = _("Usage: gpgtar [options] [files] [directories] (-h for help)");
break;
case 41:
p = _("Syntax: gpgtar [options] [files] [directories]\n"
"Encrypt or sign files into an archive\n");
break;
default: p = NULL; break;
}
return p;
}
static void
set_cmd (enum cmd_and_opt_values *ret_cmd, enum cmd_and_opt_values new_cmd)
{
enum cmd_and_opt_values c = *ret_cmd;
if (!c || c == new_cmd)
c = new_cmd;
else if (c == aSign && new_cmd == aEncrypt)
c = aSignEncrypt;
else if (c == aEncrypt && new_cmd == aSign)
c = aSignEncrypt;
else
{
log_error (_("conflicting commands\n"));
exit (2);
}
*ret_cmd = c;
}
/* Shell-like argument splitting.
For compatibility with gpg-zip we accept arguments for GnuPG and
tar given as a string argument to '--gpg-args' and '--tar-args'.
gpg-zip was implemented as a Bourne Shell script, and therefore, we
need to split the string the same way the shell would. */
static int
shell_parse_stringlist (const char *str, strlist_t *r_list)
{
strlist_t list = NULL;
const char *s = str;
char quoted = 0;
char arg[1024];
char *p = arg;
#define addchar(c) \
do { if (p - arg + 2 < sizeof arg) *p++ = (c); else return 1; } while (0)
#define addargument() \
do { \
if (p > arg) \
{ \
*p = 0; \
append_to_strlist (&list, arg); \
p = arg; \
} \
} while (0)
#define unquoted 0
#define singlequote '\''
#define doublequote '"'
for (; *s; s++)
{
switch (quoted)
{
case unquoted:
if (isspace (*s))
addargument ();
else if (*s == singlequote || *s == doublequote)
quoted = *s;
else
addchar (*s);
break;
case singlequote:
if (*s == singlequote)
quoted = unquoted;
else
addchar (*s);
break;
case doublequote:
log_assert (s > str || !"cannot be quoted at first char");
if (*s == doublequote && *(s - 1) != '\\')
quoted = unquoted;
else
addchar (*s);
break;
default:
log_assert (! "reached");
}
}
/* Append the last argument. */
addargument ();
#undef doublequote
#undef singlequote
#undef unquoted
#undef addargument
#undef addchar
*r_list = list;
return 0;
}
/* Like shell_parse_stringlist, but returns an argv vector
instead of a strlist. */
static int
shell_parse_argv (const char *s, int *r_argc, char ***r_argv)
{
int i;
strlist_t list;
if (shell_parse_stringlist (s, &list))
return 1;
*r_argc = strlist_length (list);
*r_argv = xtrycalloc (*r_argc, sizeof **r_argv);
if (*r_argv == NULL)
return 1;
for (i = 0; list; i++)
{
gpgrt_annotate_leaked_object (list);
(*r_argv)[i] = list->d;
list = list->next;
}
gpgrt_annotate_leaked_object (*r_argv);
return 0;
}
/* Command line parsing. */
static void
parse_arguments (gpgrt_argparse_t *pargs, gpgrt_opt_t *popts)
{
int no_more_options = 0;
while (!no_more_options && gpgrt_argparse (NULL, pargs, popts))
{
switch (pargs->r_opt)
{
case oOutput: opt.outfile = pargs->r.ret_str; break;
case oDirectory: opt.directory = pargs->r.ret_str; break;
case oSetFilename: opt.filename = pargs->r.ret_str; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oNoVerbose: opt.verbose = 0; break;
case oFilesFrom: files_from = pargs->r.ret_str; break;
case oNull: null_names = 1; break;
case oUtf8Strings: opt.utf8strings = 1; break;
case oNoCompress: opt.no_compress = 1; break;
case aList:
case aDecrypt:
case aEncrypt:
case aSign:
set_cmd (&cmd, pargs->r_opt);
break;
case aCreate:
set_cmd (&cmd, aEncrypt);
skip_crypto = 1;
break;
case aExtract:
set_cmd (&cmd, aDecrypt);
skip_crypto = 1;
break;
case oRecipient:
add_to_strlist (&opt.recipients, pargs->r.ret_str);
break;
case oUser:
opt.user = pargs->r.ret_str;
break;
case oSymmetric:
set_cmd (&cmd, aEncrypt);
opt.symmetric = 1;
break;
case oGpgProgram:
opt.gpg_program = pargs->r.ret_str;
break;
case oSkipCrypto:
skip_crypto = 1;
break;
case oOpenPGP: /* Dummy option for now. */ break;
case oCMS: /* Dummy option for now. */ break;
case oBatch: opt.batch = 1; break;
case oAnswerYes: opt.answer_yes = 1; break;
case oAnswerNo: opt.answer_no = 1; break;
case oStatusFD: opt.status_fd = pargs->r.ret_str; break;
case oRequireCompliance: opt.require_compliance = 1; break;
case oWithLog: opt.with_log = 1; break;
case oGpgArgs:;
{
strlist_t list;
if (shell_parse_stringlist (pargs->r.ret_str, &list))
log_error ("failed to parse gpg arguments '%s'\n",
pargs->r.ret_str);
else
{
if (opt.gpg_arguments)
strlist_last (opt.gpg_arguments)->next = list;
else
opt.gpg_arguments = list;
}
}
break;
case oTarProgram: /* Dummy option. */
break;
case oTarArgs:
{
int tar_argc;
char **tar_argv;
if (shell_parse_argv (pargs->r.ret_str, &tar_argc, &tar_argv))
log_error ("failed to parse tar arguments '%s'\n",
pargs->r.ret_str);
else
{
gpgrt_argparse_t tar_args;
tar_args.argc = &tar_argc;
tar_args.argv = &tar_argv;
tar_args.flags = ARGPARSE_FLAG_ARG0;
parse_arguments (&tar_args, tar_opts);
gpgrt_argparse (NULL, &tar_args, NULL);
if (tar_args.err)
log_error ("unsupported tar arguments '%s'\n",
pargs->r.ret_str);
pargs->err = tar_args.err;
}
}
break;
case oDebug:
any_debug = 1;
break;
case oDryRun:
opt.dry_run = 1;
break;
default: pargs->err = 2; break;
}
}
}
/* gpgtar main. */
int
main (int argc, char **argv)
{
gpg_error_t err;
const char *fname;
gpgrt_argparse_t pargs;
gnupg_reopen_std (GPGTAR_NAME);
gpgrt_set_strusage (my_strusage);
log_set_prefix (GPGTAR_NAME, GPGRT_LOG_WITH_PREFIX|GPGRT_LOG_NO_REGISTRY);
/* Make sure that our subsystems are ready. */
i18n_init();
init_common_subsystems (&argc, &argv);
gnupg_init_signals (0, NULL);
log_assert (sizeof (struct ustar_raw_header) == 512);
/* Set default options */
opt.status_fd = NULL;
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* Parse the command line. */
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags = ARGPARSE_FLAG_KEEP;
parse_arguments (&pargs, opts);
gpgrt_argparse (NULL, &pargs, NULL);
if (log_get_errorcount (0))
exit (2);
/* Get a log file from common.conf. */
if (!parse_comopt (GNUPG_MODULE_NAME_GPGTAR, any_debug) && comopt.logfile)
log_set_file (comopt.logfile);
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("NOTE: '%s' is not considered an option\n"), argv[i]);
}
/* Set status stream for our own use of --status-fd. The original
* status fd is passed verbatim to gpg. */
if (opt.status_fd)
{
es_syshd_t syshd;
err = gnupg_parse_fdstr (opt.status_fd, &syshd);
if (err)
log_fatal ("status-fd is invalid: %s\n", gpg_strerror (err));
if (syshd.type == ES_SYSHD_FD && syshd.u.fd == 1)
{
opt.status_stream = es_stdout;
if (!skip_crypto)
log_fatal ("using stdout for the status-fd is not possible\n");
}
else if (syshd.type == ES_SYSHD_FD && syshd.u.fd == 2)
opt.status_stream = es_stderr;
else
{
opt.status_stream = es_sysopen (&syshd, "w");
if (opt.status_stream)
es_setvbuf (opt.status_stream, NULL, _IOLBF, 0);
}
if (!opt.status_stream)
{
log_fatal ("can't open fd %s for status output: %s\n",
opt.status_fd, strerror (errno));
}
}
if (! opt.gpg_program)
opt.gpg_program = gnupg_module_name (GNUPG_MODULE_NAME_GPG);
if (opt.verbose > 1)
opt.debug_level = 1024;
switch (cmd)
{
case aDecrypt:
case aList:
if (argc > 1)
gpgrt_usage (1);
fname = (argc && strcmp (*argv, "-"))? *argv : NULL;
if (opt.filename)
log_info ("note: ignoring option --set-filename\n");
if (files_from)
log_info ("note: ignoring option --files-from\n");
if (cmd == aDecrypt)
{
err = gpgtar_extract (fname, !skip_crypto);
if (err && !log_get_errorcount (0))
log_error ("extracting archive failed: %s\n", gpg_strerror (err));
}
else
{
err = gpgtar_list (fname, !skip_crypto);
if (err && !log_get_errorcount (0))
log_error ("listing archive failed: %s\n", gpg_strerror (err));
}
break;
case aEncrypt:
case aSign:
case aSignEncrypt:
if ((!argc && !files_from)
|| (argc && files_from))
gpgrt_usage (1);
if (opt.filename)
log_info ("note: ignoring option --set-filename\n");
err = gpgtar_create (files_from? NULL : argv,
files_from,
null_names,
!skip_crypto
&& (cmd == aEncrypt || cmd == aSignEncrypt),
cmd == aSign || cmd == aSignEncrypt);
if (err && log_get_errorcount (0) == 0)
log_error ("creating archive failed: %s\n", gpg_strerror (err));
break;
default:
log_error (_("invalid command (there is no implicit command)\n"));
err = 0;
break;
}
if (opt.status_stream)
{
if (err || log_get_errorcount (0))
es_fprintf (opt.status_stream, "[GNUPG:] FAILURE - %u\n",
err? err : gpg_error (GPG_ERR_GENERAL));
else
es_fprintf (opt.status_stream, "[GNUPG:] SUCCESS\n");
}
return log_get_errorcount (0)? 1:0;
}
/* Read the next record from STREAM. RECORD is a buffer provided by
the caller and must be at least of size RECORDSIZE. The function
return 0 on success and error code on failure; a diagnostic
printed as well. Note that there is no need for an EOF indicator
because a tarball has an explicit EOF record. */
gpg_error_t
read_record (estream_t stream, void *record)
{
gpg_error_t err;
size_t nread;
nread = es_fread (record, 1, RECORDSIZE, stream);
if (nread != RECORDSIZE)
{
err = gpg_error_from_syserror ();
if (es_ferror (stream))
log_error ("error reading '%s': %s\n",
es_fname_get (stream), gpg_strerror (err));
else
log_error ("error reading '%s': premature EOF "
"(size of last record: %zu)\n",
es_fname_get (stream), nread);
}
else
err = 0;
return err;
}
/* Write the RECORD of size RECORDSIZE to STREAM. FILENAME is the
name of the file used for diagnostics. */
gpg_error_t
write_record (estream_t stream, const void *record)
{
gpg_error_t err;
size_t nwritten;
nwritten = es_fwrite (record, 1, RECORDSIZE, stream);
if (nwritten != RECORDSIZE)
{
err = gpg_error_from_syserror ();
log_error ("error writing '%s': %s\n",
es_fname_get (stream), gpg_strerror (err));
}
else
err = 0;
return err;
}
/* Return true if FP is an unarmored OpenPGP message. Note that this
function reads a few bytes from FP but pushes them back. */
#if 0
static int
openpgp_message_p (estream_t fp)
{
int ctb;
ctb = es_getc (fp);
if (ctb != EOF)
{
if (es_ungetc (ctb, fp))
log_fatal ("error ungetting first byte: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
if ((ctb & 0x80))
{
switch ((ctb & 0x40) ? (ctb & 0x3f) : ((ctb>>2)&0xf))
{
case PKT_MARKER:
case PKT_SYMKEY_ENC:
case PKT_ONEPASS_SIG:
case PKT_PUBKEY_ENC:
case PKT_SIGNATURE:
case PKT_COMMENT:
case PKT_OLD_COMMENT:
case PKT_PLAINTEXT:
case PKT_COMPRESSED:
case PKT_ENCRYPTED:
return 1; /* Yes, this seems to be an OpenPGP message. */
default:
break;
}
}
}
return 0;
}
#endif
diff --git a/tpm2d/Makefile.am b/tpm2d/Makefile.am
index ef9487139..4394b102f 100644
--- a/tpm2d/Makefile.am
+++ b/tpm2d/Makefile.am
@@ -1,37 +1,37 @@
-# Makfile.am - Makefile for tpm2d
+# Makefile.am - Makefile for tpm2d
# Copyright (C) 2021 James Bottomley <James.Bottomley@HansenPartnership.com>
#
# This file is part of GnuPG.
#
# GnuPG is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 3 of the License, or
# (at your option) any later version.
#
# GnuPG is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, see <https://www.gnu.org/licenses/>.
# SPDX-License-Identifier: GPL-3.0-or-later
AM_CPPFLAGS =
include $(top_srcdir)/am/cmacros.am
libexec_PROGRAMS = tpm2daemon
AM_CFLAGS = $(LIBGCRYPT_CFLAGS) \
$(KSBA_CFLAGS) $(LIBASSUAN_CFLAGS) $(NPTH_CFLAGS) $(LIBTSS_CFLAGS)
tpm2daemon_SOURCES = \
command.c \
tpm2daemon.c \
tpm2.c tpm2.h \
tpm2daemon.h ibm-tss.h intel-tss.h
tpm2daemon_LDADD = $(libcommonpth) \
$(LIBGCRYPT_LIBS) $(KSBA_LIBS) $(LIBASSUAN_LIBS) $(NPTH_LIBS) \
$(GPG_ERROR_LIBS) $(LIBINTL) $(LIBICONV) $(LIBTSS_LIBS)
diff --git a/tpm2d/tpm2daemon.c b/tpm2d/tpm2daemon.c
index 8e16e931a..46d20388a 100644
--- a/tpm2d/tpm2daemon.c
+++ b/tpm2d/tpm2daemon.c
@@ -1,1290 +1,1290 @@
/* tpm2daemon.c - The GnuPG tpm2 Daemon
* Copyright (C) 2001-2002, 2004-2005, 2007-2009 Free Software Foundation, Inc.
* Copyright (C) 2001-2002, 2004-2005, 2007-2014 Werner Koch
*
* This file is part of GnuPG.
*
* GnuPG is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* GnuPG is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, see <https://www.gnu.org/licenses/>.
* SPDX-License-Identifier: GPL-3.0-or-later
*/
#include <config.h>
#include <stdio.h>
#include <stdlib.h>
#include <stddef.h>
#include <stdarg.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <time.h>
#include <fcntl.h>
#ifndef HAVE_W32_SYSTEM
#include <sys/socket.h>
#include <sys/un.h>
#endif /*HAVE_W32_SYSTEM*/
#include <unistd.h>
#include <signal.h>
#include <npth.h>
#define INCLUDED_BY_MAIN_MODULE 1
#define GNUPG_COMMON_NEED_AFLOCAL
#include "tpm2daemon.h"
#include <gcrypt.h>
#include <assuan.h> /* malloc hooks */
#include "../common/i18n.h"
#include "../common/sysutils.h"
#include "../common/gc-opt-flags.h"
#include "../common/asshelp.h"
#include "../common/exechelp.h"
#include "../common/init.h"
#ifndef ENAMETOOLONG
# define ENAMETOOLONG EINVAL
#endif
enum cmd_and_opt_values
{ aNull = 0,
oCsh = 'c',
oQuiet = 'q',
oSh = 's',
oVerbose = 'v',
oNoVerbose = 500,
aGPGConfList,
aGPGConfTest,
oOptions,
oDebug,
oDebugAll,
oDebugLevel,
oDebugWait,
oDebugAllowCoreDump,
oDebugLogTid,
oDebugAssuanLogCats,
oNoGreeting,
oNoOptions,
oHomedir,
oNoDetach,
oNoGrab,
oLogFile,
oServer,
oMultiServer,
oDaemon,
oListenBacklog,
oParent
};
static gpgrt_opt_t opts[] = {
ARGPARSE_c (aGPGConfList, "gpgconf-list", "@"),
ARGPARSE_c (aGPGConfTest, "gpgconf-test", "@"),
ARGPARSE_group (301, N_("@Options:\n ")),
ARGPARSE_s_n (oServer,"server", N_("run in server mode (foreground)")),
ARGPARSE_s_n (oMultiServer, "multi-server",
N_("run in multi server mode (foreground)")),
ARGPARSE_s_n (oDaemon, "daemon", N_("run in daemon mode (background)")),
ARGPARSE_s_n (oVerbose, "verbose", N_("verbose")),
ARGPARSE_s_n (oQuiet, "quiet", N_("be somewhat more quiet")),
ARGPARSE_s_n (oSh, "sh", N_("sh-style command output")),
ARGPARSE_s_n (oCsh, "csh", N_("csh-style command output")),
ARGPARSE_s_s (oOptions, "options", N_("|FILE|read options from FILE")),
ARGPARSE_s_s (oDebug, "debug", "@"),
ARGPARSE_s_n (oDebugAll, "debug-all", "@"),
ARGPARSE_s_s (oDebugLevel, "debug-level" ,
N_("|LEVEL|set the debugging level to LEVEL")),
ARGPARSE_s_i (oDebugWait, "debug-wait", "@"),
ARGPARSE_s_n (oDebugAllowCoreDump, "debug-allow-core-dump", "@"),
ARGPARSE_s_n (oDebugLogTid, "debug-log-tid", "@"),
ARGPARSE_p_u (oDebugAssuanLogCats, "debug-assuan-log-cats", "@"),
ARGPARSE_s_n (oNoDetach, "no-detach", N_("do not detach from the console")),
ARGPARSE_s_s (oLogFile, "log-file", N_("|FILE|write a log to FILE")),
ARGPARSE_s_s (oHomedir, "homedir", "@"),
ARGPARSE_s_i (oListenBacklog, "listen-backlog", "@"),
ARGPARSE_p_u (oParent, "tpm2-parent",
N_("Specify tpm2 parent for key")),
ARGPARSE_end ()
};
/* The list of supported debug flags. */
static struct debug_flags_s debug_flags [] =
{
{ DBG_MPI_VALUE , "mpi" },
{ DBG_CRYPTO_VALUE , "crypto" },
{ DBG_IPC_VALUE , "ipc" },
{ 0, NULL }
};
/* The timer tick used to check card removal.
We poll every 500ms to let the user immediately know a status
change.
For a card reader with an interrupt endpoint, this timer is not
used with the internal CCID driver.
This is not too good for power saving but given that there is no
easy way to block on card status changes it is the best we can do.
For PC/SC we could in theory use an extra thread to wait for status
changes but that requires a native thread because there is no way
to make the underlying PC/SC card change function block using a Npth
mechanism. Given that a native thread could only be used under W32
we don't do that at all. */
#define TIMERTICK_INTERVAL_SEC (0)
#define TIMERTICK_INTERVAL_USEC (500000)
/* Flag to indicate that a shutdown was requested. */
static int shutdown_pending;
/* It is possible that we are currently running under setuid permissions */
static int maybe_setuid = 1;
/* Flag telling whether we are running as a pipe server. */
static int pipe_server;
/* Name of the communication socket */
static char *socket_name;
/* Name of the redirected socket or NULL. */
static char *redir_socket_name;
/* We need to keep track of the server's nonces (these are dummies for
POSIX systems). */
static assuan_sock_nonce_t socket_nonce;
/* Value for the listen() backlog argument. Change at runtime with
* --listen-backlog. */
static int listen_backlog = 64;
#ifdef HAVE_W32_SYSTEM
static HANDLE the_event;
#else
/* PID to notify update of usb devices. */
static pid_t main_thread_pid;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
/* FD to notify changes. */
static int notify_fd;
#endif
static char *create_socket_name (char *standard_name);
static gnupg_fd_t create_server_socket (const char *name,
char **r_redir_name,
assuan_sock_nonce_t *nonce);
static void *start_connection_thread (void *arg);
static void handle_connections (gnupg_fd_t listen_fd);
static int active_connections;
static char *
make_libversion (const char *libname, const char *(*getfnc)(const char*))
{
const char *s;
char *result;
if (maybe_setuid)
{
gcry_control (GCRYCTL_INIT_SECMEM, 0, 0); /* Drop setuid. */
maybe_setuid = 0;
}
s = getfnc (NULL);
result = xmalloc (strlen (libname) + 1 + strlen (s) + 1);
strcpy (stpcpy (stpcpy (result, libname), " "), s);
return result;
}
static const char *
my_strusage (int level)
{
static char *ver_gcry;
const char *p;
switch (level)
{
case 11: p = "@TPM2DAEMON@ (@GNUPG@)";
break;
case 13: p = VERSION; break;
case 17: p = PRINTABLE_OS_NAME; break;
case 19: p = _("Please report bugs to <@EMAIL@>.\n"); break;
case 20:
if (!ver_gcry)
ver_gcry = make_libversion ("libgcrypt", gcry_check_version);
p = ver_gcry;
break;
case 1:
case 40: p = _("Usage: @TPM2DAEMON@ [options] (-h for help)");
break;
case 41: p = _("Syntax: tpm2daemon [options] [command [args]]\n"
"TPM2 daemon for @GNUPG@\n");
break;
default: p = NULL;
}
return p;
}
static int
tid_log_callback (unsigned long *rvalue)
{
int len = sizeof (*rvalue);
npth_t thread;
thread = npth_self ();
if (sizeof (thread) < len)
len = sizeof (thread);
memcpy (rvalue, &thread, len);
return 2; /* Use use hex representation. */
}
/* Setup the debugging. With a LEVEL of NULL only the active debug
flags are propagated to the subsystems. With LEVEL set, a specific
set of debug flags is set; thus overriding all flags already
set. */
static void
set_debug (const char *level)
{
int numok = (level && digitp (level));
int numlvl = numok? atoi (level) : 0;
if (!level)
;
else if (!strcmp (level, "none") || (numok && numlvl < 1))
opt.debug = 0;
else if (!strcmp (level, "basic") || (numok && numlvl <= 2))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "advanced") || (numok && numlvl <= 5))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "expert") || (numok && numlvl <= 8))
opt.debug = DBG_IPC_VALUE;
else if (!strcmp (level, "guru") || numok)
opt.debug = ~0;
else
{
log_error (_("invalid debug-level '%s' given\n"), level);
tpm2d_exit (2);
}
if (opt.debug && !opt.verbose)
opt.verbose = 1;
if (opt.debug && opt.quiet)
opt.quiet = 0;
if (opt.debug & DBG_MPI_VALUE)
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 2);
if (opt.debug & DBG_CRYPTO_VALUE )
gcry_control (GCRYCTL_SET_DEBUG_FLAGS, 1);
gcry_control (GCRYCTL_SET_VERBOSITY, (int)opt.verbose);
if (opt.debug)
parse_debug_flag (NULL, &opt.debug, debug_flags);
}
static void
cleanup (void)
{
if (socket_name && *socket_name)
{
char *name;
name = redir_socket_name? redir_socket_name : socket_name;
gnupg_remove (name);
*socket_name = 0;
}
}
int
main (int argc, char **argv )
{
gpgrt_argparse_t pargs;
int orig_argc;
char **orig_argv;
char *last_configname = NULL;
const char *configname = NULL;
const char *shell;
int parse_debug = 0;
const char *debug_level = NULL;
int greeting = 0;
int nogreeting = 0;
int multi_server = 0;
int is_daemon = 0;
int nodetach = 0;
int csh_style = 0;
char *logfile = NULL;
int debug_wait = 0;
int gpgconf_list = 0;
char *config_filename = NULL;
int allow_coredump = 0;
struct assuan_malloc_hooks malloc_hooks;
int res;
npth_t pipecon_handler;
early_system_init ();
gpgrt_set_strusage (my_strusage);
gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN);
/* Please note that we may running SUID(ROOT), so be very CAREFUL
when adding any stuff between here and the call to INIT_SECMEM()
somewhere after the option parsing */
log_set_prefix ("tpm2daemon", GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_PID);
/* Make sure that our subsystems are ready. */
i18n_init ();
init_common_subsystems (&argc, &argv);
malloc_hooks.malloc = gcry_malloc;
malloc_hooks.realloc = gcry_realloc;
malloc_hooks.free = gcry_free;
assuan_set_malloc_hooks (&malloc_hooks);
assuan_set_gpg_err_source (GPG_ERR_SOURCE_DEFAULT);
assuan_sock_init ();
setup_libassuan_logging (&opt.debug, NULL);
setup_libgcrypt_logging ();
gcry_control (GCRYCTL_USE_SECURE_RNDPOOL);
disable_core_dumps ();
/* Set default options. */
opt.parent = 0; /* 0 means TPM uses default */
shell = getenv ("SHELL");
if (shell && strlen (shell) >= 3 && !strcmp (shell+strlen (shell)-3, "csh") )
csh_style = 1;
/* Check whether we have a config file on the commandline */
orig_argc = argc;
orig_argv = argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags= (ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
while (gpgrt_argparse (NULL, &pargs, opts))
{
switch (pargs.r_opt)
{
case oDebug:
case oDebugAll:
parse_debug++;
break;
case oHomedir:
gnupg_set_homedir (pargs.r.ret_str);
break;
}
}
/* Reset the flags. */
pargs.flags &= ~(ARGPARSE_FLAG_KEEP | ARGPARSE_FLAG_NOVERSION);
/* initialize the secure memory. */
gcry_control (GCRYCTL_INIT_SECMEM, 16384, 0);
maybe_setuid = 0;
/*
Now we are working under our real uid
*/
- /* The configuraton directories for use by gpgrt_argparser. */
+ /* The configuration directories for use by gpgrt_argparser. */
gpgrt_set_confdir (GPGRT_CONFDIR_SYS, gnupg_sysconfdir ());
gpgrt_set_confdir (GPGRT_CONFDIR_USER, gnupg_homedir ());
/* We are re-using the struct, thus the reset flag. We OR the
- * flags so that the internal intialized flag won't be cleared. */
+ * flags so that the internal initialized flag won't be cleared. */
argc = orig_argc;
argv = orig_argv;
pargs.argc = &argc;
pargs.argv = &argv;
pargs.flags |= (ARGPARSE_FLAG_RESET
| ARGPARSE_FLAG_KEEP
| ARGPARSE_FLAG_SYS
| ARGPARSE_FLAG_USER);
while (gpgrt_argparser (&pargs, opts, TPM2DAEMON_NAME EXTSEP_S "conf"))
{
switch (pargs.r_opt)
{
case ARGPARSE_CONFFILE:
if (parse_debug)
log_info (_("reading options from '%s'\n"),
pargs.r_type? pargs.r.ret_str: "[cmdline]");
if (pargs.r_type)
{
xfree (last_configname);
last_configname = xstrdup (pargs.r.ret_str);
configname = last_configname;
}
else
configname = NULL;
break;
case aGPGConfList: gpgconf_list = 1; break;
case aGPGConfTest: gpgconf_list = 2; break;
case oQuiet: opt.quiet = 1; break;
case oVerbose: opt.verbose++; break;
case oDebug:
if (parse_debug_flag (pargs.r.ret_str, &opt.debug, debug_flags))
{
pargs.r_opt = ARGPARSE_INVALID_ARG;
pargs.err = ARGPARSE_PRINT_ERROR;
}
break;
case oDebugAll: opt.debug = ~0; break;
case oDebugLevel: debug_level = pargs.r.ret_str; break;
case oDebugWait: debug_wait = pargs.r.ret_int; break;
case oDebugAllowCoreDump:
enable_core_dumps ();
allow_coredump = 1;
break;
case oDebugLogTid:
log_set_pid_suffix_cb (tid_log_callback);
break;
case oDebugAssuanLogCats:
set_libassuan_log_cats (pargs.r.ret_ulong);
break;
case oNoGreeting: nogreeting = 1; break;
case oNoVerbose: opt.verbose = 0; break;
case oNoOptions: break; /* no-options */
case oHomedir: gnupg_set_homedir (pargs.r.ret_str); break;
case oNoDetach: nodetach = 1; break;
case oLogFile: logfile = pargs.r.ret_str; break;
case oCsh: csh_style = 1; break;
case oSh: csh_style = 0; break;
case oServer: pipe_server = 1; break;
case oMultiServer: pipe_server = 1; multi_server = 1; break;
case oDaemon: is_daemon = 1; break;
case oListenBacklog:
listen_backlog = pargs.r.ret_int;
break;
case oParent:
opt.parent = pargs.r.ret_ulong;
break;
default:
if (configname)
pargs.err = ARGPARSE_PRINT_WARNING;
else
pargs.err = ARGPARSE_PRINT_ERROR;
break;
}
}
gpgrt_argparse (NULL, &pargs, NULL); /* Release internal state. */
if (!last_configname)
config_filename = gpgrt_fnameconcat (gnupg_homedir (),
TPM2DAEMON_NAME EXTSEP_S "conf",
NULL);
else
{
config_filename = last_configname;
last_configname = NULL;
}
if (log_get_errorcount (0))
exit (2);
if (nogreeting )
greeting = 0;
if (greeting)
{
es_fprintf (es_stderr, "%s %s; %s\n",
gpgrt_strusage (11), gpgrt_strusage (13),
gpgrt_strusage (14) );
es_fprintf (es_stderr, "%s\n", gpgrt_strusage (15) );
}
#ifdef IS_DEVELOPMENT_VERSION
log_info ("NOTE: this is a development version!\n");
#endif
/* Print a warning if an argument looks like an option. */
if (!opt.quiet && !(pargs.flags & ARGPARSE_FLAG_STOP_SEEN))
{
int i;
for (i=0; i < argc; i++)
if (argv[i][0] == '-' && argv[i][1] == '-')
log_info (_("Note: '%s' is not considered an option\n"), argv[i]);
}
if (atexit (cleanup))
{
log_error ("atexit failed\n");
cleanup ();
exit (1);
}
set_debug (debug_level);
if (gpgconf_list == 2)
tpm2d_exit (0);
if (gpgconf_list)
{
es_printf ("verbose:%lu:\n"
"quiet:%lu:\n"
"debug-level:%lu:\"none:\n"
"log-file:%lu:\n",
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_NONE,
GC_OPT_FLAG_DEFAULT,
GC_OPT_FLAG_NONE );
tpm2d_exit (0);
}
/* Now start with logging to a file if this is desired. */
if (logfile)
{
log_set_file (logfile);
log_set_prefix (NULL, GPGRT_LOG_WITH_PREFIX | GPGRT_LOG_WITH_TIME | GPGRT_LOG_WITH_PID);
}
if (debug_wait && pipe_server)
{
log_debug ("waiting for debugger - my pid is %u .....\n",
(unsigned int)getpid ());
gnupg_sleep (debug_wait);
log_debug ("... okay\n");
}
if (pipe_server)
{
/* This is the simple pipe based server */
ctrl_t ctrl;
npth_attr_t tattr;
gnupg_fd_t fd = GNUPG_INVALID_FD;
#ifndef HAVE_W32_SYSTEM
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
/* If --debug-allow-core-dump has been given we also need to
switch the working directory to a place where we can actually
write. */
if (allow_coredump)
{
if (chdir ("/tmp"))
log_debug ("chdir to '/tmp' failed: %s\n", strerror (errno));
else
log_debug ("changed working directory to '/tmp'\n");
}
/* In multi server mode we need to listen on an additional
socket. Create that socket now before starting the handler
for the pipe connection. This allows that handler to send
back the name of that socket. */
if (multi_server)
{
socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name,
&socket_nonce);
}
res = npth_attr_init (&tattr);
if (res)
{
log_error ("error allocating thread attributes: %s\n",
strerror (res));
tpm2d_exit (2);
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
ctrl = xtrycalloc (1, sizeof *ctrl);
if ( !ctrl )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
tpm2d_exit (2);
}
ctrl->thread_startup.fd = GNUPG_INVALID_FD;
res = npth_create (&pipecon_handler, &tattr, start_connection_thread, ctrl);
if (res)
{
log_error ("error spawning pipe connection handler: %s\n",
strerror (res) );
xfree (ctrl);
tpm2d_exit (2);
}
npth_setname_np (pipecon_handler, "pipe-connection");
npth_attr_destroy (&tattr);
/* We run handle_connection to wait for the shutdown signal and
to run the ticker stuff. */
handle_connections (fd);
if (fd != GNUPG_INVALID_FD)
assuan_sock_close (fd);
}
else if (!is_daemon)
{
log_info (_("please use the option '--daemon'"
" to run the program in the background\n"));
}
else
{ /* Regular server mode */
gnupg_fd_t fd;
#ifndef HAVE_W32_SYSTEM
pid_t pid;
int i;
#endif
/* Create the socket. */
socket_name = create_socket_name (TPM2DAEMON_SOCK_NAME);
fd = create_server_socket (socket_name,
&redir_socket_name, &socket_nonce);
fflush (NULL);
#ifdef HAVE_W32_SYSTEM
(void)csh_style;
(void)nodetach;
#else
pid = fork ();
if (pid == (pid_t)-1)
{
log_fatal ("fork failed: %s\n", strerror (errno) );
exit (1);
}
else if (pid)
{ /* we are the parent */
char *infostr;
close (fd);
/* create the info string: <name>:<pid>:<protocol_version> */
if (gpgrt_asprintf (&infostr, "TPM2DAEMON_INFO=%s:%lu:1",
socket_name, (ulong) pid) < 0)
{
log_error ("out of core\n");
kill (pid, SIGTERM);
exit (1);
}
*socket_name = 0; /* don't let cleanup() remove the socket -
the child should do this from now on */
if (argc)
{ /* run the program given on the commandline */
if (putenv (infostr))
{
log_error ("failed to set environment: %s\n",
strerror (errno) );
kill (pid, SIGTERM );
exit (1);
}
execvp (argv[0], argv);
log_error ("failed to run the command: %s\n", strerror (errno));
kill (pid, SIGTERM);
exit (1);
}
else
{
/* Print the environment string, so that the caller can use
shell's eval to set it */
if (csh_style)
{
*strchr (infostr, '=') = ' ';
es_printf ( "setenv %s;\n", infostr);
}
else
{
es_printf ( "%s; export TPM2DAEMON_INFO;\n", infostr);
}
xfree (infostr);
exit (0);
}
/* NOTREACHED */
} /* end parent */
/* This is the child. */
npth_init ();
gpgrt_set_syscall_clamp (npth_unprotect, npth_protect);
assuan_control (ASSUAN_CONTROL_REINIT_SYSCALL_CLAMP, NULL);
/* Detach from tty and put process into a new session. */
if (!nodetach )
{
/* Close stdin, stdout and stderr unless it is the log stream. */
for (i=0; i <= 2; i++)
{
if (!log_test_fd (i) && i != fd )
{
if ( !close (i)
&& open ("/dev/null", i? O_WRONLY : O_RDONLY) == -1)
{
log_error ("failed to open '%s': %s\n",
"/dev/null", strerror (errno));
cleanup ();
exit (1);
}
}
}
if (setsid () == -1)
{
log_error ("setsid() failed: %s\n", strerror (errno) );
cleanup ();
exit (1);
}
}
{
struct sigaction sa;
sa.sa_handler = SIG_IGN;
sigemptyset (&sa.sa_mask);
sa.sa_flags = 0;
sigaction (SIGPIPE, &sa, NULL);
}
#endif /*!HAVE_W32_SYSTEM*/
if (gnupg_chdir (gnupg_daemon_rootdir ()))
{
log_error ("chdir to '%s' failed: %s\n",
gnupg_daemon_rootdir (), strerror (errno));
exit (1);
}
handle_connections (fd);
assuan_sock_close (fd);
}
xfree (config_filename);
return 0;
}
void
tpm2d_exit (int rc)
{
gcry_control (GCRYCTL_TERM_SECMEM );
rc = rc? rc : log_get_errorcount (0)? 2 : 0;
exit (rc);
}
static void
tpm2d_init_default_ctrl (ctrl_t ctrl)
{
(void)ctrl;
}
static void
tpm2d_deinit_default_ctrl (ctrl_t ctrl)
{
if (!ctrl)
return;
xfree (ctrl->in_data.value);
ctrl->in_data.value = NULL;
ctrl->in_data.valuelen = 0;
}
/* Return the name of the socket to be used to connect to this
process. If no socket is available, return NULL. */
const char *
tpm2d_get_socket_name (void)
{
if (socket_name && *socket_name)
return socket_name;
return NULL;
}
#ifndef HAVE_W32_SYSTEM
static void
handle_signal (int signo)
{
switch (signo)
{
case SIGHUP:
log_info ("SIGHUP received - "
"re-reading configuration\n");
/* reread_configuration (); */
break;
case SIGUSR1:
log_info ("SIGUSR1 received - printing internal information:\n");
/* Fixme: We need to see how to integrate pth dumping into our
logging system. */
/* pth_ctrl (PTH_CTRL_DUMPSTATE, log_get_stream ()); */
#if 0
app_dump_state ();
#endif
break;
case SIGUSR2:
log_info ("SIGUSR2 received - no action defined\n");
break;
case SIGCONT:
/* Nothing. */
log_debug ("SIGCONT received - breaking select\n");
break;
case SIGTERM:
if (!shutdown_pending)
log_info ("SIGTERM received - shutting down ...\n");
else
log_info ("SIGTERM received - still %i running threads\n",
active_connections);
shutdown_pending++;
if (shutdown_pending > 2)
{
log_info ("shutdown forced\n");
log_info ("%s %s stopped\n", gpgrt_strusage (11),
gpgrt_strusage (13) );
cleanup ();
tpm2d_exit (0);
}
break;
case SIGINT:
log_info ("SIGINT received - immediate shutdown\n");
log_info ( "%s %s stopped\n", gpgrt_strusage (11), gpgrt_strusage (13));
cleanup ();
tpm2d_exit (0);
break;
default:
log_info ("signal %d received - no action defined\n", signo);
}
}
#endif /*!HAVE_W32_SYSTEM*/
/* Create a name for the socket. We check for valid characters as
well as against a maximum allowed length for a unix domain socket
is done. The function terminates the process in case of an error.
- Retunrs: Pointer to an allcoated string with the absolute name of
+ Returns: Pointer to an allocated string with the absolute name of
the socket used. */
static char *
create_socket_name (char *standard_name)
{
char *name;
name = make_filename (gnupg_socketdir (), standard_name, NULL);
if (strchr (name, PATHSEP_C))
{
log_error (("'%s' are not allowed in the socket name\n"), PATHSEP_S);
tpm2d_exit (2);
}
return name;
}
/* Create a Unix domain socket with NAME. Returns the file descriptor
or terminates the process in case of an error. If the socket has
been redirected the name of the real socket is stored as a malloced
string at R_REDIR_NAME. */
static gnupg_fd_t
create_server_socket (const char *name, char **r_redir_name,
assuan_sock_nonce_t *nonce)
{
struct sockaddr *addr;
struct sockaddr_un *unaddr;
socklen_t len;
gnupg_fd_t fd;
int rc;
xfree (*r_redir_name);
*r_redir_name = NULL;
fd = assuan_sock_new (AF_UNIX, SOCK_STREAM, 0);
if (fd == GNUPG_INVALID_FD)
{
log_error (_("can't create socket: %s\n"), strerror (errno));
tpm2d_exit (2);
}
unaddr = xmalloc (sizeof (*unaddr));
addr = (struct sockaddr*)unaddr;
{
int redirected;
if (assuan_sock_set_sockaddr_un (name, addr, &redirected))
{
if (errno == ENAMETOOLONG)
log_error (_("socket name '%s' is too long\n"), name);
else
log_error ("error preparing socket '%s': %s\n",
name, gpg_strerror (gpg_error_from_syserror ()));
tpm2d_exit (2);
}
if (redirected)
{
*r_redir_name = xstrdup (unaddr->sun_path);
if (opt.verbose)
log_info ("redirecting socket '%s' to '%s'\n", name, *r_redir_name);
}
}
len = SUN_LEN (unaddr);
rc = assuan_sock_bind (fd, addr, len);
if (rc == -1 && errno == EADDRINUSE)
{
gnupg_remove (unaddr->sun_path);
rc = assuan_sock_bind (fd, addr, len);
}
if (rc != -1
&& (rc=assuan_sock_get_nonce (addr, len, nonce)))
log_error (_("error getting nonce for the socket\n"));
if (rc == -1)
{
log_error (_("error binding socket to '%s': %s\n"),
unaddr->sun_path,
gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
tpm2d_exit (2);
}
if (gnupg_chmod (unaddr->sun_path, "-rwx"))
log_error (_("can't set permissions of '%s': %s\n"),
unaddr->sun_path, strerror (errno));
if (listen (FD2INT (fd), listen_backlog) == -1)
{
log_error ("listen(fd, %d) failed: %s\n",
listen_backlog, gpg_strerror (gpg_error_from_syserror ()));
assuan_sock_close (fd);
tpm2d_exit (2);
}
if (opt.verbose)
log_info (_("listening on socket '%s'\n"), unaddr->sun_path);
return fd;
}
/* This is the standard connection thread's main function. */
static void *
start_connection_thread (void *arg)
{
ctrl_t ctrl = arg;
if (ctrl->thread_startup.fd != GNUPG_INVALID_FD
&& assuan_sock_check_nonce (ctrl->thread_startup.fd, &socket_nonce))
{
log_info (_("error reading nonce on fd %d: %s\n"),
FD_DBG (ctrl->thread_startup.fd), strerror (errno));
assuan_sock_close (ctrl->thread_startup.fd);
xfree (ctrl);
return NULL;
}
active_connections++;
tpm2d_init_default_ctrl (ctrl);
if (opt.verbose)
log_info (_("handler for fd %d started\n"),
FD_DBG (ctrl->thread_startup.fd));
/* If this is a pipe server, we request a shutdown if the command
handler asked for it. With the next ticker event and given that
no other connections are running the shutdown will then
happen. */
if (tpm2d_command_handler (ctrl, ctrl->thread_startup.fd)
&& pipe_server)
shutdown_pending = 1;
if (opt.verbose)
log_info (_("handler for fd %d terminated\n"),
FD_DBG (ctrl->thread_startup.fd));
tpm2d_deinit_default_ctrl (ctrl);
xfree (ctrl);
if (--active_connections == 0)
tpm2d_kick_the_loop ();
return NULL;
}
void
tpm2d_kick_the_loop (void)
{
#ifdef HAVE_W32_SYSTEM
int ret;
/* Kick the select loop. */
ret = SetEvent (the_event);
if (ret == 0)
log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n",
w32_strerror (-1));
#elif defined(HAVE_PSELECT_NO_EINTR)
write (notify_fd, "", 1);
#else
int ret;
ret = kill (main_thread_pid, SIGCONT);
if (ret < 0)
log_error ("SetEvent for tpm2d_kick_the_loop failed: %s\n",
gpg_strerror (gpg_error_from_syserror ()));
#endif
}
/* Connection handler loop. Wait for connection requests and spawn a
thread after accepting a connection. LISTEN_FD is allowed to be -1
in which case this code will only do regular timeouts and handle
signals. */
static void
handle_connections (gnupg_fd_t listen_fd)
{
npth_attr_t tattr;
struct sockaddr_un paddr;
socklen_t plen;
fd_set fdset, read_fdset;
int nfd;
int ret;
struct timespec timeout;
struct timespec *t;
int saved_errno;
#ifdef HAVE_W32_SYSTEM
HANDLE events[2];
unsigned int events_set;
#else
int signo;
#endif
#ifdef HAVE_PSELECT_NO_EINTR
int pipe_fd[2];
ret = gnupg_create_pipe (pipe_fd);
if (ret)
{
log_error ("pipe creation failed: %s\n", gpg_strerror (ret));
return;
}
notify_fd = pipe_fd[1];
#endif
ret = npth_attr_init (&tattr);
if (ret)
{
log_error ("npth_attr_init failed: %s\n", strerror (ret));
return;
}
npth_attr_setdetachstate (&tattr, NPTH_CREATE_DETACHED);
#ifdef HAVE_W32_SYSTEM
{
HANDLE h, h2;
SECURITY_ATTRIBUTES sa = { sizeof (SECURITY_ATTRIBUTES), NULL, TRUE};
events[0] = the_event = INVALID_HANDLE_VALUE;
events[1] = INVALID_HANDLE_VALUE;
h = CreateEvent (&sa, TRUE, FALSE, NULL);
if (!h)
log_error ("can't create tpm2d event: %s\n", w32_strerror (-1) );
else if (!DuplicateHandle (GetCurrentProcess (), h,
GetCurrentProcess (), &h2,
EVENT_MODIFY_STATE|SYNCHRONIZE, TRUE, 0))
{
log_error ("setting synchronize for tpm2d_kick_the_loop failed: %s\n",
w32_strerror (-1) );
CloseHandle (h);
}
else
{
CloseHandle (h);
events[0] = the_event = h2;
}
}
#else
npth_sigev_init ();
npth_sigev_add (SIGHUP);
npth_sigev_add (SIGUSR1);
npth_sigev_add (SIGUSR2);
npth_sigev_add (SIGINT);
npth_sigev_add (SIGCONT);
npth_sigev_add (SIGTERM);
npth_sigev_fini ();
main_thread_pid = getpid ();
#endif
FD_ZERO (&fdset);
nfd = 0;
if (listen_fd != GNUPG_INVALID_FD)
{
FD_SET (FD2INT (listen_fd), &fdset);
nfd = FD2NUM (listen_fd);
}
for (;;)
{
int periodical_check;
int max_fd = nfd;
if (shutdown_pending)
{
if (active_connections == 0)
break; /* ready */
/* Do not accept anymore connections but wait for existing
connections to terminate. We do this by clearing out all
file descriptors to wait for, so that the select will be
used to just wait on a signal or timeout event. */
FD_ZERO (&fdset);
listen_fd = GNUPG_INVALID_FD;
}
periodical_check = 0;
timeout.tv_sec = TIMERTICK_INTERVAL_SEC;
timeout.tv_nsec = TIMERTICK_INTERVAL_USEC * 1000;
if (shutdown_pending || periodical_check)
t = &timeout;
else
t = NULL;
/* POSIX says that fd_set should be implemented as a structure,
thus a simple assignment is fine to copy the entire set. */
read_fdset = fdset;
#ifdef HAVE_PSELECT_NO_EINTR
FD_SET (pipe_fd[0], &read_fdset);
if (max_fd < pipe_fd[0])
max_fd = pipe_fd[0];
#endif
#ifndef HAVE_W32_SYSTEM
ret = npth_pselect (max_fd+1, &read_fdset, NULL, NULL, t,
npth_sigev_sigmask ());
saved_errno = errno;
while (npth_sigev_get_pending (&signo))
handle_signal (signo);
#else
ret = npth_eselect (nfd+1, &read_fdset, NULL, NULL, t,
events, &events_set);
saved_errno = errno;
if (events_set & 1)
continue;
#endif
if (ret == -1 && saved_errno != EINTR)
{
log_error (_("npth_pselect failed: %s - waiting 1s\n"),
strerror (saved_errno));
gnupg_sleep (1);
continue;
}
if (ret <= 0)
/* Timeout. Will be handled when calculating the next timeout. */
continue;
#ifdef HAVE_PSELECT_NO_EINTR
if (FD_ISSET (pipe_fd[0], &read_fdset))
{
char buf[256];
read (pipe_fd[0], buf, sizeof buf);
}
#endif
if (listen_fd != GNUPG_INVALID_FD
&& FD_ISSET (FD2INT (listen_fd), &read_fdset))
{
ctrl_t ctrl;
gnupg_fd_t fd;
plen = sizeof paddr;
fd = assuan_sock_accept (listen_fd,
(struct sockaddr *)&paddr, &plen);
if (fd == GNUPG_INVALID_FD)
{
log_error ("accept failed: %s\n", strerror (errno));
}
else if ( !(ctrl = xtrycalloc (1, sizeof *ctrl)) )
{
log_error ("error allocating connection control data: %s\n",
strerror (errno) );
assuan_sock_close (fd);
}
else
{
char threadname[50];
npth_t thread;
snprintf (threadname, sizeof threadname, "conn fd=%d", FD_DBG (fd));
ctrl->thread_startup.fd = fd;
ret = npth_create (&thread, &tattr, start_connection_thread, ctrl);
if (ret)
{
log_error ("error spawning connection handler: %s\n",
strerror (ret));
xfree (ctrl);
assuan_sock_close (fd);
}
else
npth_setname_np (thread, threadname);
}
}
}
#ifdef HAVE_W32_SYSTEM
if (the_event != INVALID_HANDLE_VALUE)
CloseHandle (the_event);
#endif
#ifdef HAVE_PSELECT_NO_EINTR
close (pipe_fd[0]);
close (pipe_fd[1]);
#endif
cleanup ();
log_info (_("%s %s stopped\n"), gpgrt_strusage (11), gpgrt_strusage (13));
npth_attr_destroy (&tattr);
}
/* Return the number of active connections. */
int
get_active_connection_count (void)
{
return active_connections;
}

File Metadata

Mime Type
application/octet-stream
Expires
Tue, Oct 1, 5:35 AM (1 d, 23 h)
Storage Engine
chunks
Storage Format
Chunks
Storage Handle
h6AkLGYUUfRW

Event Timeline